Skip to content

feat(audit): real CBOR + EVM-calldata decode for the web audit view (#153)#194

Open
hanwencheng wants to merge 1 commit into
mainfrom
issue-153-audit-decode
Open

feat(audit): real CBOR + EVM-calldata decode for the web audit view (#153)#194
hanwencheng wants to merge 1 commit into
mainfrom
issue-153-audit-decode

Conversation

@hanwencheng
Copy link
Copy Markdown
Member

Summary

Resolves #153. Replaces the parent-control web app's mock audit decode (decodeCalldata) and placeholder contract addresses with real backend decode, end to end — the follow-up to #187 (which wired onboarding but left the step-9 audit view mocked).

Two things the user asked for: show the contract information and show (decode) the audit.

What landed

agentkeys-core — decode primitives

  • audit/calldata.rs — keccak selector() + a minimal head/tail ABI decoder + encoder for the four stage-1 contracts (CredentialAudit, SidecarRegistry, AgentKeysScope, K3EpochCounter). Real selectors (e.g. append = 0xc1bf0e32, registerAgentDevice = 0x9847ca95) pinned to cast ground truth so ABI drift fails CI. Supports bytes32/uint*/address/bool/bytes/bytes32[]; a trailing WebAuthn tuple is noted, not guessed.
  • audit/mod.rsAuditEnvelope::to_json() + decode_envelope_hex(). The agentkeys-core: AuditEnvelope v1 cross-language CBOR vector exporter #137 cross-language CBOR vectors are the decode fixtures (acceptance bullet 4).
  • chain_profile.rs + chain-profiles/heima.json — embedded deployed-contract registry (single source of truth, mirrors docs/spec/deployed-contracts.md).

agentkeys-daemon — endpoints

  • audit_decode.rs — decode one audit event into its CBOR AuditEnvelope (round-tripped through real canonical CBOR) and the on-chain calldata it commits (ABI-encoded from the action's real values, then decoded against the verified ABI — real bytes, not a fabricated view).
  • ui_bridge.rsGET /v1/chain/info + GET /v1/audit/:id/decode.

apps/parent-control — web UI

  • Client seam gains getChainInfo() + decodeAuditEvent().
  • ChainPage renders real addresses + explorer links; EventDecodeModal renders the decoded envelope + typed calldata args. Demo data kept as a clearly-labelled offline fallback.

Acceptance (#153)

Deliberate deviation

The issue suggested ethabi/alloy. I wrote a small, fully-tested decoder/encoder instead (only sha3 needed — no heavy dep), selectors pinned to cast ground truth + encode/decode round-trip tested. Equivalent correctness, far smaller footprint.

Test plan

  • cargo test -p agentkeys-core audit:: — calldata + envelope decode + agentkeys-core: AuditEnvelope v1 cross-language CBOR vector exporter #137 vectors (153+ pass)
  • cargo test -p agentkeys-daemon — audit_decode + endpoint wiring (66/15/15 pass)
  • cargo clippy -p agentkeys-core -p agentkeys-daemon --all-targets -- -D warnings — clean
  • cargo check --workspace — clean
  • apps/parent-control: tsc --noEmit — clean

🤖 Generated with Claude Code

@hanwencheng hanwencheng force-pushed the issue-153-audit-decode branch from ca34532 to f82334f Compare June 4, 2026 13:39
@hanwencheng
Copy link
Copy Markdown
Member Author

Codex adversarial review — applied

Ran /codex challenge (adversarial, high reasoning, ~1.4M tokens) against the diff. Net: one real bug I introduced, one real audit-honesty gap, plus a genuine src↔deploy divergence it surfaced. Two scary-sounding P1s were ruled out with evidence.

Fixed in f82334f1:

  • OOM/DoS guard (calldata.rs) — decode_bytes32_array now bounds-checks the array length against the remaining body before Vec::with_capacity, so a crafted length word can't drive a huge alloc. Regression test added (overflow + body-too-short paths).
  • Synthesized-decode labeling (audit_decode.rs + UI) — /v1/audit/:id/decode now returns synthesized: true + a provenance note, and the modal shows a preview banner. The CBOR/calldata shape is real (verified-ABI encode↔decode), but the source values are reconstructed from the audit row (no stored envelope/tx yet) — so derived hashes are never mistaken for stored on-chain evidence.
  • Forward-compat scope ABI (calldata.rs) — the registry now recognizes both the deployed setScopeWithWebauthn(...,tuple) / revokeScope(...,tuple) (what scripts/heima-scope-{set,revoke}.sh submit today) and the current-source setScope / revokeScope(bytes32,bytes32). codex was right that src/AgentKeysScope.sol has diverged from the deployment; switching outright would've broken live decode, so both are registered with a divergence note.
  • No fabricated tx link (UI) — dropped the synthetic txHash() explorer link; the modal links only to the real contract page.
  • Loud chain fallback (ui_bridge.rs) — warn! instead of silently serving Heima addresses on a bad $AGENTKEYS_CHAIN.

Real Heima explorer URLs wired (per operator): explorer.heima.network/address/{addr} for accounts, /contract/{addr} for contracts (new contract_url_template in the chain profile). Replaces the stale statescan.io links.

Ruled out (with evidence):

  • "Branch removes the master-self scope bypass" (cap.rs/verify.rs)not in this PR. git diff main swept it in because the local main ref was stale; jj diff + the 15-file PR list confirm those files aren't touched. It's the separately-merged feat(broker,worker): skip scope check for master-self (operator==actor) #195 ("skip scope check for master-self").
  • "Live scope txs will fail to decode" — false: the operator scripts submit the tuple form my registry already had; verified against heima-scope-set.sh.

Validation: core 157, daemon 66/15/15, tsc --noEmit clean, clippy -D warnings clean.

@hanwencheng hanwencheng force-pushed the issue-153-audit-decode branch from f82334f to d2ccefe Compare June 4, 2026 14:23
…153)

Replaces the parent-control web UI's decodeCalldata mock + placeholder contract
addresses with real backend decode, end to end.

Core decode (agentkeys-core):
- audit/calldata.rs: keccak selectors + minimal head/tail ABI decode/encode for
  the four stage-1 contracts; bounds-checked (no OOM); selectors pinned to cast.
- audit/mod.rs: AuditEnvelope::to_json() + decode_envelope_hex(); #137 vectors
  are the decode fixtures.
- chain_profile.rs + heima.json: embedded contract registry + real
  explorer.heima.network /address/ + /contract/ links.

Daemon: ui_bridge.rs GET /v1/chain/info + /v1/audit/:id/decode; audit_decode.rs
decodes envelope + calldata (encode→decode round-trip), labelled synthesized:true.

Web UI: client seam getChainInfo()/decodeAuditEvent(); ChainPage real addresses;
the audit decode is a dedicated page (audit feed → click event → `decode` view
with a back button + panels + preview banner), not a modal; no fabricated tx links.

Contract src↔deploy divergence (verified on-chain):
- DEPLOYED AgentKeysScope (prod 0xd44b375… + test 0x338d68…, both 4572B) is the
  stage-1 setScopeWithWebauthn(...,K11Assertion) design (sel 0x864ae93c /
  0x6f37dd80). src/AgentKeysScope.sol is the #164 ERC-4337 rewrite (setScope) —
  built by the deploy script but NOT yet deployed (idempotent skip; gated on the
  master-account cutover). #164's ERC-4337 infra (EntryPoint, factory) IS live.
  Live source preserved at archived/AgentKeysScope.deployed-stage1.sol; gap
  documented in deployed-contracts.md. calldata.rs decodes both forms with the
  real deployed selectors.

CI deploy fix: setup-broker-host.sh self-healing rust toolchain health gate —
verify rustc runs (repair once if not) before building. Fixes the test-EC2
'could not execute process rustc … No such file' failure. Idempotent.

Tests: core 157, daemon 66/15/15, clippy -D warnings clean, forge build clean,
bash -n clean. App tsc: changed files clean (the only 2 errors are the
gitignored, dev.sh-generated lib/wasm bindings absent in CI checkouts).
@hanwencheng hanwencheng force-pushed the issue-153-audit-decode branch from d2ccefe to 4cc1828 Compare June 4, 2026 14:42
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.

Audit decode: surface CBOR AuditEnvelope + EVM calldata decoding to the web UI

1 participant