Skip to content

fix(operator): harden money-paths — revoke/resale reconcile, restart-safe gateway, durable redeem reservation#25

Merged
drewstone merged 1 commit into
mainfrom
feat/harden-audit
Jun 14, 2026
Merged

fix(operator): harden money-paths — revoke/resale reconcile, restart-safe gateway, durable redeem reservation#25
drewstone merged 1 commit into
mainfrom
feat/harden-audit

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Critical audit of the consumption money-paths (/critical-audit and /harden) scored 6/10 — REQUEST_CHANGES across reviewers A/B/C, with 6 confirmed HIGH findings (0 CRITICAL). This PR fixes all six. Every fix is backed by a real-system e2e (anvil + operator + stub OpenAI), not a mock.

HIGH fixes

# File Bug Fix Proof
1 spend.rs authorize() never mirrored on-chain revocation/resale → a revoked/leaked session key or resold lot kept being served for free (settleSpend reverts, operator eats it) reconcile_revocations() on each flush drops revoked (spendRevoked) / resold channels; fail-open on transient RPC spend-e2e: revoke on-chain → flush.dropped>=1 → serve returns 401, no further billing
2 gateway.rs per-channel acked in-memory only → restart re-signs cum=0 → operator 409 stale_voucherchannel bricked journal acked to SURPLUS_GATEWAY_STATE (atomic) on every advance; seed on startup gateway-multilot-e2e: kill + restart gateway → post-restart call succeeds, bills on
3 redeem.rs serve-auth reservation + served journaled only after inference → crash re-opens spent quota persist_redeem() after reserving the auth (before inference) + on release settlement-e2e redemption serve/receipt path green
4 gateway-multilot-e2e.mjs streaming through the gateway untested add stream:true call; assert SSE flows + surplus event stripped "streamed N chars through the gateway (surplus event stripped)"
5 ci.yml money-path e2e excluded from CI new money-e2e job runs spend-e2e + gateway-multilot-e2e (viem = root devDep) both pass end-to-end
6 ci.yml app job typecheck-only; SOR math ran nowhere app — test runs router.check.ts (tsx) "router: 15 passed, 0 failed"

Verification (local, all green)

  • scripts/settlement-e2e.sh — fill → receipt redemption → default → attested + proven batches → attested redemption.
  • scripts/spend-e2e.sh — one-sig key mint → buffered + streamed calls → on-chain billing → forged voucher refused → revocation drops the channel (401).
  • scripts/gateway-multilot-e2e.sh — drain + failover → streaming through the gatewayrestart resumes from persisted acked and bills on.
  • cargo fmt --all --check, cargo build --features chain|mesh, cargo test --workspace clean.

Verdict

Initial REQUEST_CHANGES (6 HIGH) → after harden + re-verify APPROVE: 0 CRITICAL/HIGH remain. MEDIUM/LOW (stream task-leak, redeem assert_domain, Buy.tsx SOR leg-drop, Deploy 1-of-1 guard, …) are tracked in .evolve/critical-audit/2026-06-14T01:20:02Z/ as follow-ups — none blocks a 1-of-1 launch.

Audit run + fix-plan persisted under .evolve/critical-audit/.

🤖 Generated with Claude Code

…safe gateway, durable redeem reservation

Critical audit of the consumption money-paths scored 6/10 (REQUEST_CHANGES) with
6 confirmed HIGH findings. Fix all six; each is backed by a real-system e2e
(anvil + operator + stub), not a mock.

- spend: authorize() never mirrored on-chain revocation/resale, so a revoked or
  leaked session key (or a resold lot) kept being served for free — settleSpend
  would revert, the operator just ate it. reconcile_revocations() now runs on each
  flush, dropping any channel the holder revoked (spendRevoked) or whose lot
  changed hands, fail-open on transient RPC. Exposure bounded to one flush
  interval. spend-rail.md corrected to describe the real mechanism.
- gateway: per-channel acked was in-memory only; a restart re-signed cum=0, the
  operator 409'd stale_voucher, and the channel bricked. Journal acked to
  SURPLUS_GATEWAY_STATE (atomic) on every advance; seed from it on startup.
- redeem: serve-auth reservation + served were journaled only after inference; a
  crash re-opened already-spent quota. persist_redeem() now runs after reserving
  the auth (before inference) and on release.

Coverage:
- spend-e2e: revoke on-chain -> flush drops the channel -> serve returns 401, no
  further billing.
- gateway-multilot-e2e: streaming through the gateway (surplus event stripped) +
  a gateway restart that resumes from persisted acked and bills on.
- CI: new money-e2e job runs spend-e2e + gateway-multilot-e2e (viem as a root
  devDependency so node resolves it); app job now runs the SOR unit checks
  (router.check.ts: 15 passed) instead of typecheck-only.

Audit run + fix-plan persisted under .evolve/critical-audit/.
@drewstone drewstone merged commit da71dd9 into main Jun 14, 2026
8 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