Deployment & Security

Aura Workshop ships as a desktop app and a headless daemon image built from the same Rust crate. This page covers how to run it as a server, reach it from a browser, deploy it to a remote host, and — most importantly — what its security controls actually defend against. The single most important fact: isolation strength depends entirely on where the agent runs. The desktop "Docker mode" only wraps the bash tool; the daemon container isolates the whole process; guardrails and redaction are defense-in-depth on top, and several of them ship disabled by default.

Web viewer UI (browser access)

Aura Workshop embeds an axum HTTP server that serves the SolidJS UI over any browser on port 18800. In the desktop app the same server can be toggled on to reach the app from another device; the daemon image runs it as the primary surface. Depending on the mode, the server delivers either the full frontend or a lighter viewer SPA (set AURA_VIEWER_MODE=true) tuned for monitoring and driving a deployed agent.

Enabling it on the desktop app

  1. Open Settings → Connectivity.
  2. Toggle the Web UI Server on and set the port (default 18800).
  3. Optionally set a Bearer token so requests must authenticate.
  4. Open http://localhost:18800 — or http://<host-ip>:18800 from another machine on the LAN.
The browser UI has full feature parity with the desktop app — every navigation view, real-time SSE streaming, the complete REST API, file upload, folder selection, voice input, and all settings tabs. The transport differs (HTTP/SSE instead of Tauri IPC), not the feature set.

Serving the UI to anything beyond localhost means it is reachable by anyone who can route to the port. Configure a token (below) and, for anything past a trusted LAN, put it behind a TLS reverse proxy.

Docker daemon image

For always-on server use, run the aura-daemon image (coolkoo/aura-workshop:daemon-latest on Docker Hub). It runs the entire shared runtime — agent loop, tools, MCP, scheduler, listeners, webhooks, workflows, and the SQLite DB — inside the container. This is isolation layer G (below) and the strongest boundary Aura offers.

Single full mode

The daemon runs one mode. --mode is still accepted for backward compatibility: worker is an alias for full, and any other value is coerced to full with a log line. There are no inference-master / inference-worker modes and no LAN GPU cluster — Aura does not run local model inference. To use a local model, run any OpenAI-compatible server yourself (Ollama, LM Studio, vLLM, LocalAI, …) and add it as a custom provider in Settings → Providers; Aura talks to it over HTTP but does not bundle or supervise it.

Run it

docker run -d \
  --name aura-daemon \
  -p 18800:18800 \
  -p 18790:18790 \
  -v aura-data:/data \
  -e AURA_WEB_TOKEN=$(uuidgen) \
  -e AURA_API_KEY=sk-... \
  -e AURA_MODEL=deepseek-chat \
  -e AURA_BASE_URL=https://api.deepseek.com \
  coolkoo/aura-workshop:daemon-latest

The SQLite DB lives at /data/aura-workshop.db (WAL mode) and file memory at /root/.aura; mount /data on a volume to persist across restarts. The image is a multi-arch manifest spanning linux/amd64 and linux/arm64 — Docker pulls the right variant automatically. A built-in HEALTHCHECK hits /api/health every 30s.

Common environment variables

VariablePurposeDefault
AURA_WEB_TOKENBearer token for /api/* and most /acp/* (env wins over the DB setting)none
AURA_API_KEY / AURA_MODEL / AURA_BASE_URLSeed the default provider on first bootnone / deepseek-chat / —
AURA_VIEWER_MODEServe the viewer SPA instead of the full frontendfalse
AURA_REMOTE_DEPLOYMENTStrict auth — reject anonymous /api/* even with no token setfalse
AURA_HEARTBEAT_URL / AURA_DEPLOYMENT_IDPOST status every 30s + payload UUIDnone
AURA_LICENSE_KEYLicense key injected by the deployernone
AURA_DB_PATHSQLite path (daemon image only)/data/aura-workshop.db

ACP peer discovery & --net=host

ACP peer discovery uses a UDP broadcast on port 18802, which does not cross the default Docker bridge. If you want the daemon to discover other Aura agents on the LAN, run with --net=host. This is the only reason to use host networking — it is not for GPU passthrough or inference.

docker run -d \
  --name aura-daemon \
  --net=host \
  -v aura-data:/data \
  -e AURA_WEB_TOKEN=$(uuidgen) \
  coolkoo/aura-workshop:daemon-latest
Host networking widens the blast radius. --net=host gives the containerized agent the host's network namespace. Treat the daemon image as the trust boundary and only mount what the agent needs — never bind-mount your home directory into it. Reference compose files ship at src-tauri/docker/docker-compose.daemon.yml and docker-compose.remote.yml.

Reverse proxy + TLS

The daemon has no built-in TLS — it serves plain HTTP on 18800. Terminate HTTPS at Caddy (a reference Caddyfile ships at src-tauri/docker/Caddyfile) or any reverse proxy:

aura.example.com {
  reverse_proxy localhost:18800
}

The daemon image has no in-app auto-update — pull a fresh image and re-run with the same env and volume to upgrade.

Headless mode

For servers without a display, the daemon image is the recommended path — it is headless by design. The desktop binary can also run without a window on a machine that has no display server:

xvfb-run aura-workshop

It starts without rendering a GUI and serves the full web UI for browser access. Every feature remains available through the REST API and web UI. For production, prefer the Docker daemon over an xvfb-wrapped desktop build.

Remote deployment

The desktop deploy_remote tool ships the daemon image to a remote host over SSH and starts it there. This is isolation layer H — the same full-process isolation as the daemon image, running on a remote machine.

  1. Provide the target: hostname, username, and an authentication method.
  2. Aura connects over SSH, detects the remote OS, and installs Docker if it is missing — get.docker.com on Linux, Colima via Homebrew on macOS, or manual instructions on Windows.
  3. It copies docker-compose.remote.yml + an .env, runs docker compose up -d with --net=host, and surfaces a one-time pairing code.
  4. The remote daemon POSTs a heartbeat every 30s; the desktop app pairs and stores an auth token locally.

SSH authentication methods

MethodDescription
Key filePath to an SSH private key (RSA, Ed25519, …). Most secure and recommended.
PasswordUsername/password via sshpass for non-interactive auth.
Saved credentialReferences an entry in the encrypted credential store; decrypted at deploy time.
Remote agent containers are single-use by design. To "update" a deployed agent, delete it and deploy fresh — each deploy mints a new auth token, pairing code, and container name. Do not hand-roll ssh + docker run to redeploy; use the deploy flow so cleanup (deleting the container) happens correctly. Schedules, listeners, and webhooks can each be targeted at a remote agent, and deleting the automation item also tears down its remote deployment.

The eight isolation layers

There are eight distinct isolation layers with very different blast radius. Knowing which one you are in matters. Layers A–F are desktop toggles; B/C/D are mutually exclusive bash backends, while E and F are orthogonal and can stack on top. Only G and H are strong process-level boundaries. Everything above them isolates a subset of tools and leaves the rest running with host privileges.

LayerTriggerWhat IS isolatedWhat is NOT
A. Nativenative_mode=true (also the settings-load-failure fallback)nothingeverything
B. Desktop Docker bashnative_mode=false + Docker available + not sandboxedthe bash command, inside debian:bookworm-slimfile ops, MCP, the SQLite DB, web tools, email
C. SSH backendexecution_backend=sshbash, executed on a remote hostlocal file ops, MCP, DB
D. Singularity backendexecution_backend=singularitybash, inside a .sif containerlocal file ops, MCP, DB
E. Host sandboxexecution_isolation=sandboxfile reads/writes, via path rewrite into /tmp/aura-sandbox-{task_id}bash, network, kernel
F. MCP per-taskmcp_servers.isolation="per_task" (stdio only)stdio MCP child processes, with a per-task data dirHTTP MCP servers (stay shared), bash, other tools
G. Daemon container imagerunning aura-daemon in the daemon imageeverything — file, bash, MCP, DB all execute inside the containernothing from the agent's view; the container IS the process
H. Remote deploydeploy_remote ships Layer G to a remote hostsame as G, on the remote hostnothing

The bash routing switch

In the desktop app, the only thing deciding whether bash is containerized is a single check: if !native_mode && docker_available && !sandbox_active. When it does route to Docker, the container is minimal — docker run --rm -v {project}:/workspace -w /workspace, with no --network, no --user, and no blanket env forwarding.

Two sharp edges to internalize. (1) The native_mode load-failure fallback is insecure-open: if the settings row fails to load (corrupt DB, transient lock, migration hiccup), Aura defaults to host bash even if you had toggled Docker on — there is no fail-closed here. And if Docker mode is on but the Docker binary can't be resolved at runtime, bash silently falls back to native host execution. (2) Layer B is bash-only. In desktop Docker mode, read_file, write_file, edit_file, glob, grep, web_fetch, email_send, MCP, and the SQLite DB all run on the host with your privileges. The only thing Layer B stops is a destructive command inside a bash invocation — a misbehaving agent can still read ~/.ssh/id_rsa, write any file you can write, reach any HTTP endpoint, or read credentials straight out of the DB.

For untrusted workloads, run the daemon container (G) or a remote deploy (H) — never desktop native_mode. The sandbox (E) also has a known limit: it blocks .. traversal but does not normalize symlinks that already live inside the project tree, so a read that follows a symlink pointing outside the project resolves on the host.

Guardrails registry

Aura ships a composable guardrails registry that runs at three stages — Input (before the agent sees user content), Tool (before a tool call executes), and Output (before a reply or tool result is returned). Each rule returns Allow, Block, Redact, or a secret-capturing Vault action. The first Block short-circuits the rest; multiple Redacts chain; every firing rule is logged to a guardrail_violations audit row.

RuleStageDefault actionWhat it does
bash-destructive-commandsToolBlockSubstring match of the destructive bash blocklist.
secret-detectionInputVault → BlockDetects API keys/tokens/PEM/JWT in user input, rewrites to {{credential:slug}} placeholders.
secret-detection-outputOutputRedactScans tool results for secrets → [REDACTED-<kind>] (closes cat .env exfil).
pii-detectionInputRedactSSN and Luhn-valid card numbers redacted; emails/phones deliberately pass through.
pii-detection-outputOutputRedactSame PII patterns on model output text/thinking.
prompt-injection-signaturesInputBlockMatches known override phrasings ("ignore previous instructions", …).
output-max-tokensOutputRedactTruncates output over a ~32,000-token estimate, appending [TRUNCATED].
Bundled guardrails ship DISABLED by default — they are opt-in. Fresh installs land with every guardrail off; the secret / PII / bash-destructive / injection checks are toggled per-rule in Settings → Guardrails. The rationale is privacy-preserving defaults — Aura does not police your content unless you ask it to. The consequence for your threat model: do not assume PII redaction, secret redaction, or injection blocking is active on a given deployment unless it has been explicitly enabled. For production, enable at minimum secret-output, injection, and bash-destructive.

Always-on scan primitives

Independent of the toggleable registry, a few checks run unconditionally. These are recoverability fail-safes and defense-in-depth, not sandboxes — treat them as raising the bar, not sealing a path.

PrimitiveWhere it firesBehaviorLimitation
Hardcoded bash blocklistevery bash commandBlocks rm -rf /, rm -rf /*, mkfs, dd if=/dev/zero, fork bombs, > /dev/sda, chmod -R 777 /Does not catch rm -rf $HOME, curl … | sh, or a non-bash tool doing equivalent damage
redact_secretstool-result fallback when no guardrail context is availableMasks 8 secret patterns (OpenAI/Anthropic/AWS/GitHub/xAI/Bearer/PEM/JWT) with [REDACTED]Regex-based; unknown token formats slip through
is_ssrf_targetweb_fetchBlocks localhost, 0.0.0.0, ::1, cloud-metadata hosts, and any hostname resolving to loopback / RFC-1918 / link-localDNS resolved once; a TOCTOU rebind is not defended. bash (e.g. curl) bypasses it entirely
scan_for_injectioncontext files read via file_readWarns (does not block) on 6 override phrasingsAdvisory only; the blocking version is the opt-in injection guardrail

Anything written into durable memory or the wiki also passes through a write-time threat scan first, which rejects content carrying injection phrasings, credential-exfil patterns, or invisible-unicode steganography — so a poisoned page cannot be planted for later recall. It is signature-based and evadable by novel phrasing.

Web-server authentication & TLS

  • Bearer token — from AURA_WEB_TOKEN (preferred) or the web_server_token setting, compared in constant time. The token is the identity; there is no per-user scoping — anyone holding it is "the user".
  • Password auth (optional)POST /auth/login issues a JWT session cookie; the hash is read from the DB per request so changes take effect immediately.
  • Strict remote mode — when AURA_REMOTE_DEPLOYMENT=true or AURA_VIEWER_MODE=true, the daemon rejects unauthenticated /api/* even if no token or password is configured. This is the correct production default; without it, a network-reachable daemon with no credentials would serve everything to anyone who finds the port.
  • Chrome-extension bypass — requests whose Origin starts with chrome-extension:// skip auth (the browser-automation channel).

Public routes (auth bypass): /auth/*, /api/health, /api/heartbeat/*, /ws/*, /api/image-proxy, /charts/*, /acp/ping, /.well-known/*, and GET /acp/agents — the ACP discovery surface is intentionally unauthenticated so peers can introspect. ACP mutation and run-bound endpoints (/acp/runs, /acp/session/*) still require a token.

There is no built-in TLS. Plain HTTP behind a trusted token is acceptable on a known-trusted LAN; for any internet exposure, TLS at a reverse proxy is mandatory.

Encryption at rest

Sensitive values are encrypted with AES-256-GCM before they reach SQLite, stored as enc:v1:{nonce}:{ciphertext} with a random 96-bit nonce per value. Encrypted columns include the credential store, the credential pool, cloud connectors, and OAuth access/refresh tokens; listener auth_config encrypts fields whose key name looks sensitive.

Key-storage caveat. The 256-bit key lives in the macOS Keychain, read/written via the security CLI. On platforms without that command — Linux, Windows, and the daemon container — the keychain read/write fails, so Aura generates a fresh ephemeral in-memory key each start: values encrypted in one run are not decryptable in the next. On those hosts, treat the DB as protected only by the host filesystem and rely on OS-level disk encryption (FileVault / BitLocker / LUKS) plus strict permissions (chmod 600 the DB, lock down /data). Provider API keys in settings and custom_providers are stored plaintext by design — if the DB file leaks, those keys leak.

OAuth client_id values and the Composio API key are baked into the binary at build time. These are designed to be visible — they identify the Aura app to OAuth providers and function as public client identifiers, not per-user secrets.

Spend tracking & caps

The Billing tab tracks every LLM call so cost never surprises you. Four summary cards show today's cost, this month's cost, all-time total, and a per-provider monthly breakdown; an interactive 14-day bar chart plus per-model area charts show where spend is going; and a detailed table lists every model used with input/output tokens, request count, and total cost.

Spend limits & fallback

Set a maximum daily and monthly spend per provider. The limit is checked before every LLM call: when the current provider exceeds its cap — or hits rate limits (HTTP 429) or server errors — Aura walks the provider fallback order (drag-to-reorder) until it finds one that is in budget and reachable. This fallback applies only to Aura Routing and cost enforcement; a pinned model never silently switches. Pricing is pre-seeded with current market rates per model and editable, with a reset-to-defaults button.

During a task, the Context Panel shows real-time usage: context-window percentage, input / output / cache-read tokens, running cost, latency, and the active model and provider. Spend caps can also be published centrally by the Enterprise Portal, where they are enforced locally on every deployment.

Chrome extension

A companion Chrome extension provides a side-panel chat interface and lets agents drive the browser.

  • Side panel — a full chat surface with Markdown/code rendering, streaming responses, tool-usage display, and file attachments, connected over WebSocket at /ws/sidepanel. It is tab-aware: the agent can see the active tab's URL and page context.
  • Context menus — right-click selected text, a link, or a page element to send it to the agent with its page context.
  • Screenshot capture — grab the current tab and send it for visual analysis.
  • Browser automation — through /ws/browser, agents drive the browser with the browser_action tool: navigate, click, type, screenshot, extract page content or DOM elements, scroll, and wait.

Setup

  1. Load the extension from the extension/ directory in Chrome's developer mode (chrome://extensions → Developer mode → "Load unpacked").
  2. Point it at your Aura web server URL (e.g. http://localhost:18800).
  3. If the server has a token configured, enter it in the extension settings.
  4. The extension icon turns green when connected.

Note that extension requests bypass web-server auth by Origin (above), so treat a machine with the extension installed as trusted.

Embeddable chat widget

Embed an Aura-powered chat interface on any website with a small script snippet. Configure it by creating a chatbot-type listener in the Listeners tab, then drop in:

<script src="http://your-server:18800/widget.js"></script>
<script>
  AuraWidget.init({
    serverUrl: "http://your-server:18800",
    token: "your-auth-token",      // optional
    position: "bottom-right",       // bottom-right or bottom-left
    title: "AI Assistant",
    greeting: "How can I help?",
    theme: "dark"                   // dark or light
  });
</script>

Messages stream in real time over WebSocket; responses render Markdown including code blocks; conversation history persists per user across page loads; and color scheme, position, title, and greeting are all configurable. When you expose the widget to the public internet, front it with TLS and scope what the underlying listener can do.

Network ports

PortProtoServiceExposure guidance
18800TCPWeb UI + REST + SSE + WS + /acp/* REST + Gateway /v1/*Behind a reverse proxy + auth
18790TCPInbound webhooks (separate from the main API server)Behind a reverse proxy + per-webhook secret
18802UDPACP peer discovery broadcastLAN only (needs --net=host in Docker)
Recommended for production. Run the daemon image (or a remote deploy); front it with a TLS reverse proxy; set AURA_REMOTE_DEPLOYMENT=true so anonymous access is impossible; enable disk encryption and chmod 600 the DB; explicitly enable the guardrails you need (they ship off); set spend limits on every paid provider; restrict egress to just the providers you use; and gate consequential actions behind human-in-the-loop workflow steps. Report security issues to [email protected].