Skip to content

Security ​

This page describes caic's trust boundaries: authentication, credential storage, container isolation, and network exposure.

Authentication ​

Dashboard sessions ​

When OAuth login is configured, caic issues signed sessions for the web UI, Android app, and API clients. Browser sessions use an HttpOnly, SameSite=Lax cookie that is marked Secure over HTTPS. Android and other API clients use an Authorization: Bearer header.

Dashboard sessions expire and require a new login. Forge access tokens are refreshed when the provider supplies a refresh token. MCP clients use a separate OAuth flow described in MCP Server.

caic supports login with GitHub, GitLab, and Google. Google authenticates dashboard access only and does not grant repository or forge authority. Set each provider's allowed_users list for a private deployment. Leaving it unset permits any account that completes that provider's login flow.

No-auth mode ​

Without an OAuth login provider, ordinary enabled dashboard and API routes require no login. MCP is still disabled when the server listens on a non-loopback address, and webhook endpoints still require their configured secret verification. Use no-auth mode only on localhost or a network where every peer is trusted, such as a private tailnet.

Network security ​

Listener and origin allowlist ​

By default, port-only addresses such as :2242 bind to localhost. You must explicitly use 0.0.0.0:2242 to listen on all IPv4 interfaces.

Every request is checked against allow_origins. The complete accepted value types are:

ValueMatches
localUnspecified, IPv4 link-local, RFC 1918, IPv6 ULA and link-local, and IPv4 or IPv6 loopback addresses
tailscaleTailscale CGNAT range (100.64.0.0/10)
githubGitHub webhook addresses fetched from GitHub
anthropicPublished Anthropic outbound addresses for Claude MCP clients
openaiPublished ChatGPT integration and Codex cloud addresses
ISO country codeRequests geolocated to that country; requires a configured GeoLite2 database
CIDR rangeRequests whose client address belongs to the range

The default is ['local', 'tailscale', 'github']. Unmatched requests receive HTTP 403.

If a reverse proxy terminates HTTPS, add only its direct peer addresses to trusted_proxies. caic accepts forwarded protocol, host, and client-IP headers only when the direct peer matches this list. Configure the edge proxy to strip client-supplied Forwarded, X-Forwarded-*, and X-Real-IP headers and replace them with values it observed. Adding client or public network ranges would let those clients influence origin checks and the address checked by allow_origins.

External URL and HTTPS ​

The default external_url = "auto" supports browser login and same-origin session MCP after locking to the first fully qualified hostname. Remote MCP OAuth requires an explicit stable URL. When OAuth login is configured, explicit URLs must use HTTPS except for loopback development origins. Use HTTPS for any deployment exposed beyond a trusted local network.

Use Tailscale Serve or a TLS reverse proxy such as Caddy. See Configuration for setup examples.

Webhooks ​

GitHub and GitLab webhooks require a configured secret and are verified before their payloads are processed. Without a webhook secret, the endpoint is unavailable. GitHub webhook requests are also checked against GitHub's published address ranges.

Credential handling ​

Server-side storage ​

CredentialStorageNotes
Forge personal access tokensconfig.toml in plaintextRestrict file permissions
OAuth client secretsconfig.toml in plaintextRestrict file permissions
GitHub App private keyPEM file in the config directoryThe config stores its path
Gemini API keyconfig.toml or server environmentUsed for title generation and embedded voice
Tailscale API keyconfig.toml or server environmentUsed for ephemeral task nodes
Provider API keys[core.env], a harness environment table, or server environmentUsed for supported usage cards; [core.env] and server values can also support titles and commit descriptions
Harness environment credentials[harness.<name>.env] in config.tomlWritten into matching task containers and used for that harness's model refresh
Webhook secretsconfig.toml in plaintextUsed to verify webhooks
Dashboard session secret and MCP issuer signing keysettings.json in plaintextGenerated on first launch
User profiles and forge OAuth tokensusers.jsonUsed for login and per-user forge access
MCP clients, grants, hashed refresh-token family state, and private access-token signing keysoauth_refresh_tokens.jsonPersists MCP authorization and revocation across restarts

These files live under ~/.config/caic/. Restrict that directory to your user, for example with chmod 700 ~/.config/caic.

Container access ​

By default, a task does not receive host SSH keys, forge tokens, the caic session secret, OAuth client secrets, webhook secrets, or the container-runtime socket. Mounted harness state can contain its own provider credentials, so treat those mounts as credential-bearing.

md automatically bind-mounts shared agent state such as ~/.agents and the selected harness's configuration directories into every applicable task. These mounts are generally writable, while md's own configuration mount is read-only. Tasks using the same harness can therefore read and change the same host-side agent state.

Custom host mounts and shared build caches expose additional selected host paths. Review each mapping in Settings, prefer read-only mounts when possible, and do not mount a directory containing unrelated secrets.

GitHub token injection ​

If GitHub token injection is enabled for a task, caic resolves the token in this order:

  1. The signed-in GitHub user's OAuth access token
  2. The server-level personal access token
  3. The result of gh auth token when neither of the first two is configured

The token is not baked into the container image. At startup, md combines any .env files from the selected repositories, explicit values from the selected [harness.<name>.env] table, and the optional injected GitHub token. It writes the result to /home/user/.env inside the task container over the encrypted SSH connection. These values persist in the task filesystem until you remove or purge the container. A newly started task receives the currently resolved token and configuration.

Sudo access ​

When sudo is enabled, md generates a unique random password for the task container. It is not baked into the image and does not grant root access on the host. The credential is specific to that container and becomes useless when the container is deleted and recreated.

Sudo is unavailable with rootless Podman. Choose Docker for a task that needs sudo.

Container isolation ​

Each task has its own container and receives copies of its selected repositories. Agents modify those copies rather than the host checkouts. The container filesystem and repository copies are separate, but the automatic agent and harness mounts described above are shared host paths.

Isolation changes when you enable custom mounts, shared caches, mounted harness configuration, network access, devices, display access, or sudo. These options are intentional trust decisions. A writable host mount lets the agent change that host path, and shared mounts let tasks affect one another through shared data.

md does not use privileged containers or share the host network, PID namespace, IPC namespace, or container-runtime socket. It grants the capabilities required by its networking model. See md Security for the current container boundary and options for restricting outbound access.

Secret scanning on push ​

Before pushing, caic scans changes for common credentials such as cloud access keys, GitHub tokens, private keys, and hardcoded config secrets. Findings are reported to the agent so they can be removed.

This scan is a safety net, not a security boundary. An agent still has outbound network access unless you restrict it.

MCP security ​

On a non-loopback listener, caic disables MCP when OAuth login is not configured. Remote clients can use Authorization Code with PKCE or the Device Authorization Grant, both with rotating refresh tokens. Clients can register dynamically or use an HTTPS Client ID Metadata Document with private_key_jwt. DPoP-capable clients can use sender-constrained access tokens.

Consent grants only selected scopes:

ScopeGrants
caic:mcp.readBasic MCP tools, usage, and non-task resources
caic:tasks.readRead task information
caic:tasks.createCreate coding tasks
caic:tasks.writeSend and answer messages, fork tasks, and stop or revive existing tasks
caic:tasks.adminPurge tasks
caic:repos.writeClone repositories, push task branches or squash-push directly to default branches, repair pull request CI, and create default-branch CI repair tasks
caic:server.adminRead whole-machine CPU, memory, network, and filesystem statistics, and server health

Tools and resources outside the approved scopes are not exposed to the client. Authorization Code consent lets you approve a subset of requested scopes. In the device flow, the client starts at /oauth/device_authorization, you approve its code at /oauth/device, and it polls /oauth/token; approval grants every requested scope. Review those scopes before approving. Revoke a grant from Settings at any time. Add anthropic or openai to allow_origins only when the corresponding hosted client must reach caic. See MCP Server for connection steps.

Voice security ​

Voice can run through embedded Gemini Live, a standalone gateway, or a local model stack. WebRTC audio is processed in memory rather than saved by caic. Short-lived service-issued credentials authorize each voice session. A standalone gateway receives the public verification material but not caic's private signing key.

What caic does not protect ​

caic does not encrypt configuration, logs, or caches at rest. It does not prevent an agent from reaching the internet, audit data exfiltration, or limit what an authorized agent can commit or push.

For stronger outbound controls, configure container-runtime network restrictions as described in md Security.

Your agents. Your machine.