Skip to content

Harden the agent guardrail: default-deny on every real-value chain (#76) - #104

Merged
hellno merged 2 commits into
mainfrom
hellno/issue-76-context-autoplan
Jun 20, 2026
Merged

Harden the agent guardrail: default-deny on every real-value chain (#76)#104
hellno merged 2 commits into
mainfrom
hellno/issue-76-context-autoplan

Conversation

@hellno

@hellno hellno commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Closes #76.

What

Inverts the signer daemon's auto-approval guardrail from fail-open ("armed only on chain_id == 1") to fail-safe default-deny ("armed on every chain EXCEPT an explicit exempt testnet/dev allowlist {Sepolia 11155111, anvil 31337}"). An unknown chain-id is treated as real-value and guarded, so configuring a new real chain (Base 8453, OP 10, …) can never silently turn the brake off. The safety property no longer depends on remembering to special-case each new chain.

Guard-shape change only: no policy fields, no new deps.

Changes

  • daemon.rs — module-scope TESTNET_FORK_CHAIN_IDS + is_testnet_or_fork; mainnet_guardrail_activeguardrail_active (chain- and origin-agnostic — ProposalOrigin is display-only, so it guards app + agent proposals equally).
  • config.rsmainnet_overrideautonomy_override. The env var DECKARD_I_KNOW_THIS_IS_MAINNET is kept unchanged (fewer env vars), but now disarms the guardrail on any real-value chain; its broadened meaning is documented in THREAT-MODEL.md only and never echoed to the agent.
  • Tests (tests/guardrail.rs + a daemon unit module):
    • within-cap on Base 8453 and an unknown idNeedsApproval in every ApprovalMode (acceptance pin)
    • exec-level: execute is refused (not_approved) on Base before any human resolve — pins the actual brake, not just the label
    • structural pin: 1 / 8453 / 10 / 42161 are never on the exempt list (catches a future fat-finger)
    • exempt testnets (Sepolia) stay hands-free; override restores auto-allow on both mainnet and Base
  • Renamed the now-inaccurate "mainnet guardrail" strings, including the client-visible deckard-wallet-client/src/failure.rs, to "auto-approval guardrail".
  • Docs broadened (README, THREAT-MODEL, CONTRIBUTING, STATUS, agent docs). Honesty kept: on the exempt testnet/dev ids within-cap auto-allow still happens by design (the demo), the default policy is still OverCap, and THREAT-MODEL residual-risk feat(contract): deckard-contract crate + virtual Cargo workspace (freeze-first wire) #3 is marked narrowed, not resolved.

Review

Produced via /autoplan (CEO + Eng dual-voice: Claude subagent + Codex). Both phases converged; their findings are folded in (dropped a speculative 1337 exempt id, fixed a broken intra-doc link that would have been a -D warnings CI red, added the exec + structural tests, widened the doc sweep, doc-honesty about the OverCap default).

Two items deferred to follow-up issues (out of #76's guard-shape scope): making ApprovalMode::Always the default policy, and reconsidering the override env var's name/necessity.

Definition of done

  • cargo fmt --all --check clean
  • just check green (clippy -D warnings, default + --features tray)
  • cargo test --workspace green — 282 passed, 0 failed (2 ignored = shield_e2e, needs a live Sepolia RPC)
  • No new/changed deps

hellno added 2 commits June 20, 2026 16:00
Invert the signer daemon's auto-approval guardrail from "armed only on
chain_id == 1" (fail-open) to "armed on every chain except an explicit
exempt testnet/dev allowlist {Sepolia 11155111, anvil 31337}" (fail-safe).
An unknown chain-id is treated as real-value and guarded, so configuring a
new real chain (Base, OP, ...) can never silently turn the brake off.

- daemon.rs: module-scope TESTNET_FORK_CHAIN_IDS + is_testnet_or_fork;
  mainnet_guardrail_active -> guardrail_active (chain- and origin-agnostic).
- config.rs: mainnet_override -> autonomy_override; env var
  DECKARD_I_KNOW_THIS_IS_MAINNET kept, now disarms on any real-value chain
  (documented in THREAT-MODEL.md only, never echoed to the agent).
- tests: pin within-cap on Base 8453 and an unknown id -> NeedsApproval; an
  exec-level test that execute is refused (not_approved) on Base before any
  human resolve; a structural pin that 1/8453/10/42161 are never exempt.
- rename the now-inaccurate "mainnet guardrail" strings (client-visible
  failure.rs, rpc/app comments) to "auto-approval guardrail".

Guard-shape only: no policy fields, no new deps.
README/THREAT-MODEL/CONTRIBUTING/STATUS + agent docs said the guardrail
downgrades auto-allow "on chain 1 / on mainnet". After #76 it is default-deny
on every real-value chain, so update the claim. Keep it honest: on the exempt
testnet/dev ids within-cap auto-allow still happens by design (the demo runs
hands-free), the default policy is still OverCap, and THREAT-MODEL residual
risk #3 is narrowed, not resolved.
@hellno
hellno merged commit 606ad65 into main Jun 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Harden the agent guardrail: fire on every real-value chain by default, not just chain_id == 1

1 participant