feat(seal): CROSS-3 bounded-autonomy seal — offline-verifiable receipt#6
feat(seal): CROSS-3 bounded-autonomy seal — offline-verifiable receipt#6gesh75 wants to merge 1 commit into
Conversation
Fuses the #4 model-identity wedge + the #5 authority ceiling into ONE detached, offline-checkable receipt: a named+hashed model produced THIS exact change AND it was bounded by the ceiling — signed with a pinned key, checkable on an appliance that holds no secrets. The thing competitors assert but cannot hand an auditor offline. - core/seal/signing.py: Signer/Verifier protocols + Ed25519Signer/Ed25519Verifier (real asymmetric crypto). The hardware path (YubiKey PIV slot via PKCS#11 + attestation chain) implements the SAME Signer protocol and is the documented swap-in — NOT stubbed-as-working (no false-green crypto). - core/seal/seal.py: detached receipt over canonical claims {bundle_sha256 + model_identity (#4) + authority (#5) + sealed_at}. seal_bundle() REFUSES a tampered or unbounded change. verify_seal() runs offline gates G0..G6 (structure / key pinning / signature / bundle binding / model bound+honest / bounded-and-matching / bundle integrity). - core/seal/schema/seal.schema.json: the receipt schema (alg enum includes piv-ecdsa-p256). - tests/seal_test.py: 12 tests — roundtrip, OFFLINE check (public key only), wrong key, forged signature (G2), bundle tamper (G3/G6), model swap (G4), refuse-to-seal-unbounded, reject-unbounded (G5), and a 0-violation property (every valid seal certifies a bounded change). + requirements: cryptography. DETACHED by design: the seal binds the bundle by integrity.sha256 and never mutates it, so the bundle's own hash + every existing test stay untouched. Next: the real PIV sign/verify (ECDSA-P256 + Yubico attestation chain) against a physical YubiKey — same Signer protocol. Green: seal 12/12; stress/promote PASS; ceiling 8/8; authority 14/14; wedge 10/10; PR-1 29/29.
Reviewer's GuideIntroduces the CROSS-3 bounded-autonomy SEAL: a detached, offline-verifiable cryptographic receipt binding a bundle’s integrity hash, model identity, and authority ceiling, implemented with Ed25519 signing/verifying primitives, a structured verification pipeline (gates G0–G6), a JSON schema for the seal format, and comprehensive tests for round‑trip sealing, offline verification, tampering, and bounded‑authority guarantees. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
verify_seal, you never check theseal_versionon the seal or insideclaimsagainstSEAL_VERSION; adding an explicit version gate early (e.g., as part of G0) would make format evolution safer and avoid silently accepting future-incompatible receipts. - The
key_idis currently only the first 16 hex chars of the SHA-256 of the public key (64 bits); if you expect many keys or long-lived deployments it may be worth extending this prefix length to reduce collision risk while keeping IDs short.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `verify_seal`, you never check the `seal_version` on the seal or inside `claims` against `SEAL_VERSION`; adding an explicit version gate early (e.g., as part of G0) would make format evolution safer and avoid silently accepting future-incompatible receipts.
- The `key_id` is currently only the first 16 hex chars of the SHA-256 of the public key (64 bits); if you expect many keys or long-lived deployments it may be worth extending this prefix length to reduce collision risk while keeping IDs short.
## Individual Comments
### Comment 1
<location path="core/seal/seal.py" line_range="98-107" />
<code_context>
+ reason: str
+
+
+def verify_seal(bundle: dict, seal: dict, verifier: Verifier) -> SealVerdict:
+ """Offline verification — uses ONLY the pinned public key. Runs gates G0..G6; the first
+ failure decides."""
+ # G0 — structure
+ try:
+ claims = seal["claims"]
+ sig = seal["signature"]
+ alg, kid, value = sig["alg"], sig["key_id"], sig["value"]
+ b_sha, model, authority = claims["bundle_sha256"], claims["model"], claims["authority"]
+ except (KeyError, TypeError):
+ return SealVerdict(False, "G0", "malformed seal")
+
+ # G1 — key pinning: only the pinned key + alg are trusted
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider explicitly validating `seal_version` in the verifier to catch incompatible or unexpected seal formats.
Right now we never compare `seal["seal_version"]` / `claims["seal_version"]` to `SEAL_VERSION`, so a seal with an older or newer format could still verify as long as the shape mostly matches and the signature is valid. Please add a version check (e.g., in G0 or a dedicated gate) that rejects unknown major versions so format changes can’t be silently accepted without explicit migration handling.
Suggested implementation:
```python
def verify_seal(bundle: dict, seal: dict, verifier: Verifier) -> SealVerdict:
"""Offline verification — uses ONLY the pinned public key. Runs gates G0..G6; the first
failure decides."""
# G0 — structure
try:
claims = seal["claims"]
sig = seal["signature"]
alg, kid, value = sig["alg"], sig["key_id"], sig["value"]
b_sha, model, authority = claims["bundle_sha256"], claims["model"], claims["authority"]
claims_seal_version = claims["seal_version"]
except (KeyError, TypeError):
return SealVerdict(False, "G0", "malformed seal")
# G0 — seal version compatibility
# We require a known major version so that format changes cannot be silently accepted.
if not isinstance(claims_seal_version, str):
return SealVerdict(False, "G0", "seal_version must be a string")
seal_seal_version = seal.get("seal_version", claims_seal_version)
if not isinstance(seal_seal_version, str):
return SealVerdict(False, "G0", "seal.seal_version must be a string if present")
if seal_seal_version != claims_seal_version:
return SealVerdict(False, "G0", "seal_version mismatch between envelope and claims")
# Compare major versions only (e.g., "1.x" compatible with "1.y"), reject unknown majors.
expected_major = str(SEAL_VERSION).split(".", 1)[0]
actual_major = claims_seal_version.split(".", 1)[0]
if expected_major != actual_major:
return SealVerdict(False, "G0", f"unsupported seal_version {claims_seal_version}")
# G1 — key pinning: only the pinned key + alg are trusted
if alg != verifier.alg() or kid != verifier.key_id():
return SealVerdict(False, "G1", f"unpinned key/alg ({alg}/{kid})")
```
1. Ensure that `SEAL_VERSION` is defined at module scope (e.g., `SEAL_VERSION = "1.0"` or similar). If it is not yet defined, add it near the top of `core/seal/seal.py` and update its value according to your current seal format versioning scheme.
2. If your project already has a helper for parsing/handling version strings, you may want to replace the simple `split(".", 1)[0]` logic with that helper for consistency.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def verify_seal(bundle: dict, seal: dict, verifier: Verifier) -> SealVerdict: | ||
| """Offline verification — uses ONLY the pinned public key. Runs gates G0..G6; the first | ||
| failure decides.""" | ||
| # G0 — structure | ||
| try: | ||
| claims = seal["claims"] | ||
| sig = seal["signature"] | ||
| alg, kid, value = sig["alg"], sig["key_id"], sig["value"] | ||
| b_sha, model, authority = claims["bundle_sha256"], claims["model"], claims["authority"] | ||
| except (KeyError, TypeError): |
There was a problem hiding this comment.
suggestion (bug_risk): Consider explicitly validating seal_version in the verifier to catch incompatible or unexpected seal formats.
Right now we never compare seal["seal_version"] / claims["seal_version"] to SEAL_VERSION, so a seal with an older or newer format could still verify as long as the shape mostly matches and the signature is valid. Please add a version check (e.g., in G0 or a dedicated gate) that rejects unknown major versions so format changes can’t be silently accepted without explicit migration handling.
Suggested implementation:
def verify_seal(bundle: dict, seal: dict, verifier: Verifier) -> SealVerdict:
"""Offline verification — uses ONLY the pinned public key. Runs gates G0..G6; the first
failure decides."""
# G0 — structure
try:
claims = seal["claims"]
sig = seal["signature"]
alg, kid, value = sig["alg"], sig["key_id"], sig["value"]
b_sha, model, authority = claims["bundle_sha256"], claims["model"], claims["authority"]
claims_seal_version = claims["seal_version"]
except (KeyError, TypeError):
return SealVerdict(False, "G0", "malformed seal")
# G0 — seal version compatibility
# We require a known major version so that format changes cannot be silently accepted.
if not isinstance(claims_seal_version, str):
return SealVerdict(False, "G0", "seal_version must be a string")
seal_seal_version = seal.get("seal_version", claims_seal_version)
if not isinstance(seal_seal_version, str):
return SealVerdict(False, "G0", "seal.seal_version must be a string if present")
if seal_seal_version != claims_seal_version:
return SealVerdict(False, "G0", "seal_version mismatch between envelope and claims")
# Compare major versions only (e.g., "1.x" compatible with "1.y"), reject unknown majors.
expected_major = str(SEAL_VERSION).split(".", 1)[0]
actual_major = claims_seal_version.split(".", 1)[0]
if expected_major != actual_major:
return SealVerdict(False, "G0", f"unsupported seal_version {claims_seal_version}")
# G1 — key pinning: only the pinned key + alg are trusted
if alg != verifier.alg() or kid != verifier.key_id():
return SealVerdict(False, "G1", f"unpinned key/alg ({alg}/{kid})")- Ensure that
SEAL_VERSIONis defined at module scope (e.g.,SEAL_VERSION = "1.0"or similar). If it is not yet defined, add it near the top ofcore/seal/seal.pyand update its value according to your current seal format versioning scheme. - If your project already has a helper for parsing/handling version strings, you may want to replace the simple
split(".", 1)[0]logic with that helper for consistency.
…authority gate, ceiling, CROSS-3 seal (#9) Squashed release of the 8-PR stack (#2..#8): the offline, hardware-rootable bounded-autonomy receipt and everything under it. AUTO disabled by default; the only un-built piece is the hardware-PIV signer (docs/PIV_HARDWARE_SIGNER_PLAN.md) — software Ed25519 runs the whole demo. - core/llm: single shared LLM egress + adapter (local-first fallback chain, retry/cooldown, 4-event hook bus; raw httpx, no anthropic SDK; air-gap fail-closed). [#2] - evidence: model-identity wedge — change.model_identity sealed into the bundle hash; honest UNKNOWN / unattested states, never a faked attribution. [#3] - core/risk: deterministic AUTO/HITL/HOTL/BLOCK authority model (severity != authority; AS/RD/RT -> BLOCK; spine/underlay -> >= HOTL) + the no-self-escalation ceiling. [#4] - core/promote: gate rule G5 enforces the ceiling from the sealed required tier; change.authority sealed (bundle_version 1.1->1.2). [#5] - core/seal: CROSS-3 detached, offline-verifiable bounded-autonomy receipt (Ed25519; gates G0..G6; refuses to seal an unbounded change). [#6] - serve: /api/preflight/run emits the seal; GET /api/seal/pubkey + POST /api/seal/verify; examiner PDF shows the seal. [#7] - docs: PIV hardware-signer plan — the final, hardware-gated CROSS-3 pass. [#8] Property-tested safety invariants: no-self-escalation 0-violations, every-seal-is-bounded, tamper detection, offline-keyless verify, air-gap fail-closed.
|
Superseded by the squashed release #9, now merged to main. This PR's changes are in main; closing for tidiness (branch kept for history). |
CROSS-3 — the bounded-autonomy SEAL (the capstone, stacked on #5-ceiling)
Fuses the #4 model-identity wedge + the #5 authority ceiling into one detached, offline-checkable receipt: a proof that a named + hashed model produced THIS exact change AND that it was bounded by the ceiling — signed with a pinned key, checkable on an appliance that holds no secrets. That intersection (model wedge ∧ signed ceiling ∧ offline-keyless check) is the moat none of Forward / Cisco / Itential hand an auditor offline.
What's here (additive
core/seal/— the bundle is never mutated)signing.py—Signer/Verifierprotocols +Ed25519Signer/Ed25519Verifier(real asymmetric crypto). The hardware path (YubiKey PIV slot via PKCS#11 + slot-9a attestation chain) implements the sameSignerprotocol and is the documented swap-in — deliberately not stubbed-as-working, so there's no false-green crypto.seal.py— a detached receipt over canonical claims{bundle_sha256 + model_identity (#4) + authority (#5) + sealed_at}.seal_bundle()refuses a tampered or unbounded change (you never certify a self-escalation).verify_seal()runs offline gates G0–G6: structure · key pinning · signature · bundle binding · model bound + honest · bounded-and-matching · bundle integrity.schema/seal.schema.json— the receipt schema (algenum already includespiv-ecdsa-p256).Why detached
The receipt binds the bundle by
integrity.sha256and never mutates it — so the bundle's own hash and every existing suite are untouched (no version bump, no schema churn).Tests —
tests/seal_test.py, 12/12roundtrip · offline verify with the public key only (no secret) · wrong key rejected · forged signature → G2 · bundle tamper → G3/G6 · model swap → G4 · refuse-to-seal-unbounded · reject-unbounded → G5 · 0-violation property (every valid seal certifies a bounded change). Regression: stress/promote PASS, ceiling 8/8, authority 14/14, wedge 10/10, PR-1 29/29.
The one remaining piece (honest)
The signer here is a software Ed25519 key. The last step is the hardware-PIV signer — ECDSA-P256 via the YubiKey PIV slot + the Yubico attestation chain pinned compiled-in — which needs the physical token and drops into the same
Signerprotocol. That's the critic's D6–D7 crypto spike; everything it plugs into (the seal mechanics + the offline G0–G6 verifier) ships and is tested here.Summary by Sourcery
Introduce a detached, offline-verifiable cryptographic seal that binds model identity, bounded authority, and bundle integrity into a single receipt.
New Features:
Enhancements:
Tests: