HTTP Security
RoboCo can front its API with a fastapi-guard security layer — IP and rate controls, a signature WAF, security headers, cloud-provider and honeypot checks, an emergency kill switch, and three RoboCo-specific content validators — for when you expose the panel/API beyond a trusted LAN.
The layer is off by default. When ROBOCO_GUARD_ENABLED is unset, create_app never mounts the middleware and the request path is byte-for-byte unchanged; the per-route guard decorators are harmless no-ops (roboco/security.py). A personal NAS deploy needs none of this — it's built for cloud/public hosting.
What it does
Armed, the middleware sits outermost and applies, per route:
- Rate limiting & size caps — a baseline throttle plus tighter per-endpoint limits on sensitive routes (provider-key writes, CEO release ops, agent verbs).
- A signature WAF — SQLi/XSS/path-traversal/etc. detection on request bodies, headers, params, and path.
- Security headers — HSTS, CSP,
X-Frame-Options,nosniff, referrer and permissions policies on every response. - Cloud-provider & honeypot checks — block datacenter egress on the highest-value routes; form-trap honeypots on human-facing POSTs.
- Three RoboCo custom validators the stock WAF cannot cover — prompt-injection, secret-exfil, and internal-SSRF scanning on the prompt-facing and agent-content surfaces.
- An emergency lockdown (
ROBOCO_GUARD_EMERGENCY) — a flip-on-without-redeploy switch that blocks every non-whitelisted IP during an active attack.
Behind nginx, guard trusts the single proxy hop for the real client IP, excludes WebSocket upgrades and health/docs paths, and keeps RoboCo's own CORS.
- The internal agent mesh is exempt from WAF/IP-ban entirely. Agents reach the orchestrator directly over the docker bridge, HMAC-authenticated — guard's WAF/IP-ban/rate-limit exists for the external attack surface arriving through nginx, not for already-authenticated internal traffic. Without this exemption, guard IP-banned agent containers the moment a single journal or note body tripped a signature, wedging every subsequent verb that container tried. The exemption is scoped to loopback plus docker's own bridge address-pool range — never a broader LAN/CGNAT range that could also cover a real external client hitting nginx.
- A host-proxied chain (e.g. Tailscale Serve terminating in front of nginx) resolves to the real client, not a whitelisted hop. Guard's own logic peels a single fixed proxy hop, which is correct for a direct nginx client but wrong for one more hop in front of it — that shape used to resolve to a whitelisted gateway IP, silently going inert on the whole surface behind that hop.
ROBOCO_GUARD_TRUSTED_CHAIN_PEERSnames the extra hop(s) to peel (a single exact IP per entry, never a CIDR range — a range would readmit every sibling container's real address into the trusted-hop set). Left unset, only a loopback rightmost hop ever peels; a host-proxied chain behind an unconfigured docker-bridge gateway is logged once so the gap isn't silent.
The orchestrator API itself is not reachable from outside the host either way — see how the orchestrator is kept off the open network for that separate, always-on layer. Guard is what stands between nginx's forwarded traffic and the app once a request does arrive.
Passive first, then enforce
Guard has two enforcement postures:
ROBOCO_GUARD_PASSIVE_MODE | Behaviour |
|---|---|
true (calibrate) | Log-only. Detections are logged but never block — legit and malicious requests pass through. Safe to arm on live traffic to surface false positives first. |
false (enforce) | Detections block the request. |
RoboCo's request bodies are code, SQL, diffs, file paths, HTML, and URLs — task specs, agent notes and commits, RAG queries, git bodies, chat. A stock WAF reads that legitimate traffic as attacks. RoboCo ships a calibration (excluded_detection_body_fields in build_security_config, derived from the real request models) that excludes those free-text fields from WAF scanning so active mode does not false-positive — while the WAF stays active on every structured (id/enum/slug/branch) field and the prompt-injection / secret-exfil / SSRF validators keep firing regardless. Those two content validators are themselves calibrated against RoboCo's own vocabulary — documenting a placeholder secret in a doc-shaped string, or writing neutral engineering prose about a security control, no longer trips them. If you're building your own deployment from a config default rather than the tracked NAS compose below, arm passive first, watch the logs for any straggler false positive, then flip to active.
The NAS composes (docker-compose.yml / docker-compose.yaml) now default to active enforcement (ROBOCO_GUARD_PASSIVE_MODE=false) — passive-mode calibration on that deploy reviewed clean, so a matching request is genuinely blocked, not just logged. ROBOCO_GUARD_FAIL_SECURE=true on that same compose (as of v0.29.0) means a guard-internal error fails closed rather than letting a bad request through — the NAS no longer relaxes this knob now that the calibration is proven clean. The registry compose (docker-compose.registry.yml) still omits the guard trio entirely, so a fresh third-party deploy starts from the safe config default (guard off) and calibrates on its own schedule before enforcing. A bare roboco/config.py default is also off. See The three compose files in Deployment for what each compose is for.
Active enforcement means a real false positive now genuinely blocks instead of just logging. The two triggers that used to catch legitimate RoboCo traffic — a documented placeholder secret in a doc-shaped string, and neutral engineering prose about a security control — are the ones the RoboCo-vocabulary calibration above fixed. If you still hit a straggler on unusual content, the instant rollback is env-only: set ROBOCO_GUARD_PASSIVE_MODE=true and restart.
Scanner honeytrap & auto-ban
Automated scanners probe every host for well-known soft spots (/.env, /wp-login.php, /phpmyadmin, /.git/config, …). RoboCo turns those probes against the scanner, in two layers matched to where the traffic actually lands — behind nginx only /api, /ws, /health, and /ready reach the orchestrator, so guard can only see probes on those paths:
- Guard adaptive ban (the
/apisurface). The guardthreat_ban_configcarriesrecon/sensitive_file/cms_probingcategories. A scanner probing those fingerprints on an/apipath is detected on the URL-path scan, and repeated probes from one IP trip a per-IP auto-ban (redis-backed, 24h). This only bans in active mode (passive logs the recon hit) and requires redis (the 24h ban exceeds the in-memory cap). - nginx edge-drop (the classic root paths). The classic scanner paths never reach the orchestrator, so nginx drops them at the edge with
444(closes the connection, returns nothing) before they touch the panel. It's anchored to known scanner fingerprints —/.well-knownand every real panel/API route are untouched — and is always on, independent ofROBOCO_GUARD_ENABLED. - status:401 behavior rule. A second global log-only behavior rule watches
status:401responses for credential-probing visibility — threshold 20 in 300s, and it never bans, only logs. A stale internal HMAC token must never earn one, since bans override the whitelist and a rotating agent credential would otherwise look like a brute-force sweep.
Fail-secure
ROBOCO_GUARD_FAIL_SECURE decides what happens if a security check itself errors: true (default) fails closed — block the request — which is the right default for public hosting. As of v0.29.0 the NAS compose also sets this to true — the earlier false relaxation was a holdover from the passive-mode calibration phase, and the proven-clean calibration no longer justifies letting a guard-internal error pass a request through.
Package floor-pinning
RoboCo floor-pins both guard packages: fastapi-guard>=7.6.0 and guard-core[redis]>=3.12.0.
The two are mutually unpinned upstream and the pairing matters — guard-core 3.12.0 paired with fastapi-guard below 7.6.0 silently drops every body-based return_pattern rule, so a mismatched upgrade leaves your response-body rules inert with no error.
The floor pins in RoboCo's requirements prevent that combination from installing.
Excluded paths
exclude_paths semantics changed upstream in v0.29.0 — excluded paths no longer skip every check.
An excluded path now skips only the WAF and behavioral tracking; IP-ban and rate-limit still enforce on excluded paths.
The practical effect: health checks and docs paths stay WAF-exempt (so a benign health probe doesn't trip a signature), but a banned IP or a rate-limit violator hitting an excluded path is still blocked.
Readiness probe
apply_guard exposes an internal readiness probe at GET /_guard/status, reachable only on the docker mesh or localhost:8000 — nginx never routes it, so it's invisible from outside the host.
Use it for container health checks and mesh-level readiness; it returns guard's current configuration posture without touching the request path.
Enable it
1ROBOCO_GUARD_ENABLED=true2ROBOCO_GUARD_PASSIVE_MODE=false # default on the NAS composes: enforce. true to log-only and calibrate first3ROBOCO_GUARD_FAIL_SECURE=true # now also the NAS compose default as of v0.29.04# ROBOCO_GUARD_EMERGENCY=true # attack lockdown kill switch5# ROBOCO_GUARD_EMERGENCY_WHITELIST=1.2.3.4 # extra allowed IPs during lockdown6# ROBOCO_GUARD_TELEMETRY_ENABLED=true # + guard_agent_api_key + guard_project_id (payloads exclude HMAC/session headers)7# ROBOCO_GUARD_TRUSTED_CHAIN_PEERS=172.18.0.1 # exact IP(s) of a host proxy hop in front of nginx (e.g. Tailscale Serve's docker gateway); never a CIDR range8# ROBOCO_GUARD_SCAN_RESPONSE_BODY=true # let return_pattern rules read response bodies (default off; Roboco's own rules never need it)9# ROBOCO_GUARD_LOG_SUSPICIOUS_LEVEL=WARNING # log level for suspicious-request entriesGuard's enforce_https check is hardcoded off — TLS termination sits at nginx, in front of the orchestrator, so the app layer never needs to redirect or reject on scheme. There's no flag for this; it isn't derived from ROBOCO_ENVIRONMENT or any other setting.
See the environment reference for the full flag list.
Response-body scanning & log levels
ROBOCO_GUARD_SCAN_RESPONSE_BODY (default off) lets return_pattern rules read response bodies, not just status codes — Roboco's own rules never need it, so it stays off on the tracked composes. ROBOCO_GUARD_LOG_SUSPICIOUS_LEVEL (default WARNING) controls the log level for suspicious-request entries when you need more or less detail in the guard logs.
Telemetry (optional, off)
ROBOCO_GUARD_TELEMETRY_ENABLED reports security events/metrics to a guard-core platform via guard-agent. It is off by default and no data leaves the box while off; flip it on and set ROBOCO_GUARD_AGENT_API_KEY + ROBOCO_GUARD_PROJECT_ID to enable. As of v0.29.0, telemetry payloads exclude HMAC and session headers via agent_sensitive_headers — agent auth tokens never leave the box even with telemetry on.
Route resolution
route_resolution_strict was evaluated for v0.29.0 and deliberately left off.
Enabled, it turns every unresolved path into a 500 for every client regardless of whitelist — which would blind the status:404 behavior rule (a scanner sweep would read as 500s, not 404s) and turn routine client/panel typos into apparent server errors.
Leave it off unless you have a specific reason and understand this trade-off.
Next
→ Environment reference for every flag · Deployment for where to set them · back to Optional subsystems.