Skip to content

SIGNOFF: claim-verifier chronology lower bounds (reject pre-emission / negative-time / contradictory-lifecycle false-accepts) - #10

Draft
AnubisQuantumCipher wants to merge 3 commits into
masterfrom
security/claim-chronology-lower-bounds-v1
Draft

SIGNOFF: claim-verifier chronology lower bounds (reject pre-emission / negative-time / contradictory-lifecycle false-accepts)#10
AnubisQuantumCipher wants to merge 3 commits into
masterfrom
security/claim-chronology-lower-bounds-v1

Conversation

@AnubisQuantumCipher

Copy link
Copy Markdown
Owner

⚠️ SIGNOFF_REQUIRED — trust-surface (verifier accept-condition) change. DO NOT MERGE without architect sign-off on the exact semantics below.

This changes what the claim-kernel verifier accepts. It is a security fix for a reproduced false-accept, but activating a new accept condition is a trust-surface decision. Merge/tag/release only after the architect approves the exact diff.

Defect (reproduced on live bytes)

tools/claim_bundle_verify.py enforced only upper chronology bounds (vtime > expires, vtime - emitted > max_age). It accepted bundles that are chronologically impossible:

scenario pre-fix verdict
emitted=1000, max_age=0, verify at vtime=-1 verified (negative "now")
emitted=1000, max_age=0, verify at vtime=999 verified (before emission)
emitted=1000, expires=900 (expiry precedes emission), verify at 899/900 verified (contradictory lifecycle)

Reproduced against the installed plugin and against this repo's verifier (tests/claim_hostile_test.py helpers). This is a software-semantic false-accept distinct from the (correct) formal mathematics; it affects freshness/age/expiry and any "age-checked" rendering.

Root cause

deep_validate_node freshness block (pre-fix tools/claim_bundle_verify.py:2167-2173): no vtime >= emitted, no vtime >= 0, no expires >= emitted. --verification-time-unix is type=int (accepts negatives). enforce_policy policy-max-age used the same one-sided subtraction.

Proposed semantics (exact, ordered)

For each node, against caller time t:

  1. t < 0 → refuse freshness-schema (non-negative Unix time).
  2. expires_at < 0 or expires_at < emitted_at → refuse freshness-schema (lifecycle consistency).
  3. t < emitted_at → refuse freshness-premature (new class; the dual of freshness-expired).
  4. t > expires_at (when set) → refuse freshness-expired (unchanged).
  5. t - emitted_at > max_age_seconds (when set) → refuse freshness-stale (unchanged).

Equality boundaries verify (emitted == t, t == expires). freshness-premature is added to REASON_CLASSES (locked by tests/claim_receipt_context_unsupported_v172_test.py::test_no_undeclared_reason_classes, incl. python -O). Negative/contradictory cases reuse freshness-schema to match the existing emitted<0 → freshness-schema convention.

Sibling paths covered (not just one)

  • Core verifier tools/claim_bundle_verify.pydeep_validate_node (authoritative trust boundary) + enforce_policy invariant.
  • Adapter plugin/hermes/server.py::tool_jackal_verify_bundle — rejects malformed/negative/non-integer verification_time_unix at the caller boundary (plugin-args-schema).
  • Codex plugin plugins/jackel/mcp/server.py — no direct verify handler (proxies to the shared verifier; covered by the core fix).

Evidence (all commands re-runnable; JACKAL_BIN = sealed v1.7.0 runtime jackal-native)

  • RED → GREEN (tests/claim_hostile_test.py::family_freshness): 5 rows fail pre-fix (F-premature-future-emit, F-negative-vtime, F-expires-before-emit, F-expires-negative, F-aba-b) → 0 fail post-fix; controls (F-emit-eq-vtime-ok, F-expiry-eq-emit-eq-vtime-ok, F-aba-a1/a2) pass both.
  • Full hostile suite: CLAIM_HOSTILE_PASS rows=117 failures=0.
  • CI admission gate: python3 release/tools/ci_claim_admission.pyCI_CLAIM_ADMISSION_PASS checks=3.
  • Reason-class integrity: tests/claim_receipt_context_unsupported_v172_test.py → 9/9 OK.
  • A→B→A: verify → tamper-to-premature refuses → restore verifies.
  • python -O parity: refused reason=freshness-premature in both normal and -O.
  • Report scenarios vs repo verifier: -1freshness-schema, 999freshness-premature, 1000verified, 1001freshness-stale; contradictory 899/900/901freshness-schema.

Fixtures corrected (with justification, not masking)

  • tests/claim_hostile_test.py F-expired: old fixture set expires=VTIME-10 with default emitted=VTIME — itself expires<emitted (contradictory), only "passing" via the one-sided check. Corrected to a genuine expired bundle (emitted=VTIME-1000, expires=VTIME-10).
  • release/evidence/ci_claim_fixture_v160/pins.json: verification_time_unix was 1786752000, before the fixture's emitted_at_unix=1786920569 (~1.95 days before emission — a live instance of the bug in release evidence). Bumped to 1786924800 (after emission). verification_time_unix is a caller-side pin, not part of bundle_sha256, and is not referenced by any manifest/SHA256SUMS, so no hash cascade; the bundle bytes are unchanged.
  • release/evidence/claim_hostile_matrix_v160.json: regenerated deterministic evidence (adds the new rows).

Not in this PR (recommended follow-ups)

  • Compiler-side early refusal: tools/claim_kernel.py::freshness_block still constructs expires<emitted bundles; the verifier now rejects them (verifier is the trust boundary), but refusing at compile is a cheap defense-in-depth follow-up.
  • Canonical activation: this is a proposal branch. Making the behavior a mandatory release gate == merging to master; that is the sign-off action, deliberately not taken here.

Required at landing (before/at merge; not in this proposal)

  • Re-pin the verifier + evidence: changing tools/claim_bundle_verify.py changes its sha256 (e0fcb954…90d6480e…), pinned at release/MANIFEST.sha256:38 (claim_verifier) and recorded in release/evidence/claim_aba_v160.json + claim_dogfood_v160.json. Run lake -C proofs/lean build then release/tools/repin_v172.py to re-pin the manifest, regenerate the aba/dogfood evidence, and refresh the hermes plugin bundle/identity hashes. Hosted CI on this PR does not check that pin (gaussian-proof-gate runs the verifier without hashing it; jackal-codex-plugin verifies plugins/jackel/PLUGIN_IDENTITY.sha256, not the hermes pin), so CI is green as-is; the re-pin is required for the hermes plugin's runtime self-check and the full local run_gates_v172 aggregate at release.

Independent review (two read-only reviewers)

  • security-reviewer — VERDICT: SOUND. 17 adversarial bundles + gates; all six attack classes (pre-emission, negative time, expires<emitted, negative expires, multi-node non-root evasion, parse/ordering edges) closed with correct stable classes; legitimate bundles still verify; instrument-validated against the pre-fix tree (identical inputs verify pre-fix, refuse post-fix); -O does not downgrade. No BLOCKING/IMPORTANT soundness defect.
  • reviewer — VERDICT: PASS (0.95). RED→GREEN reproduced firsthand on both verifiers; the F-expired and pins.json edits are justified and non-masking; matrix consistent (117/0); SPEC framing correct. Raised the re-pin as IMPORTANT-at-landing (addressed above) plus two MINOR items (now fixed: accurate test comment; SPEC emitted<0 clause).

Non-claims (unchanged posture)

Timestamps remain self-declared by the producer. These checks enforce internal lifecycle consistency, not timestamp authenticity, temporal provenance, or replay prevention (still an explicit residual non-claim; requires an external nonce store). Documented in release/claim/SPEC.md §3, §10.

Release-blocker

Per the chronology mission, this is a release blocker for any later JACKAL release (e.g. v1.8) that claims freshness/age checking. Do not ship such a release around it.

Anubis Quantum Cipher added 3 commits August 18, 2026 13:38
deep_validate_node accepted claim bundles verified BEFORE emission
(verification_time < emitted_at, including negative verification time)
and with contradictory lifecycles (expires_at < emitted_at). Only the
upper bounds (vtime > expires, vtime - emitted > max_age) were enforced,
so future-issued and lifecycle-impossible bundles falsely verified.

Add both-sided chronology enforcement in deep_validate_node, ordered so
domain violations refuse as freshness-schema, a valid-but-too-early check
refuses as the new freshness-premature class, and the existing upper
bounds run last (keeping vtime - emitted non-negative):
  - t < 0                      -> freshness-schema
  - expires_at < 0             -> freshness-schema
  - expires_at < emitted_at    -> freshness-schema
  - t < emitted_at             -> freshness-premature (new REASON_CLASS)
  - t > expires_at (existing)  -> freshness-expired
  - t - emitted > max_age      -> freshness-stale
enforce_policy documents the node-level >= invariant it now relies on.

Harden the hermes adapter (tool_jackal_verify_bundle) to reject a
malformed / negative / non-integer verification_time_unix at the caller
boundary (plugin-args-schema) before spawning the verifier.

TRUST-SURFACE (verifier accept-condition) change: DO NOT MERGE without
architect sign-off on the exact semantics above. This is a release
blocker for any later release claiming freshness/age checking.
Add hostile chronology rows to family_freshness proving the fix closes
the false-accepts (RED on pre-fix bytes, GREEN after): F-premature-
future-emit, F-negative-vtime, F-expires-before-emit, F-expires-negative,
F-aba-b, plus equality-boundary and A->B->A controls
(F-emit-eq-vtime-ok, F-expiry-eq-emit-eq-vtime-ok, F-aba-a1/a2).

Correct two fixtures that themselves encoded the bug and only "passed"
via the one-sided check:
  - F-expired: set emitted=VTIME-1000 so expires=VTIME-10 no longer
    precedes emission (the old fixture was expires < emitted).
  - release/evidence/ci_claim_fixture_v160/pins.json: bump
    verification_time_unix 1786752000 -> 1786924800, which was ~1.95 days
    BEFORE the fixture's emitted_at_unix=1786920569 (a live instance of
    the bug in release evidence). verification_time_unix is a caller-side
    pin, not part of bundle_sha256 and not referenced by any manifest, so
    the bundle bytes are unchanged.

Regenerate release/evidence/claim_hostile_matrix_v160.json:
CLAIM_HOSTILE_PASS rows=117 failures=0; ci_claim_admission still
CI_CLAIM_ADMISSION_PASS checks=3.
SPEC.md sec 3: enumerate the both-sided chronology bounds the verifier
now enforces and their exact refusal classes. SPEC.md sec 10: state that
freshness timestamps are SELF-DECLARED by the producer -- the verifier
enforces internal lifecycle consistency, NOT timestamp authenticity or
temporal provenance (no trusted time source), and one-time replay
prevention remains an explicit residual non-claim.
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a4803bc-32cf-4aa8-93c3-bf49ada94904

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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