# Enterprise Security Matrix & InfoSec Controls

This document provides a comprehensive security controls matrix for Enterprise and Pilot Information Security (InfoSec) teams deploying GoClaw in sensitive corporate, financial, government, or telecom environments.

---

## 1. Network Perimeter & Egress Firewall Matrix

GoClaw is designed to operate securely within strictly air-gapped or egress-restricted perimeter networks (e.g. DMZ or isolated worker VLANs).

### Inbound Ports (Ingress)
| Port | Protocol | Interface / Bind | Purpose | Auth Required |
|---|---|---|---|---|
| **8080** (default, `server.port`) | HTTP | `gateway.bind`: `127.0.0.1` by default (`loopback`), `lan` for all interfaces, or a specific IP | Web UI, REST API, WebSocket Gateway | **Yes** (`X-Goclaw-Token` via `AuthMiddleware`) |
| `gateway.ingress.port` (optional, unset by default; e.g. 8081) | HTTP | `gateway.ingress.bind`: `127.0.0.1` by default, or `lan` / a specific IP | Dedicated webhook listener: Teams, Facebook and Telegram channel webhooks, and `/goclaw/hook/...` routes (GitLab, GitHub, Graylog, …). Unset, these share the main port | **Yes** (per channel: signature, Bot Framework token or secret token; per route: HMAC secret or token) |
| **8181** (optional, `tools.html.local_server`) | HTTP | `lan` by default | Serves only `/goclaw/view/` (agent-generated HTML pages) to the local network | **Yes** (per-page view token) |

GoClaw serves plain HTTP on every port; it does not terminate TLS. Put a
reverse proxy or load balancer in front of it for HTTPS.

> [!IMPORTANT]
> All other inbound ports are **CLOSED**. GoClaw exposes no SSH, Telnet, SMB, or unauthenticated RPC endpoints.

### Outbound Ports (Egress Allowlist)
When deploying behind an enterprise egress firewall or proxy, configure the following domain allowlist. **All other outbound traffic can be blocked with a default `DENY ALL` rule.**

| Destination Domain | Port | Protocol | Purpose | Fallback / Alternative |
|---|---|---|---|---|
| `generativelanguage.googleapis.com` | 443 | HTTPS/TLS 1.3 | Google Gemini / GenAI inference | Local Ollama / Private vLLM |
| `api.anthropic.com` | 443 | HTTPS/TLS 1.3 | Anthropic Claude inference | Local Ollama / Private vLLM |
| `api.openai.com` | 443 | HTTPS/TLS 1.3 | OpenAI inference | Local Ollama / Private vLLM |
| `api.deepseek.com` | 443 | HTTPS/TLS 1.3 | DeepSeek inference | Local Ollama / Private vLLM |
| `discord.com`, `gateway.discord.gg`, `cdn.discordapp.com` (optional) | 443 | WSS / HTTPS | Discord channel integration (API, gateway, attachments) | Disable if not used |
| `login.microsoftonline.com`, `*.botframework.com`, `smba.trafficmanager.net`, `graph.microsoft.com` (optional) | 443 | HTTPS | MS Teams: bot token, Bot Framework, replies, Graph | Disable if not used |
| `endpoints.office.com` (optional) | 443 | HTTPS | Microsoft 365 IP list for `gateway.ingress.allow_preset: microsoft-teams`, fetched at startup (the gateway does not start without it) | Use explicit `allow_ips` instead |
| `graph.facebook.com`, `*.fbcdn.net`, `*.fbsbx.com` (optional) | 443 | HTTPS | Facebook Messenger channel (messages; attachment downloads) | Disable if not used |
| `api.telegram.org` (optional) | 443 | HTTPS | Telegram channel | Disable if not used |
| `connect.ngrok-agent.com` (optional) | 443 | TLS | ngrok tunnel when `gateway.ingress.type: ngrok` | Reverse proxy (`type: proxy`) |
| *All other destinations* | * | * | **DENY ALL** | — |

Tools that call other systems (GitLab, Grafana, Uptrace, MCP servers, databases,
`http_request`, web search) need their own destinations opened as well.

---

## 2. Operating System Hardening & Least Privilege

### Rootless Container (Docker)
The official GoClaw Docker container operates with least privilege:
- **Dedicated Non-Root User**: Runs as UID `10001` (`goclaw`).
- **No SUID Binaries**: Root escalation is disabled.
- **Read-Only Root Filesystem**: Compatible with `read_only: true` container flags when the state directory `/home/goclaw/.goclaw` (or `GOCLAW_STATE_DIR`) is a mounted volume — it holds the database (`gc_state.sqlite`), the secrets vault, the workspace and the logs — and `/tmp` is writable (for example a tmpfs), since tools write temporary files there. The bundled `docker-compose.yaml` mounts `config.yaml` at `/app/config.yaml` and a named volume `goclaw-state` on the state directory.

### Linux systemd Hardening
`goclaw service install` writes a per-user unit (`~/.config/systemd/user/goclaw.service`)
by default. `sudo goclaw service install --system` writes a system unit,
`/etc/systemd/system/goclaw.service`, that runs as the user who ran `sudo` (or
the user given with `--run-as <user>`), never as root. The generated unit sets
`Restart=always` and `RestartSec=5`, `Environment=HOME=<home>` and
`GOCLAW_STATE_DIR=<home>/.goclaw` for that user, and reads optional variables
from `/etc/sysconfig/goclaw` (`EnvironmentFile=-`). For the system unit that
file is a place for `GOCLAW_SECRET_KEY`, root-owned and `0600`; a per-user
unit's manager runs as the user, so there the key file must be one the user
can read, or the key is silently not set.

Add sandbox directives as a drop-in rather than by editing the unit, which a
reinstall rewrites. For the system unit, `sudo systemctl edit goclaw` opens
`/etc/systemd/system/goclaw.service.d/override.conf`; for the per-user unit use
`systemctl --user edit goclaw`. Keep `Restart=always`.
The state directory lives in the run-as user's home, so `ProtectHome=read-only`
and `ProtectSystem=strict` need a `ReadWritePaths=` line for it; without one
the gateway cannot write its database, vault or logs.

```ini
# sudo systemctl edit goclaw
[Service]
# ── Sandboxing & Process Hardening ──
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
# the run-as user's state directory:
ReadWritePaths=/home/goclaw/.goclaw
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictRealtime=true
CapabilityBoundingSet=
```

Then `sudo systemctl daemon-reload && sudo systemctl restart goclaw`. These
settings also bind the commands the agent runs through `shell_exec`: one that
needs a device or a capability (a live `tcpdump` capture, for example) fails
under them.

---

## 3. AI & Data Privacy Guarantees

| Concern | GoClaw Guarantee | Technical Control |
|---|---|---|
| **Model Training on Customer Data** | **No retention by GoClaw; provider retention is yours to choose** | GoClaw stores no prompt or completion outside the customer network, and adds no telemetry of its own to inference calls. Whether the *provider* retains or trains on the data is a property of the endpoint and contract you configure, not of GoClaw: enterprise tiers (Vertex AI, Anthropic Enterprise, Azure OpenAI) offer zero-retention terms, whereas the standard public endpoints listed in the egress table do not grant them by default. For a zero-retention deployment, point GoClaw at an enterprise tier under your own agreement, or at a local Ollama / private vLLM backend. |
| **Local Vector & Memory Storage** | **On-Premise Persistence** | All episodic memories, session histories, and knowledge graphs reside in local SQLite / PostgreSQL / MySQL databases inside the customer network. |
| **Secrets in LLM Context** | **Vault References** | Credentials in `config.yaml` are written as `${secret:NAME}` references to the GoClaw secrets vault and resolved inside the gateway when the configuration loads (only in credential fields; see [Secrets](secrets.md#which-fields-are-expanded)). The model works with tools, not with the credentials behind them: a tool reads its credential from the loaded configuration. The exception is the vault tool `secret_get`, which only a human admin may call: it returns the plaintext value into the conversation, and so to the LLM provider. |

---

## 4. Tool Sandboxing & Prompt Injection Defense (OWASP LLM Top 10)

GoClaw implements multiple defense-in-depth layers to mitigate prompt injection and unauthorized command execution:
1. **Shell Command Allowlist**: Shell execution (`shell_exec`, `shell_exec_multi`) is governed by `tools.shell.allow` and `tools.shell.deny` in `config.yaml`:

   ```yaml
   tools:
     shell:
       allow: ["git", "date", "whoami"]   # empty or absent = every command blocked
       deny:  ["curl", "wget"]            # evaluated before allow
   ```

   An **empty `allow` list blocks every shell command**, and it is the default: no `allow` key means no shell. The setup wizard's operating mode writes the list: *Minimal* leaves it empty, *Full* writes `allow: ["*"]`, so a deployment set up as Full must replace that with an explicit allow list to narrow it again. `tools.mode` itself only chooses which `AGENTS.md` is seeded into the workspace; it does not change the shell policy. A hard-coded deny list (`rm -rf /`, `mkfs.`, `dd if=`, `format `, `del /s`, fork bombs, `shutdown`, Ansible, `systemctl`, `ssh`, package installs, and similar, matched anywhere in the command) is checked before both lists and cannot be overridden by configuration. Per-agent overrides of the same two keys are honoured where configured.

   Entries match by prefix: `git` also allows `gitk`, and `curl` in `deny` also blocks `curlie`. Even an allowed command is refused when it contains a shell metacharacter (`;` `&` `|` `$` `>` `<` `` ` `` `\` `!` `(` `)` `{` `}` `[` `]` `=` `#` `^` `%` `@` or a newline), so pipes, redirection and chaining never run, and neither does a command with an argument such as `git log --format=%H`.

   > [!WARNING]
   > GoClaw logs a one-line `unknown setting` warning for configuration keys it does not recognise and then continues to boot. A misspelled key is ignored, so its setting falls back to the default: a misspelled `deny` denies nothing beyond the hard-coded list. After editing this section, check the startup log for that warning before treating the restriction as applied.

2. **Path Sandboxing**: File read/write sandboxing is enforced in code, not behind a configuration flag — there is no setting that disables it. `resolveFilePath` (`internal/agent/tools.go`) rejects any path that resolves outside the agent workspace or the primary workspace, including absolute paths and `../..` traversal. System instruction files (`SOUL.md`, `AGENTS.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`) are additionally gated to privileged callers. Both checks sit in the shared resolver, so every tool that resolves a path inherits them rather than reimplementing them.
3. **Human-in-the-Loop (Approval Gates)**: A SOP step can be marked `requires_approval: true`. The run then waits until a human administrator approves or rejects the step: in a chat, through the agent's `sop_approve` tool (refused to anyone else, and to SOP, cron and webhook runs), or with `POST /api/sop/approve` and the gateway token. There is no Web UI control or CLI command for it. A notice goes to the channel set in `sop.alerts` (else only to the log), and a step not decided within 1 hour fails. Approval gates exist for SOP steps only, not for individual tool calls; for those, use the allow/deny lists above or an admin hook that blocks a call.
4. **Loop & Token Breakers**: Autonomous execution loops are guarded by circuit breakers (`loop_detector.go`) that terminate execution if repetitive tool invocations are detected.

---

## 5. Security Event Auditing & SIEM Integration

GoClaw streams structured telemetry and security audit logs to enterprise SOC / SIEM platforms:
- **OpenTelemetry (OTel)**: Native export of the gateway's logs over OTLP/HTTP (`logging.otel`) to any OTLP collector (Splunk, Datadog, Grafana, Uptrace). Traces and metrics are not exported: agent traces (steps, tool calls, timings) are kept in the `agent_traces` table and shown on the Web UI's Traces page.
- **Webhook Audit Logs**: Inbound webhook requests and verification statuses are recorded in structured format (`internal/gateway/access_log.go`).
- **Syslog / Journald**: Systemd journal logs capture all authentication attempts, pairing token generations, and configuration changes. A config reload logs each changed key with credential values masked. The Web UI authenticates with one shared gateway token, so a change is recorded by key and time but not attributed to a person.
