Skip to content

Put the policy-swap story in the web console, and stop masking failed runs - #27

Merged
imran-siddique merged 1 commit into
mainfrom
feat/web-console-policy-tamper
Jul 27, 2026
Merged

Put the policy-swap story in the web console, and stop masking failed runs#27
imran-siddique merged 1 commit into
mainfrom
feat/web-console-policy-tamper

Conversation

@imran-siddique

Copy link
Copy Markdown
Contributor

Why

The console showed enforcement and proof, but not tampering — which is the whole argument for why the proof has to exist. That story lived in a separate terminal demo (demo-02-policy-swap), so nobody saw both together.

What

A policy bundle selector on the Assessment tab:

  • Approved — the example's bundle, unchanged
  • Tampered — a copy generated at runtime with two edits an operator who wanted a loan booked would actually make: the delegated-authority ceiling raised from €500k to €5m, and the concentration guardrail inverted so it never fires

Run Nordwind Logistik AG (€750k) under each:

Bundle Step 6, the write Verify vs approved hash
Approved 403 denied — EBA/GL/2020/06 policy_bundle.hash PASS
Tampered 200 allow — the write lands policy_bundle.hash FAIL

cmcp verify is now always pinned to the approved bundle hash, so a tampered record fails and no edit to the record can make the two agree.

The tampered bundle is generated from the approved one at runtime, so it cannot drift from the submodule. If the example's policy changes shape, build_tampered() raises rather than silently producing a no-op tamper.

The gateway measures its bundle at startup, so the tampered bundle needs its own process: it starts lazily on :8444 on first use and stops with the console. The approved gateway on :8443 is untouched, so this does not change the launcher's process ownership.

Bugs fixed along the way

  • A crashed or partial run rendered as a green "Assessment recorded" banner. The UI inferred success from the absence of a denied write, so an empty step list read as a pass. Hit this for real: with CMCP_BEARER_TOKEN set in the environment, the gateway required auth the example agent does not send, the agent crashed at step 1, and the console reported a clean success. The API now returns the exit code and output tail, and the UI renders it as a failure.
  • A tampered-but-allowed write was styled the same green as a clean pass. Now an amber warning.
  • demo-02-policy-swap printed two different hashes both labelled "v1", three lines apart — step 0 showed the demo's own policies-v1 bundle, step 1 read the hash off demo-01's claim, which is a different bundle. Step 1 now computes it from the same bundle step 5 pins.

Testing

Ran end to end on Windows, software-only TEE:

  • approved + Nordwind → step 6 deny, verify policy_bundle.hash PASS
  • tampered + Nordwind → step 6 allow, verify policy_bundle.hash FAIL
  • approved bundle hash computed by policy_variants.bundle_hash() matches the trace.policy.bundle_hash the runtime writes into the record (6ed4ecc2…)

🤖 Generated with Claude Code

… runs

The console showed enforcement and proof but not tampering, which is the
argument for why the proof has to exist. That story lived in a separate
terminal demo, so nobody saw both together.

Add a policy bundle selector to the Assessment tab:

  - Approved: the example's bundle, unchanged
  - Tampered: a copy generated at runtime with two edits an operator who
    wanted a loan booked would actually make -- delegated-authority ceiling
    raised from EUR 500k to EUR 5m, concentration guardrail inverted

Nordwind Logistik (EUR 750k) is denied at the write under the approved bundle
and allowed under the tampered one. cmcp verify is now always pinned to the
approved bundle hash, so the tampered record fails policy_bundle.hash and no
edit to the record can make the two agree.

The gateway measures its bundle at startup, so the tampered bundle gets its own
process on :8444, started lazily and stopped with the console. The approved
gateway on :8443 is untouched.

Also fixed:

  - A crashed or partial agent run rendered as a green "Assessment recorded"
    banner, because the UI inferred success from the absence of a denied write.
    An empty step list is not a pass. The API now reports the exit code and
    output tail, and the UI shows it as a failure.
  - A write that only succeeded because the bundle was edited was styled with
    the same green as a clean pass. It is now an amber warning.
  - demo-02-policy-swap printed two different hashes both labelled "v1" three
    lines apart -- step 0 showed the demo's own policies-v1 bundle, step 1 read
    the hash off demo-01's claim, which is a different bundle. Step 1 now
    computes it from the same bundle step 5 pins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@imran-siddique
imran-siddique merged commit 9997461 into main Jul 27, 2026
3 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.

1 participant