Gated runs v1: PreToolUse enforcement hook + policy engine - #3
Merged
Conversation
Implements the locked policy of 2026-07-22: gate self-modification from day one, warn everything else for a calibration week. Policy table, fail posture, and approval flow per the spec; rule logic lives in code and policy.json sets modes only. - src/policy/: DEFAULT_POLICY, loadPolicy (writes the default on first use, falls back without overwriting on malformed input), evaluate with first-match-wins ordering, exported per-rule matchers. - bin/hook-pre-tool-use.js: exit 2 blocks, exit 0 allows. Fail-open on engine error (with an engine-error receipt for visibility), fail-closed at the token layer. Tokens live in <home>/pending-approvals/; the first valid one is consumed via gatedAction (nonce recorded exactly once), invalid tokens are deleted so they cannot re-fire, and every denial prints the exact canonicalized request the owner must sign. - views: POLICY WARNINGS block in the morning-after summary. - KNOWN-LIMITS 11: the gate matches patterns and patterns are evadable; v1 threat model is the honest agent, not an adversarial one. Tests: 172 passing (96 baseline + 69 policy unit + 7 hook integration). Token path proven by falsification: with verification disabled the replay test approves when it must deny; restored, all pass. Note for the record: the executor ran one hook invocation against the real store during its own verification (self-disclosed), leaving a true policy-warn receipt at seq 2 and the default policy.json. The chain verifies; the receipt stays, because it records something that actually happened. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Turns "gated runs" from a convention into a mode. Follows #1 (install readiness, session receipts) and #2 (subsessions).
Why a hook and not just the MCP tool
gated_actionis a tool the agent chooses to call. UnderbypassPermissionsnothing stops a model from skipping it and callingBashdirectly, which makes it a behavioral mitigation.PreToolUsesits below the agent's choice and can actually block (exit 2 denies the call;SessionEndcannot). The two compose: the hook is the enforcement backstop,gated_actionremains the request path.The gate engine is reused, not rebuilt.
verifyApprovalandgatedActioninsrc/gate/already do token binding, canonicalization, nonce replay protection and dual-direction receipts. The hook is an adapter over the same engine.The policy
Locked before the build, so the enforcement point knew what it was enforcing. Sized to a budget of roughly 0-5 signings on a normal day, because a gate that gets turned off is worse than no gate.
git push --forcegh pr merge,npm publish, releasesSelf-modification gates from day one because a gate that cannot protect itself is theater. Everything else starts in warn (receipt written, call allowed) for a calibration week, then promotes on the evidence. Feature-branch pushes, reads, in-repo edits, tests and local commits are unmatched and flow free.
Rule logic lives in code;
<LOTOR_HOME>/policy.jsononly sets modes per rule. Editing that file is itself the self-mod rule.Fail posture
engine-errorreceipt, exit 0. A Lotor bug must not brick every tool call.Verification
Honesty
KNOWN-LIMITSgains item 11: the gate matches patterns and patterns are evadable. v1's threat model is the honest agent making a consequential move without sign-off, not an adversarial model engineering around the matcher. Also documented: engine errors fail open by design, a baregit pushto a checked-out protected branch is not detected, and hook registration lives in the user's settings file so a hostile edit between sessions is only caught at the next session's snapshot.Activation
The hook is not registered by this PR. Registration is a
PreToolUseblock in the user's ownsettings.json, which is consistent with self-mod being rule 1.