Skip to content

Security

Argus runs LLM agents that browse the web, execute code and talk to other agents, so it assumes that model output and anything an agent reads are untrusted. This page summarises the controls in place. To report a vulnerability, follow the security policy; do not open a public issue.

Containment

flowchart TB
    subgraph host [Docker host]
        web["web :8080<br/>nginx, strict CSP"]
        api["api :8000 (127.0.0.1)"]
        subgraph internal_b [backend network, no egress]
            pg[(postgres)]
            rd[(redis)]
        end
        subgraph internal_s [sandbox network, no egress]
            sb[sandbox]
        end
    end
    web --> api
    api --> pg
    api --> rd
    api -->|token| sb
    api -->|HTTPS, SSRF-checked| internet((Internet))
    api -.->|"opt-in, token"| br["browser"]
    br -.->|"only via egress proxy"| internet
    sb -.-x|no route| internet
  • Containers: non-root users, read-only root filesystems (api, web, sandbox), every capability dropped, no-new-privileges, CPU and memory limits, and a pid limit on the sandbox.
  • Networks: Postgres and Redis are on an internal network with no egress; the sandbox is on its own internal network, reachable only by the API with a shared token.
  • Sandbox: shell commands run with a tmpfs work area and per-command rlimits; the workspace is copied in and changed files are versioned back. Destructive shell commands are blocked by a deterministic guardrail before they reach it.
  • Web access: web_fetch allows only http and https, enforces the run's domain allow list, rejects hosts that resolve to private, loopback, link-local or metadata addresses on every redirect, caps size and content types, and treats page text as data. Webhook targets on private or loopback addresses are refused too.
  • Browser: opt-in; see The live browser.
  • Protocols: stdio MCP servers spawn local processes, so they are off unless ARGUS_MCP_ALLOW_STDIO=true and then admin-only. New MCP servers default to the ask trust level and remote-agent delegations to requires_approval: true.

The live browser

With the browser compose profile on, agents can read pages in a real Chrome (the browse tool) and you can watch it. A real browser runs page scripts, so it is contained more tightly than web_fetch:

  • Read-only. The agent can open, snapshot, click, scroll, get_text, back, forward and wait. Typing, filling, selecting, key presses, uploads, downloads and script evaluation are never offered, are refused by the browser service, and are denied again in agent-browser's own action policy (default deny by command name). Downloads are denied. Every non-GET page request fails, so no form can be submitted, including through a service worker. Requests a page's own script sends with fetch or XHR (a POST, say) are not blocked; this is an accepted residual risk.
  • No way to the inside. The browser container is on an internal network with only the egress proxy. The proxy resolves every host itself, refuses any private, loopback, link-local, metadata or reserved address, pins the connection to the address it checked, and allows only ports 80 and 443. The browser never reaches your intranet, even if you let web_fetch do so with ARGUS_WEB_FETCH_ALLOW_PRIVATE_URLS; egress has no switch for it.
  • Allowed domains apply to navigations. The API checks the address before open and the page after every action, and goes back if a click landed somewhere not allowed. Images, scripts and other subresources are held only to the private-address rule, or narrowed runs would load broken pages. file:, chrome:, javascript: and data: URLs are refused.
  • Page text is data. It reaches the model wrapped in content boundaries with a random marker the page cannot know, but a page can still try to steer an agent; the usual approvals and guardrails apply to what the agent does next.
  • Watching is read-only. Live frames are relayed only to the run's owner, only while they watch, and never stored. No input goes back to the page.
  • Residual risks. Chrome runs with --no-sandbox in the container, as the same user as the browser service, so a Chrome renderer exploit could read the service token and reach other runs' sessions (it still cannot leave through egress). Chrome is pinned and needs regular refreshes. On Docker engines before 26 the embedded DNS resolver can forward queries from internal networks. See TM-13.

Identity and access

  • Passwords are hashed with Argon2id. Access tokens are short-lived JWTs; refresh tokens are single-use and rotated.
  • API keys carry write scopes per area (see Integration). Keys cannot create or revoke keys or change passwords.
  • Roles: admin, member and viewer. Viewers are read-only everywhere, including the protocol endpoints, whatever their key's scopes.
  • Requests are rate-limited per user or API key (ARGUS_RATE_LIMIT_PER_MINUTE).

Secrets and data

  • Provider API keys are encrypted at rest (ARGUS_ENCRYPTION_KEY, or a key derived from ARGUS_SECRET_KEY) and never returned; responses show a masked key_hint.
  • Production refuses to start without a strong ARGUS_SECRET_KEY (at least 32 characters).
  • Secret-like values in agent output and tool output are redacted by the guardrails.
  • Webhook secrets are shown once. Account exports never include credentials, hashes or secrets.
  • Every state-changing request is audited, including failed sign-ins; the protocol ledger is hash-chained and verifiable (Protocols and Ledger).

Web UI and exports

  • The web container serves the UI with a strict Content-Security-Policy (default-src 'self', no inline scripts, frame-ancestors 'none'), nosniff, no-referrer and a restrictive Permissions-Policy. The API adds HSTS in production.
  • Exports escape raw HTML from model output and drop unsafe link schemes.
  • Skill uploads are validated: frontmatter rules, text files only, no hidden files or path traversal, and size limits checked before a zip is extracted.

Hardening checklist

  • [ ] Put a TLS-terminating proxy in front of port 8080 and set ARGUS_CORS_ORIGINS.
  • [ ] Set ARGUS_ALLOW_REGISTRATION=false once accounts exist.
  • [ ] Set a separate ARGUS_ENCRYPTION_KEY and keep deploy/.env (mode 600) out of backups that leave the host unencrypted.
  • [ ] Set ARGUS_METRICS_TOKEN if you scrape metrics.
  • [ ] Keep approval_required on for shell, remote_agent and network_write unless the run uses the isolated sandbox.
  • [ ] Give API keys the narrowest scopes they need, and use viewer accounts for read-only users.
  • [ ] Copy backups/ off the host on a schedule.
  • [ ] If you turn the live browser on, run Docker 26 or newer and refresh the pinned Chrome regularly.