Agent Permission Gates Research

Research snapshot: 2026-08-08. The source audits below are pinned to exact commits because these projects change quickly.

Executive conclusion

A best-in-class permission gate is not a dangerous-command regex and not an LLM safety prompt. It is a layered authorization system:

  1. Normalize the requested operation into typed capabilities.
  2. Apply deterministic policy with explicit trust tiers and monotonic denies.
  3. Ask for narrowly scoped consent when policy cannot decide.
  4. Enforce the resulting capability in an OS sandbox or constrained tool implementation.
  5. Revalidate immediately before the effect to reduce TOCTOU and symlink attacks.
  6. Record an append-only audit event.
  7. Optionally use an embedding model or judge as an advisory risk signal that can escalate to ask/deny but cannot widen authority.

The closest overall reference is Codex because it cleanly separates classification, approval, sandboxing, structured denial, escalation, and durable policy amendments. Gemini CLI has the richest TypeScript policy engine and useful shell-recursion behavior. OpenCode has strong request/UI plumbing but broader and more order-sensitive grants. Hermes has extensive practical command defenses and multi-surface parity, but command-string analysis and headless defaults leave gaps. Goose is the clearest example of an LLM permission judge, and also shows why a judge is not containment.

Threat model

The gate should assume:

  • The model can be mistaken or prompt-injected.
  • Tool names, descriptions, annotations, arguments, output, and MCP metadata can be attacker-controlled.
  • Repository files and project-local configuration can be malicious.
  • An apparently read-only executable can have write/exec hooks or dangerous flags.
  • Shell syntax can compose safe-looking fragments into a dangerous whole.
  • Filesystem paths can traverse symlinks or change after approval.
  • A process with network access can exfiltrate anything it can read.
  • An approval client may disconnect, race another client, replay a response, or answer a stale request.
  • “Approved” means the user accepts a described effect; it does not prove the process can perform only that effect.

The principal failure classes are:

  • Destructive filesystem or Git changes
  • Writes outside the intended workspace
  • Secret/credential access
  • External communication and exfiltration
  • Privilege escalation and sandbox escape
  • Persistence or security-control weakening
  • Arbitrary interpreter or downloaded-code execution
  • MCP/plugin supply-chain compromise
  • Confused-deputy and cross-session approval bugs
  • TOCTOU between preview, approval, and execution

Core distinction: authorization versus containment

A permission decision answers whether an operation may be attempted. A sandbox answers what the process can actually do. Both are required.

A confirmation dialog cannot stop an approved python -c process from reading unrelated files. A filesystem sandbox with unrestricted network still permits exfiltration of every readable file. A network allowlist does not prevent local deletion. The effective security boundary is the intersection of:

  • Tool implementation
  • Filesystem policy
  • Process/syscall policy
  • Network policy
  • Credential availability
  • Approval policy
  • Runtime identity and environment

Source snapshots

The repositories were shallow-cloned under /tmp/agent-permission-research/.

ProjectCommit
OpenCodefe82a1b6ca4f535beb973b0867017e3f639f85ed
OpenAI Codex2e3a1702c2e7adea5f2ae9ea2799c625024b4fda
Hermes Agentb3aa561faffd64f05436e429a6415d175e534ec9
Gemini CLIcf22ac7e86f3dcf528e3ae591fec1c03090a49f8
Goose66051ec7d2ea4d5c2701fca2aa762fcecf82a487

Comparative overview

SystemRule modelShell analysisApproval scopesContainmentHeadless defaultLearned judge
CodexStarlark exec/network rules plus heuristicsConservative decomposition; banned broad amendmentsOnce, tightly keyed session, durable amendmentSeatbelt; bwrap/seccomp; Windows restricted backends; network proxyReject interaction / NeverOptional guardian auto-review
Gemini CLIPriority-tier allow/ask/deny rules and safety checkersTree-sitter/shell parsing, recursive subcommands/wrappers, redirection downgradeOnce and persistent policySandbox manager, platform profilesDefault denyPluggable safety checkers
OpenCodeOrdered last-match-wins rulesTree-sitter Bash/PowerShell plus arity prefixesOnce and process-wide “always”Limited; permission layer is primaryReject unless explicit auto/yoloNo central judge
HermesHardline deny, user deny, mode, allowlist, patterns/Tirith, smart approvalExtensive normalized regex/pattern analysisOnce, session, permanent, YOLOPluggable local/container/remote backendsOften auto-approves outside explicit ask contextsOptional smart approval
GooseTool-name policy and inspection precedencePrimarily tool annotations/name/LLM judgmentOnce and durable tool-name-wideNo universal host sandboxApprove modes fail closed; Auto is unrestrictedLLM read-only classifier

OpenAI Codex

Strong design choices

Codex’s authoritative flow is in codex-rs/core/src/tools/orchestrator.rs:

  1. Classify the command.
  2. Determine approval requirements.
  3. Obtain approval if required.
  4. Select and apply the sandbox.
  5. Execute.
  6. Escalate only after a structured sandbox denial.
  7. Obtain separate authorization for escalation.
  8. Retry under the explicitly approved profile.

Important properties:

  • UnlessTrusted, OnRequest, Granular, and Never are distinct approval policies.
  • A prompt suppressed by policy becomes forbidden; it does not become allowed.
  • Every segment of a parsed compound command contributes to the aggregate decision.
  • Parser ambiguity disables reusable policy-amendment suggestions.
  • Broad prefixes such as shells, python -c, node -e, env, sudo, rm, and package script runners cannot become suggested permanent amendments.
  • Session approval keys include normalized command, environment, cwd, TTY, sandbox permissions, and additional permissions.
  • Persistent amendments are explicit and serialized to $CODEX_HOME/rules/default.rules.
  • Filesystem policy uses deny > write > read precedence.
  • Protected workspace paths include .git, .agents, and .codex.
  • Denied reads cannot be bypassed through sandbox escalation.
  • Headless requests that need interaction are rejected rather than approved.
  • macOS uses the absolute /usr/bin/sandbox-exec, avoiding PATH substitution.

Lessons

  • Make sandbox escape a separate capability from command execution.
  • Use structured sandbox-denial errors; do not infer denial from stderr text.
  • Let the server enumerate valid approval decisions so clients cannot invent broader grants.
  • Never propose durable grants for ambiguous parses or general interpreters.
  • Display when an escalation also removes network mediation.

Residual concerns

  • Unsupported systems may have no native sandbox; the UI must expose that fact.
  • Persistent prefix rules remain sensitive even with conservative suggestion logic.
  • Shell-specific lowering will always need adversarial tests as syntax evolves.

Gemini CLI

Strong design choices

Gemini’s packages/core/src/policy/policy-engine.ts is a useful TypeScript reference:

  • Policy sources occupy explicit priority bands: admin, user, workspace, extension, default.
  • Rules match tool identity, MCP identity, annotations, approval mode, subagent, arguments, and interactive status.
  • Shell commands are recursively decomposed.
  • A denied subcommand denies the aggregate command.
  • Redirection downgrades allow to ask unless explicitly permitted.
  • Parser failures fall back conservatively in normal modes.
  • Safety-checker errors fail closed.
  • External additional-permission paths downgrade allow to ask.
  • Noninteractive mode changes the default from ask to deny.
  • Trust paths are realpath-normalized and longest-match based.

The scheduler correctly runs argument-modifying hooks before policy evaluation, preventing the common bug where the user approves one input and a hook executes another.

Findings to avoid

  • Equal-priority conflicts are order-dependent rather than deny-wins.
  • Workspace policy integrity changes can be auto-accepted in some rollout configurations.
  • A client-initiated boolean can bypass asking; user gestures should instead be unforgeable, short-lived capabilities bound to exact arguments.
  • Persistent sandbox approval can widen read access into write access in the audited implementation.
  • YOLO permits parser failures unless an argument-restricted rule matched.

Lessons

  • Project trust must not imply permanent trust in future project policy contents.
  • Store a policy digest and require renewed approval when authority-expanding project policy changes.
  • Persist read and write capabilities separately and test round-trip equivalence.
  • Compare canonical effective path component depth, not raw string length.

OpenCode

Strong design choices

OpenCode has mature asynchronous request handling:

  • Pending state is inserted before the event is published.
  • Clients reconcile missed events by listing pending requests.
  • UI responses are deduplicated by request ID.
  • Generation counters prevent stale auto-accept list responses.
  • ACP serializes prompts per session while allowing different sessions to proceed concurrently.
  • Missing UI and transport failures reject.
  • Interactive “always” requires a second confirmation.
  • Subagents inherit parent denies and do not inherit ordinary parent allows.

Findings to avoid

  • Rule matching is last-match-wins. A later broad allow can override an earlier deny.
  • “Always” approvals are instance-wide across sessions until restart, despite session-oriented UX.
  • MCP persistent approval uses tool-wide *, without arguments or resource identity.
  • External-path analysis is heuristic and lexical.
  • Symlink and TOCTOU escapes remain possible.
  • Write preview occurs before approval, then writes without compare-and-swap revalidation.
  • Unknown commands can receive broad executable-prefix grants.

Lessons

  • Bind approval to an immutable operation digest.
  • For writes, include expected previous content/inode hash and proposed bytes, then compare-and-swap.
  • Canonicalize existing components and use descriptor-relative safe-open APIs where available.
  • Give every grant a visible scope: call, session, project, process, or durable user policy.
  • Make reply resolution atomic, first-response-wins, and stale-safe.

Hermes Agent

Strong design choices

Hermes centralizes several surfaces into one decision core:

  1. Hardline unconditional deny
  2. Password-guessing deny
  3. User deny patterns
  4. YOLO/off mode
  5. Permanent allowlist
  6. Surface/headless policy
  7. Pattern and optional Tirith classification
  8. Smart or human approval

Other positives:

  • Process/session identity uses task-local ContextVars rather than mutable environment variables.
  • Process-level YOLO is frozen at import to prevent in-process mutation.
  • CLI prompts support once/session/always/deny and timeout-as-denial.
  • Sensitive instruction files such as AGENTS.md are separately protected, including symlink resolution, and ignore YOLO/persistent approvals.
  • Docker is only considered isolated when host bind mounts are absent.
  • Gateway user pairing has expiry, rate limits, lockout, random codes, and 0600 persistence.

Findings to avoid

  • Some noninteractive contexts auto-approve by default.
  • Interactive execute_code can use direct Python filesystem/process APIs outside its RPC tool allowlist.
  • Regex/normalized string classification cannot fully model shell semantics.
  • Smart approval puts another LLM in the authorization boundary.
  • Permanent grants are more pattern-grained than resource-grained.
  • Isolation trust depends on backend configuration labels being accurate.

Lessons

  • Test direct APIs, not just wrapped tools.
  • Attest backend properties and expose backend-specific guarantees.
  • Keep hard denies below every bypass mode.
  • Use a single decision core with TUI/RPC/channel adapters.

Goose

Permission judge

Goose’s permission_judge.rs is a valuable pattern for an advisory model:

  • Tool request IDs, names, and arguments are explicitly labeled untrusted.
  • Untrusted request data is placed in a user message, not interpolated into the system prompt.
  • The model must return a structured tool call containing read-only request IDs.
  • Ambiguity or attempted instruction injection means “not read-only.”
  • Provider failures and malformed outputs return no approvals.

This is a good escalation classifier, not a security boundary.

Findings to avoid

  • SmartApprove trusts MCP readOnlyHint=true too strongly.
  • Persistent rules are tool-name-wide and argument-independent.
  • Filesystem tools permit unrestricted absolute and parent-relative paths.
  • Flatpak shell execution deliberately escapes to the host.
  • Auto mode bypasses approvals; some Codex-provider paths also pass --yolo.
  • The more precise argument-hashed permission store appears unused.

Lessons

  • Treat MCP annotations as untrusted hints unless server identity and implementation are trusted.
  • Key grants to server identity, tool schema hash/version, normalized arguments/resources, and operation class.
  • An annotation may increase caution but should not independently suppress approval.

Other ecosystem patterns

Claude Code

Claude Code’s ordered allow/ask/deny rules, managed settings, permission modes, and optional OS sandbox are important references. dontAsk is a good headless semantic: deny requests that are not already authorized. bypassPermissions is an explicit unsafe mode. Project settings may request capabilities, but managed policy can constrain them.

Cline and Roo Code

These systems provide accessible, granular auto-approval categories and Plan/Act modes. Their main limitation is that execution usually inherits the host VS Code process/terminal authority. Plan mode is only a security boundary if mutating tool handles are technically removed.

Aider

Aider’s Git commits are excellent for recoverability and tracked-file audit, but do not cover secret reads, network actions, untracked files, or external systems. --yes-always turns confirmations into ambient authority.

OpenHands and Open Interpreter

OpenHands commonly uses a container runtime and can classify commands, but documentation warns that its runtime is not a complete security boundary. Open Interpreter executes on the host unless the operator supplies isolation; auto-run is a large trust escalation.

MCP implications

MCP annotations currently include:

  • readOnlyHint
  • destructiveHint
  • idempotentHint
  • openWorldHint

Their defaults are intentionally pessimistic, and the specification treats them as hints, not guarantees. An untrusted server may lie. A Pi gate should use annotations for UX and additional caution, never as proof.

The combined session matters more than one tool. The “lethal trifecta” is:

  1. Access to private data
  2. Exposure to untrusted content
  3. Ability to communicate externally

If all three become available in one tainted execution path, require explicit approval or prohibit the combination.

Relevant primary references:

Do not authorize broad tool names when a narrower operation can be derived.

fs.read(path-set)
fs.write(path-set, expected-version)
fs.delete(path-set)
process.exec(executable-identity, argv-constraints, cwd-set)
process.shell(command-ast)              # intrinsically elevated
network.connect(host-set, port-set, protocol)
secrets.read(secret-id)
mcp.invoke(server-identity, tool-schema-hash, argument-constraints)
browser.navigate(origin-set)
git.mutate(repository-identity)
sandbox.escape(profile-delta)           # never implicit
policy.modify(policy-id, old-hash, new-hash)

Each request should also include:

  • Session and workspace identity
  • Tool source/provenance
  • Model/agent identity
  • Raw and normalized arguments
  • Current sandbox profile
  • Whether untrusted content influenced the request
  • Requested grant scope and expiry
  • Request nonce and operation digest

Deterministic policy model

Trust tiers

Recommended precedence:

  1. Administrator/managed deny
  2. User deny
  3. Repository-requested restriction
  4. Ephemeral exact user grant
  5. Durable constrained user grant
  6. Administrator allow within the enforced ceiling
  7. Otherwise ask interactively or deny headlessly

Within the same tier use deny > ask > allow, then specificity, then stable source order. Reject irreconcilable same-tier conflicts instead of silently relying on load order.

A lower-trust source may reduce authority or request authority. It must never grant itself authority.

Grant scopes

  • Once: one nonce-bound operation digest
  • Batch: exact sibling calls from one model response
  • Task/session: expires at session end; bounded use count
  • Workspace snapshot: canonical repository identity plus policy/schema digest
  • User/global: only narrow capabilities
  • Managed: read-only to ordinary users; signed or otherwise integrity-protected

Never persist:

  • General shell/interpreter approval
  • Sandbox escape
  • Unknown MCP server identity
  • Unrestricted network access
  • Secret-read plus open-world communication combinations
  • Authority derived only from project-controlled configuration

Command analysis

A serious shell gate must parse, not split strings or match prefixes.

Extract:

  • Pipelines, sequences, conditional operators, subshells, background jobs
  • Redirections, process substitution, command substitution, heredocs
  • Environment assignments and PATH changes
  • Wrapper/interpreter payloads (sh -c, python -c, node -e, PowerShell)
  • Executable identity, not just basename
  • Working directory
  • Declared and inferred filesystem targets
  • Network destinations
  • Transitive execution paths such as Git hooks, npm lifecycle scripts, Makefiles, package runners, and repository binaries

Decision rules:

  • Any denied subcommand denies the aggregate.
  • Any ambiguous/dynamic construct prevents automatic allow.
  • Redirection cannot inherit read-only status.
  • Safe executable names must have argument-aware rules.
  • Parser failure means ask interactively or deny headlessly.
  • Approval of a compound command covers only the exact normalized AST digest.

Static command analysis is necessarily incomplete. The sandbox remains authoritative.

Filesystem safety

  • Resolve relative paths against the actual execution cwd.
  • Canonicalize all existing path components.
  • Distinguish lexical path, canonical path, and final intended target.
  • Protect policy/configuration, .git, agent instructions, credentials, sockets, devices, and system paths.
  • Use descriptor-relative APIs such as openat/openat2 where available.
  • For writes, bind approval to previous inode/content/version and proposed content hash.
  • Revalidate after approval and immediately before commit.
  • Use per-target mutation queues or compare-and-swap semantics.
  • Handle new files where final realpath is unavailable by safely opening the canonical parent.

Pi’s withFileMutationQueue() helps serialize custom tool mutations, but it is not by itself authorization or symlink containment.

Network safety

Network policy must cover:

  • DNS name and resolved IP
  • Redirect destinations
  • Port and protocol
  • HTTP proxy and upstream proxy behavior
  • Loopback, link-local, private ranges, cloud metadata endpoints
  • Unix sockets
  • Local listening/binding
  • DNS rebinding and resolution changes

Deny should beat allow. Prefer allowlist-first rules. If hostile DNS is in scope, enforce destination policy in the actual transport/proxy rather than only before connection.

Approval UX

The dialog should show independently derived facts, not model justification:

  • Risk level and capability classes
  • Exact executable path or tool/server identity
  • Command/arguments and cwd
  • Files potentially read, written, or deleted
  • Network destinations
  • Sandbox permissions before and after the operation
  • Matched policy rule and source
  • Whether untrusted content influenced the request
  • Grant scope, expiry, use count, and schema/policy digest

Choices should be generated by the policy engine:

  • Deny once
  • Deny and remember at a safe scope
  • Allow once
  • Allow for this session, if eligible
  • Allow constrained pattern, if eligible
  • Inspect full details

“Always allow” should never be the default selection and should require an explicit second confirmation showing the exact persisted rule.

Timeout, closed UI, malformed RPC, unknown reply, and no-UI conditions must fail closed.

Audit design

Record structured append-only events for:

  • Raw request hash and normalized operation
  • Provenance and session/workspace identity
  • Risk signals and classifier versions
  • Every matched rule and the winning decision
  • Human identity and response
  • Grant creation/revocation/expiry
  • Sandbox backend and effective profile
  • Execution result and structured denial
  • Observed filesystem/network effects where available

Redact secrets but retain stable hashes/identities useful for investigation. Hash chaining detects local deletion or rewriting only if the chain head is anchored somewhere the agent cannot rewrite; optional remote export provides stronger tamper evidence.

Embeddings and learned classification

Bottom line

An embedding or classifier can be useful, but it must not be the root of trust. Recommended monotonicity:

  • A model may change allow → ask.
  • A model may change ask → deny.
  • A model must not change deny → ask/allow.
  • Initially, a model should not change ask → allow.

Best uses for embeddings

  1. Retrieve similar previously reviewed operations and policy examples.
  2. Detect novelty/out-of-distribution requests and force confirmation.
  3. Cluster audit logs to find missing deterministic rules.
  4. Suggest a constrained rule for human review.
  5. Rank risk explanations, not make the final decision.

Embedding cosine similarity is not a calibrated probability of safety.

Candidate models

  • all-MiniLM-L6-v2: small, common, 384-dimensional general embedding baseline.
  • bge-small-en-v1.5: compact alternative worth benchmarking.
  • CmdCaliper: command-line semantic embedding research; relevant for command equivalence retrieval.
  • keeper-security/threat-category-classifier-experimental: experimental CLI risk classifier using sentence-transformer features, an LSTM, and engineered features; useful as a research baseline, not production authority.
  • paiml/shell-safety-classifier: extremely small shell classifier, but its reported multiclass validation accuracy and severe class imbalance make it unsuitable as an authorization boundary.

Generic CodeBERT, NLI, Llama Guard, and small coder LLMs are not directly trained for tool authorization. They may be useful experiments, but require task-specific calibration and adversarial evaluation.

Stronger learned architecture

After collecting enough reviewed data:

  1. Parse the operation deterministically.
  2. Produce structured features: capability classes, path sensitivity, reversibility, privilege, shell complexity, network openness, provenance, and sandbox delta.
  3. Embed a canonical textual representation for nearest-neighbor retrieval.
  4. Fine-tune a small cross-encoder/classifier on (operation, policy/risk statement) pairs.
  5. Calibrate after quantization/deployment.
  6. Use out-of-distribution distance and model/rule disagreement as abstention triggers.
  7. Keep deterministic ceilings authoritative.

A generative judge should receive fixed-schema untrusted JSON, have no tools, see no unnecessary secrets, and return a validated enum/schema. Goose’s permission judge is a good input-separation pattern. Codex’s guardian is a stronger example of routing only already-escalated approval requests through automatic review.

Evaluation metrics

Ordinary accuracy is misleading. Track:

  • Dangerous false-negative rate with confidence bounds
  • Recall by destructive write, secret access, privilege escalation, persistence, arbitrary execution, and exfiltration
  • False-confirm and false-block rates for normal coding work
  • Risk-coverage curves as abstention changes
  • Performance on unseen binaries and operation families
  • Parser/model disagreement
  • Adversarial transformation robustness
  • End-to-end bypass rate in a disposable sandbox

Useful benchmark material:

Build a local matrix across operation × resource sensitivity × scope × privilege × reversibility × network destination × shell obfuscation. Split by semantic template and executable family, not random command lines, to avoid leakage.

Dynamic least privilege

A promising research direction is to expose only the tools required for the current step. AgenTRIM combines a verified tool inventory, low/high-risk partitioning, adaptive tool filtering, and status-aware validation. This reduces the available attack surface before a tool call occurs.

Pi already supports pi.setActiveTools(). A permission extension could:

  • Keep read-only, closed-world tools active by default.
  • Require explicit task/session capability activation for mutating/open-world tools.
  • Never rely on removal alone as enforcement; still intercept calls and sandbox execution.
  • Include tool source/schema identity in the activation decision.

Pi extension integration

Pi provides the key interception seam:

pi.on("tool_call", async (event, ctx) => {
  // event.toolName, event.toolCallId, mutable event.input
  // return { block: true, reason, terminate? }
})

Relevant Pi properties:

  • tool_call runs before execution and can block.
  • In parallel mode, sibling calls are preflighted sequentially and then execute concurrently.
  • Input mutations are seen by later handlers and affect execution.
  • There is no revalidation after a handler mutates input.
  • Handler errors fail safe by blocking the call.
  • ctx.hasUI distinguishes prompt-capable from headless modes.
  • ctx.ui.custom() can build a detailed approval overlay in TUI mode.
  • RPC mode supports ordinary dialogs but not custom TUI components.
  • JSON/print modes have no UI.
  • Built-in tools can be overridden to enforce constrained operations and preserve built-in renderers.
  • pi.getAllTools() exposes tool provenance metadata and schemas for inventory hashing.
  • pi.setActiveTools() supports dynamic least-privilege exposure.
  • The sandbox example uses @anthropic-ai/sandbox-runtime with Seatbelt on macOS and bubblewrap on Linux.
  • project_trust can mediate whether project-local extensions/configuration load.

Important Pi limitations

A tool_call handler alone cannot guarantee containment:

  • Another extension handler may mutate arguments after this gate if load order permits.
  • A custom tool may perform arbitrary effects unrelated to its declared arguments.
  • Path checks before execution remain vulnerable to TOCTOU unless the execution implementation cooperates.
  • User !/!! shell commands use the separate user_bash event.
  • Project trust and session lifecycle actions are separate event families.
  • JSON/print mode cannot ask the user.

For the strongest implementation, combine:

  1. A final-order tool_call authorization handler
  2. Overrides/wrappers for built-in bash, write, and edit
  3. user_bash interception
  4. OS sandboxing for shell execution
  5. Protected-path enforcement at actual file-open/write time
  6. Tool inventory/schema hashing at session start and reload
  7. Explicit headless policy
  8. A detailed TUI/RPC approval adapter

Proposed architecture for the Pi extension

src/
  index.ts
  inventory.ts               # source/schema identity and reload diff
  normalize/
    tool-call.ts
    shell.ts
    path.ts
    network.ts
  policy/
    types.ts
    engine.ts
    precedence.ts
    loader.ts
    integrity.ts
  grants/
    store.ts
    scope.ts
    digest.ts
  risk/
    deterministic.ts
    embeddings.ts            # optional retrieval/OOD
    judge.ts                 # optional advisory reviewer
  enforcement/
    bash.ts
    files.ts
    sandbox.ts
    user-bash.ts
  ui/
    approval.ts
    settings.ts
  audit/
    log.ts
    redaction.ts
    chain.ts
  tests/
    policy-matrix.test.ts
    shell-adversarial.test.ts
    filesystem-races.test.ts
    headless.test.ts
    concurrent-approval.test.ts

Decision state machine

raw tool call
  → resolve tool identity/schema/source
  → normalize typed operation(s)
  → compute operation digest
  → immutable hard denies
  → trust-tier policy evaluation
  → optional advisory risk escalation
  → allow | deny | ask
  → exact-scope approval
  → revalidate digest/resource state
  → materialize sandbox capability profile
  → execute
  → structured result/denial
  → separately authorize any escalation
  → append audit event

Implementation phases

Phase 1: deterministic gate

  • Typed policy schema and explicit precedence
  • Built-in tool normalization
  • Conservative shell parser
  • Canonical protected-path rules
  • Once/session grants
  • Fail-closed headless behavior
  • Basic detailed approval UI
  • Structured audit events
  • Adversarial unit tests

Phase 2: containment

  • Sandboxed Bash override
  • Filesystem enforcement wrappers
  • Network domain/IP/socket policy
  • Structured sandbox denials
  • Separately authorized escalation
  • Backend capability reporting

Phase 3: persistence and ecosystem

  • Atomic durable grant store with restrictive permissions
  • Project policy hash/integrity approval
  • MCP/server/tool/schema identities
  • Policy management UI and revocation
  • Multi-client atomic reply handling

Phase 4: learned advisory layer

  • Embedding-based reviewed-example retrieval
  • OOD/novelty-triggered confirmation
  • Human-reviewed rule suggestions
  • Optional local or provider judge
  • Calibration and adversarial benchmark suite

Security invariants

  1. No lower-trust policy source can increase authority.
  2. Deny is monotonic across inspectors and subagents.
  3. Missing UI never implies consent.
  4. Parser failure never produces automatic allow.
  5. A model signal never widens deterministic authority.
  6. Approval and containment are separate decisions.
  7. Sandbox escape is always explicit and separately described.
  8. Durable grants never widen capability during serialization/deserialization.
  9. Every approval is bound to an operation digest and visible scope.
  10. Filesystem state is revalidated after approval.
  11. MCP annotations are untrusted unless independently attested.
  12. The UI explanation is derived from normalized effects, not model prose.
  13. Policy/config/instruction files are protected through every write path.
  14. Headless execution requires an explicit noninteractive policy.
  15. Audit logging never records raw secrets.

Validation plan

  • Rule precedence and conflict tests
  • Policy integrity and project self-grant tests
  • Shell composition, wrappers, substitutions, redirects, heredocs, Unicode, encoding, aliases, and PATH shadowing
  • Git hooks, npm lifecycle scripts, Makefiles, and repository executables
  • Relative paths, .., symlinks, dangling symlinks, path replacement, race swaps, and content changes during approval
  • Network redirects, DNS failure/rebinding, private IPs, metadata endpoints, proxies, loopback, listeners, and Unix sockets
  • MCP identity/schema changes and lying annotations
  • Duplicate IDs, stale replies, cross-session isolation, timeout, disconnect, reload, and restart
  • TUI, RPC, JSON, print, and user-bash parity
  • Sandbox unavailable/misconfigured behavior on macOS, Linux, and Windows
  • Classifier timeout, malformed output, injection, disagreement, OOD, and unavailable-model behavior

Implement the deterministic capability engine and sandbox boundary before adding embeddings. Instrument approval decisions from the beginning so real, reviewed data can later train or evaluate a learned advisor. An embedding prototype can be built concurrently, but it should initially provide only:

  • Similar reviewed examples
  • Novelty score
  • Suggested risk labels
  • “Ask” escalation

It should not auto-allow operations until a deployment-specific evaluation demonstrates an acceptably low dangerous false-negative rate—and even then only inside deterministic capability ceilings and sandbox containment.