GoClaw implements deep application-level security across multiple layers: path sandboxing, shell/HTTP allowlists, SSRF protection, PBKDF2 encryption, skill security scanning, SAST analysis, ingress IP allowlisting, and gateway auth.
flowchart TD
subgraph SEC["GoClaw Security Layers"]
GW["<b>Gateway</b><br/>Auth + Rate limit<br/>trustProxy ยท IP allowlist"]
TOOL["<b>Tool-level</b><br/>Sandboxing<br/>Path / Shell ยท HTTP policy"]
DATA["<b>Data-at-rest</b><br/>Encryption<br/>AES-256-GCM ยท PBKDF2 (600K)"]
NET["<b>Network</b><br/>SSRF block<br/>Domain WL ยท Private IP"]
CODE["<b>Code</b><br/>SAST scan<br/>Linter ยท gosec / ruff"]
CONT["<b>Content</b><br/>Skill scanner<br/>Rule-based ยท Prompt inject"]
GW --> NET
TOOL --> CODE
DATA --> CONT
end
| # | Feature | OpenClaw | GoClaw | Status |
|---|---|---|---|---|
| 7.1 | Path sandboxing | Workspace isolation | resolveFilePath() โ path traversal blocked, all paths resolved within workspace |
Done |
| 7.2 | Shell command allowlist | Permission system | tools.shell.allow / tools.shell.deny in config. Empty allow = all blocked. Deny checked first. Entries match by prefix (git also matches gitk). A command containing a shell metacharacter (; & | $ > < ` \ ! ( ) { } [ ] = # ^ % @ or a newline) is refused even with allow: ["*"]. Hardcoded deny patterns, matched anywhere in the command and case-insensitively: destructive commands (rm -rf /, dd if=, mkfs., format ), shutdown/reboot, Ansible, make deploy/install/update/restart and similar targets, service management (systemctl, launchctl, service), ssh/scp, system package installs, user management, Windows registry and firewall changes |
Done |
| 7.3 | DM pairing system | Device pairing (web) | Pairing code for Discord/Teams DMs; approve on the Web UI's Pairing tab. A CLI approval made while the gateway runs reaches it only after a restart | Done |
| 7.4 | DM policy modes | Per-channel config | dm_policy: pairing (default), allowlist, open |
Done |
| 7.5 | Guild channel isolation | Channel allowlists | Bot only responds to @mentions or direct replies in Discord guilds | Done |
| 7.6 | Read-only database queries | โ | db_query allows only SELECT/SHOW/DESCRIBE/EXPLAIN. INSERT/UPDATE/DELETE blocked. Writes go through db_write, only on datasources with allow_write: true, only for admins and SOP/cron runs (not webhook-triggered or delegated runs), capped at tools.max_db_write_rows rows per call. metrics_store writes its own agent_metrics table on a datasource only with allow_write, for an admin or admin-authored SOP/cron run (not webhook-triggered or delegated runs); its first write creates the table, and its writes are not row-capped |
Done |
| 7.7 | Query timeout + row limits | โ | 30s timeout, which the database enforces too: about 29s on PostgreSQL and MySQL/MariaDB, and on ClickHouse where the account lets goclaw set max_execution_time (otherwise the profile's own); max 10,000 rows (tools.max_db_query_rows), max 1000 chars per cell |
Done |
| 7.7a | Read-only on the database | โ | db_query reads in a read-only PostgreSQL transaction; on MySQL/MariaDB with the connection's session set to transaction_read_only=1 for the read (a read-only transaction alone still runs DDL); on ClickHouse with readonly=1 where the account allows it (writes and SET refused; url()/file()/remote() stay refused by goclaw's checks, since DESCRIBE TABLE url(โฆ) still connects out). db_query's SQL never runs on PostgreSQL's simple protocol, which would run several statements from one query |
Done |
| 7.8 | HTTP response size limit | โ | http_request caps response body at 100KB |
Done |
| 7.9 | Tool loop safety | โ | tools.max_tool_iterations (default 20) prevents infinite LLM tool loops |
Done |
| 7.10 | Dangerous command hardcodes | โ | Fork bombs, disk wipes, shutdown commands blocked regardless of config | Done |
| 7.11 | Git write gating | โ | Read ops always allowed, write ops require config flag, dangerous ops always blocked | Done |
| 7.12 | File edit safety | โ | file_edit requires unique string match โ no mass replacements |
Done |
| 7.13 | Bootstrap file protection | Read-only MEMORY.md | For anyone but an admin or a SOP/cron run, file_write/file_edit block writes to MEMORY.md, SOUL.md, TOOLS.md, AGENTS.md, IDENTITY.md, USER.md, subagent profiles (*.agent.md) and instructions (*.instructions.md). No file tool writes inside a .git directory |
Done |
| 7.14 | Gateway auth (token) | OAuth / device code | X-GoClaw-Token header, Bearer auth, health probes exempt |
Done |
| 7.15 | Rate limiting | โ | Per-IP token bucket middleware, configurable gateway.rate_limit.per_minute |
Done |
| 7.16 | Secrets encryption | Credential store | AES-256-GCM vault + PBKDF2 key derivation (600K iterations) | Done |
| 7.17 | Trust proxy | โ | gateway.ingress.trust_proxy (and gateway.rate_limit.trust_proxy) โ only reads X-Forwarded-For when trusted, and then its last entry, the one the proxy wrote. Used by the ingress allowlist, the rate limits (gateway and App API) and the audit log. Prevents IP spoofing |
Done |
| 7.18 | HTTP default-deny policy | โ | nginx-style: empty allow list blocks all external HTTP. default_policy: deny|allow |
Done (GoClaw only) |
| 7.19 | SSRF protection | โ | DNS-level private IP blocking (0.x, 10.x, 100.64-127.x, 172.16-31.x, 192.168.x, loopback, link-local, ULA IPv6) | Done (GoClaw only) |
| 7.20 | Domain whitelist/blacklist | โ | tools.http.allow / tools.http.deny with wildcard (*.mn), CIDR, exact match |
Done (GoClaw only) |
| 7.21 | Internal hostname bypass | โ | Allowlisted hostnames that resolve to private IPs are permitted (e.g. monitor.example.mn โ 10.0.2.7). A wildcard entry counts: see Bypass Mechanisms |
Done (GoClaw only) |
| 7.22 | Per-agent HTTP scope | โ | Agent-level tools.http.allow/deny overrides global policy via runtimeScope |
Done (GoClaw only) |
| 7.22a | Hosts per run | โ | tools.http.max_hosts_per_run (default 100): one agent run, its subagents included, reaches at most that many different hosts through SafeHTTPClient; the next new host is refused and logged. A start-up warning names shell.allow: ["*"] or an open default_policy: allow on a gateway other machines can reach |
Done (GoClaw only) |
| 7.23 | Pre-flight URL validation | โ | ValidateURL() โ domain policy + SSRF check before the browser tools navigate and before notify posts to a webhook URL. Other tools' requests are checked when they connect (SafeHTTPClient) |
Done (GoClaw only) |
| 7.24 | Skill Security Scanner | โ | Rules for prompt injection in skill text and dangerous code patterns. Blocks critical findings on install | Done (GoClaw only) |
| 7.25 | SAST Scanner | โ | run_sast_scanner tool โ runs gosec (Go) or bandit (Python) on workspace code; the scanner must be installed on the host |
Done (GoClaw only) |
| 7.26 | Code Linter | โ | run_code_linter tool โ runs golangci-lint (Go), ruff (Python) or dart analyze (Dart/Flutter); the linter must be installed on the host |
Done (GoClaw only) |
| 7.27 | Ingress IP allowlist | โ | Webhook ingress: M365 preset auto-fetch or explicit IPs/CIDRs + access logging (see below) | Done (GoClaw only) |
| 7.28 | Memory write RBAC | โ | memory_write: personal scope for any identified (paired) user, never from SOP/cron/webhook runs; agent_notes and agent_config for admins and the roles/users listed under memory.write_access (SOP/cron/webhook runs may write agent_notes only, never global or agent_config); global: while memory.write_access is enabled (the default, with allowed_roles: [system]), only the roles and users it lists; with it disabled, admins. memory_consolidate: admins and SOP/cron runs. update_core_instruction: a human admin only |
Done |
| 7.29 | isHumanAdmin Check | โ | Critical actions (altering identity, persona) require a real human admin โ automated SOP/Cron runs are blocked. | Done (GoClaw only) |
| 7.30 | Rollback Safety | โ | SOP rollback commands are validated against the shell allowlist to prevent privilege escalation. | Done (GoClaw only) |
| 7.31 | Docker sandboxing | Docker-based isolation | โ | TODO |
GoClaw's SafeHTTPClient implements DNS-level SSRF protection at the transport layer:
flowchart LR
REQ["HTTP Request"] --> DOM["<b>Domain Filter</b>"]
DOM -->|"Pass"| DNS["<b>DNS Resolve</b>"]
DNS --> IP["<b>Private IP Check</b>"]
IP -->|"Public / Allowed"| CONN["<b>Connect</b>"]
DOM -->|"Deny list / Not in allow /<br/>default_policy=deny + empty allow"| B1["<b>BLOCK</b>"]
IP -->|"10.x / 172.16-31.x / 192.168.x /<br/>loopback / link-local / IPv6 ULA"| B2["<b>BLOCK</b>"]
| Range | Description |
|---|---|
0.0.0.0/8 |
"This host" (a connect to 0.0.0.0 reaches loopback) |
10.0.0.0/8 |
Class A private |
100.64.0.0/10 |
Carrier-grade NAT |
172.16.0.0/12 |
Class B private |
192.168.0.0/16 |
Class C private |
127.0.0.0/8, ::1 |
Loopback |
169.254.0.0/16, fe80::/10 |
Link-local |
fc00::/7 |
IPv6 Unique Local Address |
tools.http.allow list. IP and CIDR entries match only a URL whose host is an IP address: a hostname that resolves into an allowed CIDR is still refused unless the hostname itself is allowed.monitor.example.mn โ 10.0.2.7)*.mn, any .mn name that resolves to a private address is reached, and * turns private-address blocking off for every host. Allow internal hosts by their exact name, and keep wildcards for public domains.gitlab, sonarqube, the observability tools, Doctor and the Integrations page's Test reach the host of the instance's own url (or Uptrace dsn) as if it were on allow (WithConfiguredHost), for their own requests only. The address is the admin's, not the model's; http_request and the browser are not affected, and every other host such a request reaches (a redirect) is checked as usual.ValidateURL()) by the same rules, and every request a page makes against the policy of the agent using the browser.tools.http.deny is refused whichever host name resolves to it, a name on the allow list included.tools:
http:
default_policy: "deny" # "deny" (default) or "allow"
allow:
- "*.googleapis.com"
- "api.github.com"
- "monitor.example.mn" # internal hostname โ private IP allowed
- "10.0.2.0/24" # explicit CIDR allowlist (URLs with an IP host only)
deny:
- "127.0.0.1"
- "*.internal"
| Pattern | Matches |
|---|---|
* |
All domains, private addresses included |
*.mn |
example.mn, sub.example.mn, including names that resolve to private addresses |
api.github.com |
Exact match only |
10.0.2.0/24 |
CIDR range, for URLs whose host is an IP address |
192.168.1.1 |
Exact IP, for URLs whose host is that IP |
| Layer | Rules | Detects |
|---|---|---|
| Prompt Injection | prompt-override, role-injection, jailbreak-attempt, shell-danger-in-skill, secret-extraction, hidden-instruction, invisible-unicode, data-exfil-url |
LLM manipulation, curl|bash, credential theft, instructions hidden in HTML comments or zero-width characters |
| Code Patterns | dangerous-exec, dynamic-code-exec, crypto-mining, reverse-shell, env-harvesting, potential-exfiltration, obfuscated-hex, obfuscated-base64, suspicious-network |
Shell injection, mining, exfiltration |
goclaw skills install or goclaw skills add, from any sourcegoclaw skills scan / goclaw skills scan <name>| Feature | Description |
|---|---|
| M365 preset | gateway.ingress.allow_preset: microsoft-teams fetches the Microsoft 365 Skype/Teams IPv4 ranges from endpoints.office.com at startup. If the fetch fails, the gateway does not start |
| Explicit IPs | Direct IP addresses in gateway.ingress.allow_ips. A non-empty allow_ips replaces the preset: the two are not combined |
| CIDR ranges | Network ranges (e.g. 52.112.0.0/14) |
| Scope | Without gateway.ingress.port the webhooks share the main port, and the allowlist then applies to the whole gateway, Web UI included. Set gateway.ingress.port to keep it to the webhook listener |
| Loopback | Requests from 127.0.0.0/8 and ::1 are always let through (CLI, local health probes) |
| Access logging | Every request logged via runtimeinfo.RecordIngressAccess() |
| Denied tracking | runtimeinfo.RecordIngressDenied() for monitoring |
| Web UI display | Gateway tab shows access logs table |
GoClaw's interactive approval covers SOP steps only. A step marked requires_approval: true waits for an approval made through the gateway API; everything else is governed by the allow/deny lists.
| # | Feature | OpenClaw | GoClaw | Status |
|---|---|---|---|---|
| 19.1 | Approval policy | security (deny/allowlist/full) |
Shell allowlist + HTTP domain policy + per-agent scopes | Done |
| 19.2 | Control UI approval | Visual approve/deny in web panel | No dashboard control. A SOP step marked requires_approval: true is approved or rejected with POST /api/sop/approve ({"job_id": "โฆ", "action": "approve"} or "reject" with a "reason") and the gateway token. A step still waiting after 1 hour fails. |
Partial |
| 19.3 | macOS app approval | Native dialog | โ | TODO |
| 19.4 | Channel approval | /approve <id> |
โ (no chat or CLI command approves a step) | TODO |
| 19.5 | Allowlist persistence | exec-approvals.json |
config.yaml based allow/deny lists with hot-reload support. |
Done |
| 19.6 | Safe bins auto-allow | Known safe commands pre-approved | Hardcoded deny patterns + skill bin-check | Partial |
| 19.7 | Skill CLI auto-allow | Skills register their CLIs as safe | โ | TODO |
| 19.8 | Approval forwarding | Route approval prompts to channels | A step waiting for approval sends a notice to the channel and target set in sop.alerts (else only to the log). The notice carries the job ID to pass to the API; the sop approve <id> it suggests is not a command. |
Done (GoClaw only) |
| Aspect | OpenClaw | GoClaw |
|---|---|---|
| Sandboxing model | Docker container isolation | Application-level (path, shell, HTTP, IP) |
| HTTP policy | No default-deny | nginx-style default-deny |
| SSRF protection | โ | DNS-level private IP blocking |
| Domain filtering | โ | Wildcard + CIDR + hostname matching |
| Secret encryption | Credential store | AES-256-GCM + PBKDF2 (600K iterations) |
| Skill scanning | โ | Rule-based security scanner |
| Code analysis | โ | SAST scanner + code linter (gosec, bandit, golangci-lint, ruff) |
| Ingress security | โ | M365 IP preset + CIDR allowlist + access logs |
| Memory RBAC | โ | Admin-only write for core tools |
| Interactive approval | Multi-surface (web, macOS, channel) | SOP step approval through the API; otherwise shell/HTTP allowlists |
| Per-agent scopes | โ | Agent-level HTTP policy overrides |
GoClaw security philosophy: Defense in depth at the application layer โ no Docker dependency for security. Every tool has its own safety boundary (path traversal, SQL injection, SSRF, shell injection) with configurable allow/deny policies.
Coverage: the security features above are in place except Docker sandboxing; exec approval is limited to SOP steps approved through the API.