The views

Guard

The authority envelope and how decisions are made.

Press 6. Optimize decides what Claude can see; Guard decides what it can do. Guard runs as a Claude Code PreToolUse hook, which means it returns a decision before the tool executes rather than reporting on it afterwards.

Guard: the authority envelope for this session and the decision ledger

The authority envelope

The envelope is the task's authority. It is stored outside the conversation, at ~/.iris/projects/<id>/sessions/authority.json, for one reason: the model must not be able to widen its own permissions by writing convincing text. It may propose an envelope; a proposal that grants more than the current one is rejected.

{
  "scope": {
    "filesystem":     ["$PROJECT/**"],
    "environments":   ["staging"],
    "network":        [],
    "destructive":    "ask",
    "production":     "deny",
    "credentials":    ["staging"],
    "externalWrites": "ask"
  }
}

Those are the defaults, written by init if no envelope exists yet. The path can be overridden with IRIS_ENVELOPE_PATH; the hook, the UI and the server all read the same file, so there is exactly one source of truth.

Step 1 — normalize the effect, not the string

Matching on command text is how you end up denying echo "the word production appears in this string". Iris instead compiles the tool call into a structured effect:

{ effect: "delete", resourceType: "cloud", service: "aws",
  environment: "production", external: true, destructive: true,
  reversible: false, credential: "production", unknown: false }

Before classifying, it strips the parts of a command that are data rather than targets — heredoc bodies, comments, and prose inside quotes — so a deployment keyword in a log line is not read as a deployment target.

Dedicated recognizers cover filesystem tools, Railway, AWS, git, npm/yarn/pnpm publish and install, databases (psql, mysql, mongosh, redis-cli), curl/wget, and rm. Anything else falls to a read-only allowlist of 58 inspection commands (ls, grep, cat, head, find, jq, awk, stat …).

Why an allowlist, not a denylist

A denylist can only catch the shapes it has been taught, and silently allows the rest. So npx some-unknown-cli is not "probably fine" — it is unknown: true, and unknown means ASK.

The allowlist revokes itself when a reader is turned into a writer: sed -i, find -delete, any > redirect, or any $(…) command substitution drops the command out of read-only.

Step 2 — evaluate against the envelope

evaluate() walks a fixed ladder and returns the first match. Order matters: a hard deny is checked before anything can be explicitly allowed.

#RungFires on
1Hard denyproduction env · production credentials · denied destructive · denied external writes
2Scope violationpath outside $PROJECT · env not in scope · host not in allowlist
3Authority expansionstaging-scoped session reaching for production · credential discovery beyond the envelope
4High consequenceunknown effect · destructive · external write · credential use → ASK
5Explicit allowin-project non-destructive file ops · known-safe local commands
6DefaultASK

Step 3 — what that produces

Real output against the default envelope:

Tool callDecisionRule
Read src/guard/policy.mjsALLOWexplicit-allow.in-scope
Edit src/guard/effects.mjsALLOWexplicit-allow.in-scope
npm testALLOWexplicit-allow.in-scope
git status --shortALLOWexplicit-allow.in-scope
rm -rf ./buildASKhigh-consequence.destructive
npx unknown-cli --wipeASKhigh-consequence.unknown
git push origin mainASKhigh-consequence.external-writes
Write /Users/dev/.ssh/configDENYscope.filesystem
npm publish --access publicDENYhard-deny.production
aws s3 rm s3://acme-prod-assets --recursiveDENYhard-deny.production
psql postgres://prod-db.internal/app -c "DROP TABLE users"DENYhard-deny.production

Note what is not denied. git push origin main asks rather than blocks — a branch name is not an environment, and treating it as one made every ordinary push a "production deploy". npm test allows — classifying it as an install made it prompt on every run. A guardrail that cries wolf gets switched off.

Step 4 — trajectory signals

Single calls are not the whole risk. A session that reads, fails, retries, then reaches for a production credential is a pattern, and Guard tracks it across the session.

SignalFires when
AUTHORITY_EXPANSIONenvironment escalates (staging → production)
CONSEQUENCE_ESCALATIONread/write turns into destructive
CREDENTIAL_SCOPE_CHANGEcredential scope widens, or a secret is discovered after a failure
RECOVERY_ESCALATIONrecovery depth ≥ 2 while effects keep escalating
PRODUCTION_DESTRUCTIVEdestructive effect aimed at production

What happens when policy cannot load

Missing envelope, unreadable envelope, malformed JSON, or an unclassifiable command all resolve to ASK or stricter. There is no path through the hook that produces a silent allow, and every decision — including the failure — is written to the ledger.

Known limitations

Guard is not a sandbox

It is another boundary around the agent, strongest on filesystem scope and production access. It should not be the only protection standing between an agent and irreversible infrastructure.