# HMAC approval-boundary closure review

Use for a final, read-only review of short-lived server-authorized approvals in Preview → Confirm transactional workflows.

## Threat-model checklist

1. **Minting boundary**
   - Missing approval-key configuration fails closed.
   - Missing/wrong operator authentication is rejected with constant-time comparison.
   - The MAC uses a dedicated server-configured key, not a public fingerprint or an unkeyed digest of response fields.
   - Canonical MAC material includes domain/scope, evidence/version, exact cluster/object identity, complete sorted member set, baseline fingerprint, issued/expiry timestamps, and nonce.

2. **Full-set binding attacks**
   - Try public-field SHA-256 forgery.
   - Try mint-time subset approval.
   - Try consumption-time subset, exclusion, duplicate, omitted-member, direct payload, and action/category substitution.
   - Try reuse against a changed input or reconstructed cluster.
   - The consumer must reconstruct the current complete server-side member set and compare both included and approved members against it. Validating only client-supplied included members is insufficient.

3. **Time and baseline attacks**
   - Changed baseline rejects an unused approval before writes.
   - Malformed timestamps, wrong TTL, future issue time beyond tolerance, and expiry reject.
   - Probe the exact expiry boundary explicitly; decide and test whether expiration is inclusive (`now >= expires_at`) or exclusive (`now > expires_at`).

4. **Replay semantics**
   - First valid confirm writes exactly once.
   - Immediate exact replay returns idempotent success with no writes.
   - Expired exact replay fails with no writes.
   - Compare a deterministic database dump or exact table/audit counts before and after each replay.
   - Do not require reconstruction against a live baseline that necessarily changed because of the original successful confirm. The idempotent path should verify immutable stored request identity and the explicitly required live constraints, such as approval expiry.

5. **Audit closure**
   - Read the committed audit row and validate action/entity, approval count, evidence version, decision version, status, and actual/expected write counts.
   - Confirm the secret, MAC, nonce, raw approval payload, and sensitive source material are not persisted in the audit.
   - Failed confirms must atomically roll back audit and domain writes.

## Production approval timing

- A predeploy preview may prove classification and accounting semantics, but its short-lived, baseline-bound approval is never a production Confirm capability.
- After remote CI, exact-SHA merge/deployment, and the final backup/restore gate, reconstruct the undecided production preview, mint fresh approvals against that live baseline, and immediately run the fingerprint-bound final Preview → Confirm sequence.
- Keep predeploy signed requests as superseded private evidence only; never refresh timestamps or reuse their tokens after deployment, restart, or any database change.

## Read-only execution discipline

- Capture `git status --short` and `git diff --check` before probes and recheck afterward.
- Disable interpreter/test caches where practical (`PYTHONDONTWRITEBYTECODE=1`, pytest `-p no:cacheprovider`); never create lockfiles during closure review.
- If the HTTP harness is unavailable but imports work, supplement static route/header inspection with an in-memory service-level attack probe. This is fallback evidence, not proof of framework wiring.
- A useful deterministic check is `"\n".join(conn.iterdump())`: snapshot immediately after first confirm and require exact equality after immediate replay and rejected expired replay.

## Severity-only report gate

When the request says `P0/P1/P2 only`, report only findings in those classes and write `APPROVE` only when all are empty. Keep setup limitations separate from security findings. Do not elevate unrelated pre-existing lint debt into a boundary finding unless it materially affects the approval implementation.