> ## Documentation Index
> Fetch the complete documentation index at: https://comis-feature-skill-archive-import.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Hardening

> A step-by-step checklist to maximize the security of your Comis installation

Comis is secure by default -- audit logging, input scanning, output guarding, and many other protections are active from the moment you start the daemon. But you can make your installation even stronger by following this hardening checklist. Each item below addresses a specific security concern, ordered by priority so you tackle the most important changes first.

## Quick Check: Run the Security Audit

Before working through the checklist manually, run the built-in security audit to see where your installation stands:

```bash theme={}
comis doctor
```

This command checks 5 security categories and reports findings with severity levels (critical, warning, info) and specific remediation advice. Each finding tells you exactly what to fix and how. Run it again after making changes to verify your progress.

<Tip>
  The security audit can automatically fix some findings. Follow the suggested remediation commands in the output.
</Tip>

## Hardening Checklist

### Critical (Do First)

These items address the most significant security risks. Complete them before exposing your installation to any network.

<Steps>
  <Step title="Set file permissions">
    Your config file contains sensitive settings and your data directory holds databases and secrets. Restrict access so only your user account can read them:

    ```bash theme={}
    chmod 600 ~/.comis/config.yaml      # Only owner can read/write
    chmod 700 ~/.comis/                  # Only owner can access directory
    ```

    **Why:** Prevents other users on the system from reading your configuration or data files. On a shared server, this is essential.

    **Verify:** `ls -la ~/.comis/config.yaml` shows `-rw-------`.
  </Step>

  <Step title="Remove plaintext secrets from config">
    If your config.yaml contains raw API keys, migrate them to environment variables or the encrypted store:

    ```yaml theme={}
    # Bad: plaintext secret in config
    providers:
      openai:
        apiKey: sk-abc123...

    # Good: reference an environment variable
    providers:
      openai:
        apiKey: ${OPENAI_API_KEY}
    ```

    Or better yet: set up the [encrypted secrets store](/security/secrets) for production-grade secret management.

    **Why:** Config files may be backed up, shared, or accidentally committed to version control. Plaintext secrets in config are the most common security mistake.

    **Verify:** `comis doctor` reports no "plaintext secret" findings.
  </Step>

  <Step title="Configure gateway authentication">
    If you expose the HTTP gateway (for the web dashboard, JSON-RPC, or WebSocket), always require authentication tokens. Each token entry needs a unique `id`, a `secret` of at least 32 characters, and the `scopes` it is allowed to use:

    ```yaml title="~/.comis/config.yaml" theme={}
    gateway:
      tokens:
        - id: "dashboard"
          secret: "your-strong-random-token-here"
          scopes:
            - rpc
            - ws
            - admin
    ```

    Generate a strong token:

    ```bash theme={}
    openssl rand -hex 32
    ```

    **Why:** Without authentication, anyone who can reach the gateway can control your agents, read your data, and modify your configuration.

    **Verify:** `curl http://localhost:4766/health` returns 200, but `curl http://localhost:4766/api/agents` returns 401.
  </Step>

  <Step title="Bind to localhost or enable TLS">
    If the gateway is only for local use (web dashboard on the same machine), bind to localhost:

    ```yaml title="~/.comis/config.yaml" theme={}
    gateway:
      host: "127.0.0.1"
      port: 4766
    ```

    If you need remote access, enable TLS to encrypt traffic:

    ```yaml title="~/.comis/config.yaml" theme={}
    gateway:
      tls:
        certPath: "/path/to/cert.pem"
        keyPath: "/path/to/key.pem"
        caPath: "/path/to/ca.pem"
    ```

    **Why:** Without TLS, traffic (including auth tokens) is sent in plain text over the network. Anyone on the same network can intercept it.

    **Verify:** `comis doctor` reports no "gateway exposure" findings.
  </Step>
</Steps>

### Important (Do Next)

These items strengthen your security posture significantly. They are important for any production deployment.

<Steps>
  <Step title="Enable the encrypted secrets store">
    Migrate from .env files to the encrypted store for production:

    ```yaml title="~/.comis/config.yaml" theme={}
    security:
      storage: encrypted
    ```

    The encrypted store uses AES-256-GCM encryption at rest, protecting your secrets even if someone gains access to the data directory. This is the default backend.

    See [Secrets](/security/secrets) for the step-by-step setup procedure, including master key generation and importing existing secrets.

    **Why:** .env files store secrets in plain text. The encrypted store ensures secrets cannot be read without the master key.
  </Step>

  <Step title="Configure per-agent secret access">
    Restrict each agent to only the secrets it needs using glob patterns:

    ```yaml title="~/.comis/config.yaml" theme={}
    agents:
      customer-support:
        secrets:
          allow:
            - "openai_*"
      code-assistant:
        secrets:
          allow:
            - "ANTHROPIC_API_KEY"
            - "GITHUB_TOKEN"
    ```

    See [Secrets: Per-Agent Access](/security/secrets) for details.

    **Why:** Follows the principle of least privilege. If an agent is compromised, it can only access the secrets it was explicitly granted -- not every secret in the store.
  </Step>

  <Step title="Enable the approval workflow">
    Require human approval for destructive agent actions:

    ```yaml title="~/.comis/config.yaml" theme={}
    approvals:
      enabled: true
      defaultMode: auto
    ```

    This pauses execution when an agent attempts a destructive action (like deleting a file) and waits for your approval before proceeding. If you do not respond within the timeout, the action is automatically denied.

    See [Approvals](/security/approvals) for the full configuration including custom rules and timeout settings.

    **Why:** Prevents agents from taking irreversible actions without your knowledge. This is especially important for agents with broad tool access.
  </Step>

  <Step title="Review tool policy profiles">
    Switch agents from the default `full` profile to a more restrictive one:

    ```yaml title="~/.comis/config.yaml" theme={}
    agents:
      customer-support:
        skills:
          toolPolicy:
            profile: messaging    # Only communication tools
      code-assistant:
        skills:
          toolPolicy:
            profile: coding       # Only development tools
    ```

    See [Tool Policy](/skills/tool-policy) for all profiles and custom rules.

    **Why:** Limits the blast radius if an agent is compromised. A messaging agent does not need file system access, and a coding agent does not need fleet management tools.
  </Step>

  <Step title="Verify audit logging is enabled">
    Confirm that audit logging is active (it should be by default):

    ```yaml title="~/.comis/config.yaml" theme={}
    security:
      auditLog: true
    ```

    **Why:** Audit logs are essential for detecting and investigating security incidents. Without them, you have no visibility into what your agents have done.

    **Verify:** Check the [Security dashboard](/web-dashboard/security-view) Audit Log tab for recent events.
  </Step>
</Steps>

### Advanced

These items provide additional hardening for environments with strict security requirements.

<Steps>
  <Step title="Verify exec sandbox is active">
    The exec sandbox wraps shell commands in an OS-level namespace so agents
    cannot access files outside their workspace. On Linux, this requires
    bubblewrap:

    ```bash theme={}
    # Check if bubblewrap is installed
    which bwrap

    # Install if missing (Debian/Ubuntu)
    sudo apt install bubblewrap
    ```

    On macOS, `sandbox-exec` is built in -- no installation needed.

    Verify your config does not disable the sandbox:

    ```yaml title="~/.comis/config.yaml" theme={}
    agents:
      my-agent:
        skills:
          execSandbox:
            enabled: "always"   # default -- do not set to "never"
    ```

    **Why:** Without the exec sandbox, agents can bypass file tool path
    restrictions by using `exec cat` instead of the `read` tool. The
    sandbox prevents this at the kernel level.

    **Verify:** Check logs for `"Exec sandbox provider: bwrap"` (Linux) or
    `"Exec sandbox provider: sandbox-exec"` (macOS) at daemon startup.
  </Step>

  <Step title="Configure Node.js permission model">
    For maximum process isolation, enable the Node.js permission model to restrict what the Comis process itself can access:

    ```yaml title="~/.comis/config.yaml" theme={}
    security:
      permission:
        enableNodePermissions: true
        allowedFsPaths:
          - "~/.comis/"
        allowedNetHosts:
          - "api.openai.com"
          - "api.anthropic.com"
    ```

    **Why:** Restricts the Node.js process itself to only the file paths and network hosts you specify. Even if an attacker achieves code execution, they cannot access files or hosts outside the allowed list.

    <Warning>
      This is a restrictive setting. Test thoroughly before enabling in production -- some features may need additional paths or hosts. The default is `security.permission.enableNodePermissions: false`.
    </Warning>

    <Warning>
      Enabling `enableNodePermissions: true` also disables the fd-based fs API family (`fsync`, `fchmod`, `fchown`) at the Node.js level. Under `--permission`, encrypted-mode credential file writes are **best-effort durability** (fsync is skipped) and trace/session file permissions (fchmod) are best-effort. Comis guards all call sites via `isFsyncDisabledByPermissionModel`, so the daemon will not crash, but power loss between write and rename could leave a corrupt credential file. This behavior is Linux-only (production systemd deploys). See [Node Permissions -- fd-API disablement](/reference/node-permissions#production-fd-api-disablement-linux) for full detail.
    </Warning>
  </Step>

  <Step title="Set up CORS restrictions">
    If the web dashboard is accessed from a specific domain, restrict CORS to prevent unauthorized web pages from making API requests:

    ```yaml title="~/.comis/config.yaml" theme={}
    gateway:
      corsOrigins:
        - "https://your-domain.com"
    ```

    **Why:** Without CORS restrictions, any web page open in a user's browser could potentially make API requests to your gateway if the user is on the same network.
  </Step>

  <Step title="Review plugin hook registrations">
    If you use custom plugins, review which hooks they register. Each hook point has security implications -- a plugin registered on the `beforeToolCall` hook can intercept and modify tool invocations, while a plugin on `beforeResponse` can alter agent output.

    **Why:** Malicious or poorly written plugins can intercept and modify messages, tool calls, or agent behavior. Only install plugins from trusted sources.

    **Verify:** Review your plugin configuration and the hooks each plugin uses.
  </Step>

  <Step title="Configure browser sandboxing">
    If agents use browser tools, ensure sandboxing is properly configured. Browser tools provide powerful web access capabilities, which also means they are a potential vector for data exfiltration or accessing internal services.

    **Why:** Browser tools can access the web, which creates opportunities for both SSRF attacks and data exfiltration. Proper sandboxing ensures browser actions are constrained.

    **Verify:** Check your `comis doctor` output for specific browser security recommendations.

    **What the installer does for you:** The browser tool ships enabled, so a default install provisions the browser runtime (unless you pass `--without-browser`). Whenever the browser runtime is provisioned — the default, or an explicit `--with-browser` / `--with-cloakbrowser` — the systemd unit is written with two layers of filesystem isolation around the browser binary:

    | Layer                                           | What it does                                                                                                   | Browser-tool specifics                                                                                                                                                                                                                                      |
    | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Kernel sandbox (`ReadWritePaths=`)              | Bind-mounts the rest of the host filesystem read-only. The browser process can write only to the listed paths. | `~/.config/comis/browser` (profile dir) plus a small set of binary-specific paths — `~/.config/google-chrome` + `~/.local/share/applications` for stock Chrome, or `~/.cloakbrowser` + `~/.config/chromium` for CloakBrowser (tighter — no mimeapps write). |
    | Node `--permission` model (`--allow-fs-write=`) | Independently constrains what the Node daemon process can write, even before kernel checks run.                | Same paths as `ReadWritePaths`, kept in lockstep so the two layers don't drift.                                                                                                                                                                             |

    Both are emitted by `render_systemd_unit` in `install.sh` and rendered into `/etc/systemd/system/comis.service`. The unit is checksummed and refuses to overwrite hand edits, so once you've audited the paths they stay stable across upgrades.

    For the `--with-xvfb` variant, the companion `comis-xvfb.service` unit runs as the same user and exposes Xvfb on a Unix socket only (`-nolisten tcp`) — no network surface added. Both units bind a shared host dir (`/run/comis-x11`) onto their `PrivateTmp` `/tmp/.X11-unix` — Xvfb read-write (it creates the `X99` socket), the daemon read-only (it connects) — so the socket is reachable without opening either `/tmp` more broadly.

    **What it does *not* do:** the daemon does not block what URLs the browser navigates to (beyond the always-on SSRF guard against private IPs, loopback, and cloud metadata endpoints). If you need URL allowlisting, layer that at the network egress level (egress-logging chain + outbound firewall rules).
  </Step>

  <Step title="Review SSRF attack surface">
    Review which agents have access to web-fetching tools. SSRF protection is always active -- Comis blocks requests to private IPs, loopback addresses, and cloud metadata endpoints -- but reducing the number of agents with web access reduces your overall attack surface.

    ```yaml title="~/.comis/config.yaml" theme={}
    agents:
      customer-support:
        skills:
          toolPolicy:
            deny:
              - "web_fetch"
              - "browser_*"
    ```

    **Why:** Fewer agents with web access means fewer potential SSRF vectors. Even with built-in SSRF protection, defense in depth means reducing unnecessary exposure.
  </Step>

  <Step title="Rotate the SECRETS_MASTER_KEY periodically">
    Treat the master key like any other production credential -- rotate it on
    a schedule (every 90 days is a reasonable cadence) and after any
    suspected exposure. The recommended flow:

    ```bash theme={}
    # 1. Generate the new key
    openssl rand -hex 32 > /tmp/new-master-key.hex

    # 2. Re-encrypt every secret under the new key
    SECRETS_MASTER_KEY="$(cat /tmp/new-master-key.hex)" \
      node packages/cli/dist/cli.js secrets rotate \
        --old-key="$OLD_SECRETS_MASTER_KEY"

    # 3. Atomically swap the env var in your secret store, restart the daemon
    pm2 restart comis
    ```

    **Why:** A compromised master key gives an attacker every secret in the
    encrypted store. Periodic rotation limits the blast radius and ensures
    your operational runbook actually works before you need it.
  </Step>

  <Step title="Lock down the email allowlist">
    If you enable the email channel, set `allowMode: "allowlist"` and list
    every legitimate sender. The default closed posture rejects any sender
    not on the list:

    ```yaml title="~/.comis/config.yaml" theme={}
    channels:
      email:
        allowMode: "allowlist"   # never use "open" in production
        allowFrom:
          - "alice@example.com"
          - "bob@example.com"
          - "ops@example.com"
    ```

    Comis additionally auto-detects bulk and automated mail (Auto-Submitted
    headers, Precedence, List-Unsubscribe, noreply addresses) and rejects
    those even if the sender is listed.

    **Why:** Email is the easiest prompt-injection vector. An open allowlist
    on a public address is equivalent to letting the internet drive your
    agent.
  </Step>

  <Step title="Configure network egress allowlist (allowedNetHosts)">
    If you enable the Node.js permission model, also restrict outbound
    network to only the providers your agents actually need:

    ```yaml title="~/.comis/config.yaml" theme={}
    security:
      permission:
        enableNodePermissions: true
        allowedNetHosts:
          - "api.openai.com"
          - "api.anthropic.com"
          - "api.deepgram.com"
    ```

    Remember to launch the daemon with the matching `--allow-net=...` flag
    \-- the config field alone is a declaration, not enforcement. See
    [Node Permissions](/reference/node-permissions).

    **Why:** Even with SSRF protection on every web tool, an
    egress allowlist provides a second layer that limits exfiltration to
    known good hosts.
  </Step>
</Steps>

## Verification

After completing the checklist, run the security audit again to confirm your changes:

```bash theme={}
comis doctor
```

A fully hardened installation should show no critical or warning findings. Info-level findings are informational and typically do not require action.

Review the output carefully. Each finding includes:

* **Severity** -- Critical, warning, or info
* **Description** -- What was found
* **Remediation** -- Specific steps to fix the finding
* **Category** -- Which security area the finding relates to

<Info>
  Security hardening is not a one-time task. Re-run `comis doctor` periodically, especially after configuration changes or upgrades. New versions may introduce additional security checks.
</Info>

## Output Guard for Agent File Writes

Comis can scan file writes for secret-shaped values and warn or block before a live API key is committed to disk in cleartext. Configure `security.writeSecretGuard` (default: `warn`) -- see [Secrets -- Output Guard for Secret Egress](/security/secrets#output-guard-for-secret-egress) for configuration options and tradeoffs.

## Related

<CardGroup cols={2}>
  <Card title="Defense in Depth" icon="shield-halved" href="/security/defense-in-depth">
    All 22 categorical layers (24 primitives) explained
  </Card>

  <Card title="Secrets" icon="key" href="/security/secrets">
    Set up the encrypted secrets store
  </Card>

  <Card title="Approvals" icon="clipboard-check" href="/security/approvals">
    Configure human-in-the-loop approval
  </Card>

  <Card title="Security Model Reference" icon="file-code" href="/reference/security-model">
    Technical security reference
  </Card>
</CardGroup>
