fix(security): active guard enforcement, CEO A2A target check, notification expiry (#595)

* fix(security): guard goes active; CEO A2A respects no-comms roles; ack notifications expire

ROBOCO_GUARD_PASSIVE_MODE defaults to false in both compose files — the
deferred post-calibration flip; fail_secure stays off and the env override
remains the rollback. can_a2a_direct no longer short-circuits the CEO past
the no-comms set (auditor/pr_reviewer/prompter/secretary), now canonical
in foundation.policy.communications.NO_COMMS_ROLES and shared with the
content-actions gate; the A2A service refuses at conversation creation
instead of silently suppressing the wake. Ack-required notifications get
expires_at stamped from ROBOCO_NOTIFICATION_ACK_TTL_HOURS (default 48,
0 disables), so the re-escalation sweeper's expires_at query matches rows
for the first time.

* refactor(notification): extract _ack_and_expiry — xenon rank back under B

The expires_at stamping pushed _create_notification_with_session to
rank C; the requires_ack + expiry derivation moves into a helper with
the same semantics and comments.

* test(conftest): dispose the global DB engine after every test

Production code reaching get_db_context()/get_engine() lazily creates the
process-global engine bound to the current event loop; with per-test
function-scoped loops, any later test touching the global path inherits a
dead-loop engine and dies with 'Future attached to a different loop' —
the order-dependent class that has been wandering the suite (cloud_auth
login, metrics, tasks-routes, full-lifecycle) whenever collection order
shifts. An autouse fixture now close_db()s after every test, keeping the
global path loop-local; no-op when untouched.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-19 18:46:44 +02:00
committed by GitHub
co-authored by Renn F
parent 5b27a443e9
commit fc41dfa40e
18 changed files with 445 additions and 65 deletions
@@ -6,7 +6,7 @@ A call to the orchestrator API fails with a generic `400` or `403` and no detail
## Cause
RoboCo's HTTP security layer (`fastapi-guard`; see `docs/rag/architecture/http-security-guard.md`) rejected the request. This can only happen when both `ROBOCO_GUARD_ENABLED=true` and `ROBOCO_GUARD_PASSIVE_MODE=false` (enforce mode) are set. As of 2026-07-01 the guard is off by default, and wherever it is enabled it runs passive/log-only, so this is not something that occurs today — it's documented so the block is recognizable if/when enforcement is turned on later.
RoboCo's HTTP security layer (`fastapi-guard`; see `docs/rag/architecture/http-security-guard.md`) rejected the request. This can only happen when both `ROBOCO_GUARD_ENABLED=true` and `ROBOCO_GUARD_PASSIVE_MODE=false` (enforce mode) are set. As of 2026-07-19 that is the default on the NAS build compose — a matching request is actually blocked there, not just logged.
The guard never returns which rule or signature matched, by design, so the 400/403 body itself gives you nothing to act on. Avoiding the triggers below is the only real mitigation.
@@ -14,18 +14,22 @@ The guard never returns which rule or signature matched, by design, so the 400/4
Generic WAF signatures (SQL injection, XSS, path traversal, suspicious URL patterns) are excluded from scanning on the free-text body fields of `note` / `commit` / `dm`, so normal code, SQL, diffs, file paths, and URLs in those bodies are safe from that layer. Three custom validators scan those same bodies regardless of that exclusion:
- Prompt-injection detection
- Secret-exfil detection — literal credential-shaped strings (`sk-ant-...`, `ghp_...`, postgres connection URLs) or phrasing like "reveal your api keys"
- Prompt-injection detection — phrasing like "ignore previous instructions", or "bypass/disable/override the guard/filter/restriction" (a real risk in this repo's own security-work commit messages and notes: `roboco/security.py`'s own doctring vocabulary uses "guard", "bypass", "disable" constantly)
- Secret-exfil detection — literal credential-shaped strings (`sk-ant-...`, `ghp_...`, postgres connection URLs) **or the literal doc pattern `ROBOCO_ENCRYPTION_KEY=<...>` / `ROBOCO_AGENT_AUTH_SECRET=<...>` / `FERNET_KEY=<...>`** (matches `CLAUDE.md` and `.env.example`'s own env-var documentation verbatim — quoting or editing those lines in a `note`/`commit` body trips this) or phrasing like "reveal your api keys"
- Internal-SSRF detection — fetch-type bodies targeting internal or metadata hosts (`169.254.169.254`, `roboco-*` internal service hostnames)
The three custom validators scan the raw request body regardless of which top-level field the text sits in — unlike the WAF's field exclusion, there is no safe field for these three.
## Solution: Hygiene Rules
Follow these when composing `note` / `commit` / `dm` bodies or any fetch-type payload, regardless of whether enforcement is currently active:
Follow these when composing `note` / `commit` / `dm` bodies or any fetch-type payload:
1. Never paste real secrets or credentials (API keys, tokens, DB connection strings) into a request body, even inside a code snippet or diff.
2. Never aim a fetch/HTTP-call body at an internal service host (`roboco-*`) or a cloud metadata endpoint (`169.254.169.254`).
3. Code, SQL, diffs, file paths, and HTML snippets are otherwise fine to include — the WAF layer is calibrated to exclude legitimate agent content on those fields.
2. When discussing an env var like `ROBOCO_ENCRYPTION_KEY` in a note/commit body, don't write it as `NAME=value` (even a placeholder value) — write `ROBOCO_ENCRYPTION_KEY` and describe the value separately, e.g. "set to a generated Fernet key", to avoid the `NAME=<10+ chars>` credential-shape match.
3. Avoid "bypass/override/disable the guard/filter/restriction/safety" phrasing in commit messages or notes about this security layer itself — describe the change without that verb+noun adjacency (e.g. "excludes X from the WAF scan" instead of "bypasses the guard's WAF scan").
4. Never aim a fetch/HTTP-call body at an internal service host (`roboco-*`) or a cloud metadata endpoint (`169.254.169.254`).
5. Code, SQL, diffs, file paths, and HTML snippets are otherwise fine to include — the WAF layer is calibrated to exclude legitimate agent content on those fields (this exclusion does not cover the three custom validators above).
## Current Status (2026-07-01)
## Current Status (2026-07-19)
The guard is built on the `feature/fastapi-guard-hardening` branch, off by default (`ROBOCO_GUARD_ENABLED=false`), and wherever enabled it runs in passive/log-only mode. No agent request is being blocked by it today — the rules above are about good hygiene now and correctness later, not a live restriction.
`ROBOCO_GUARD_ENABLED` is off by default in config; the NAS build compose arms it ON with `ROBOCO_GUARD_PASSIVE_MODE=false` (enforce). A matching request on that deploy is genuinely blocked, not just logged — the rules above are a live restriction there, not just future-proofing. The registry compose and a bare config default both stay off.