Part IV — The gates¶
The gates are the deterministic, fail-closed checks at the CLI entry points — the
code half of enforcement (S2), distinct from the runtime PreToolUse hook (S19)
that catches agent-initiated tool calls. Each prints a gate-first [SEC-GATE/…]
code so an operator sees exactly which check fired. The reviewer's takeaway for
the whole Part: these gates are genuine deterministic controls, but they guard
only the CLI entry points they wrap — agent-initiated actions are the hook's
surface, not theirs.
| SEC-GATE family | Fires on | Cleared by |
|---|---|---|
[SEC-GATE/DESTRUCTIVE:*] (S11) |
Destructive writes: overwrite-update, overwrite, restore-backup | PASS / verified CONDITIONAL PASS clearance, or valid signed waiver |
[SEC-GATE/INTEL-FRESHNESS] (S12) |
Stale threat-intel snapshot on a generation run | Signed waiver, action security-intel-freshness |
The destructive-operation gate ✅ {#S11}¶
The adversary: an agent or operator invoking a destructive overwrite without
a recorded decision that it is acceptable. The control: the gate wraps the
clearance engine of S8 at three call sites — overwrite-update
(--update --overwrite), overwrite (fresh generate with --overwrite), and
restore-backup — and blocks any of them unless a matching PASS or verified
CONDITIONAL PASS clearance, or a valid signed waiver, exists. That is C-5
("clearance precedes destruction") made executable. For overwrite-update, the
refusal tips the operator toward --merge, which needs no clearance because it
does not destructively overwrite.
The one exemption is disciplined: a --migrate overwrite is exempt only via an
explicit in-process parameter threaded through the call chain — never via ambient
module state. The module treats a public off-switch as "an off-switch, not a
control," and --migrate supplies its own rollback (a pre-fencing snapshot tag
plus --revert-migration).
Honest ceiling. The gate is fail-closed and deterministic, but it guards
only the four CLI entry points it wraps — agent-initiated Bash/Write never
reach it (that is S19). It also cannot judge whether a clearance should have
been granted; it enforces that one exists, is unspent, and is not overridden by
a HALT. The soundness of the clearance rests on the sentinel's judgment (S8).
The intelligence-freshness gate ✅ {#S12}¶
The adversary: a generation run that silently ships stale threat
intelligence — including one that tries to look fresh by editing a timestamp.
The gate blocks a whole generation run when the threat-intel snapshot is stale
(all-or-nothing: age > 24h TTL, a future/unparseable timestamp, an explicit
stale status, or stale-data markers).
The interesting control for a reviewer is the payload-digest bind: if the
digest field is present, it must equal the SHA-256 of the six intel-bearing
placeholders. This closes the obvious evasion — relabelling a stale snapshot's
timestamp to "now" no longer passes, because reproducing the digest requires
regenerating the placeholders, i.e. actually having fresh data. Freshness is
bound to content, not to a self-asserted timestamp. The gate is cleared by a
signed waiver (action security-intel-freshness) for air-gapped/offline runs,
and its refusal names a blast radius — how many intel-bearing placeholders are
in play.
Honest ceiling. The gate guarantees a generation cannot silently ship stale intel and cannot fake freshness with a timestamp edit. It does not vouch for the correctness or completeness of the feed content itself — only recency and content-timestamp consistency. An offline waiver is an explicit, signed acceptance that intel is knowingly stale for that run.
Shrink-policy: protecting enriched fenced content ✅ {#S13}¶
The adversary here is subtle, which is why it matters. --shrink-policy
protects operator-enriched content inside AGENTTEAMS fences from being silently
overwritten on --update --merge. The detector flags a regenerated body that is
<50% of the existing length, drops ≥3 list items, or loses concrete file-paths or
backticked identifiers. Levels: preserve (default — keep the enriched body +
notice), warn (write the smaller body but save a .lost.<sid>.md.agentteams-bak recovery
sidecar), halt (refuse the whole-file write), allow (write silently).
The security-relevant twist is the override: template-authoritative fences
(invariant_core, security_authority, security_rules_invariant,
security_verdict_contract), brief-derived fences, and whole constitutional
files ignore preserve — the template body always wins. This exists to
defeat a specific attack: an adversary padding a weakened security region with
tokens so the regenerated (correct) body looks like a "shrink" and gets
preserved. For these fences, "shrinking" back to canonical is exactly right. A
security-fence rename is separately detected and refuses the merge
outright, so the override cannot be dodged by relabelling.
Honest ceiling. preserve protects operator enrichment from accidental loss;
the override protects the enforcement plane from a shrink-based pin. But the
policy governs only content inside fences on a merge path — it is not a
general tamper detector, and it presupposes the fence boundaries are intact
(broader tamper is S22/S19's concern). The default is the safe one; allow
writes silently and should be chosen deliberately.
Bridge-refresh safety ✅ (policy) / ⚙ (operator Pre-Flight) {#S14}¶
--bridge-refresh unconditionally overwrites target entry files
(CLAUDE.md, .claude/*, goose AGENTS.md/.goosehints). Its origin is a
2026-05-27 incident where a refresh silently replaced user content,
recoverable only because the files were git-tracked. The destructiveness is the
reason the safety policy exists.
Choose the right verb: --bridge-check inspects only (always safe);
--bridge-merge re-renders only AGENTTEAMS-BRIDGE-fenced regions and skips
unfenced files (the correct default whenever the target has user content); a
bare --bridge-from creates missing entry files (neither check nor merge);
--bridge-refresh overwrites (destructive). Before any --bridge-refresh, a
four-check Pre-Flight §II must all pass (inventory present entry files; each
carries a bridge fence; working tree clean for them; each git-tracked); any
failure ⇒ use --bridge-merge. This is mirrored as orchestrator Rule 14.
A boundary a reviewer should note: privilege scoping is an explicit non-goal of bridging. A bridge carries no sandbox block, capability grant, or privilege profile; a bridged target needing confinement sets its own profile and regenerates natively.
Honest ceiling. The Pre-Flight is an operator procedure (⚙), not a code
gate — --bridge-refresh will still overwrite if invoked, so safety rests on
following the checks and preferring --bridge-merge. The recoverability that
saved the 2026-05-27 incident depended on git-tracking; check 4 exists precisely
because an untracked overwrite is unrecoverable. The policy makes the destructive
path visible and procedurally guarded; it does not make --bridge-refresh
non-destructive. Full command table and line numbers: Edition R, S14.
Sources for Part IV. agentteams/cli/security_gate.py;
agentteams/cli/generate.py; agentteams/cli/standalone_modes.py;
agentteams/security_refs.py; agentteams/cli/parser.py; agentteams/emit.py;
agentteams/fences.py; references/bridge-refresh-safety.md;
agentteams/templates/universal/orchestrator.template.md. Line-precise
provenance: SOURCES.md (S27).