Skip to content

feat(ledger): Rekor v2 hashedrekord anchoring backend - #9397

Merged
JSONbored merged 2 commits into
mainfrom
feat/ledger-anchor-rekor
Jul 27, 2026
Merged

feat(ledger): Rekor v2 hashedrekord anchoring backend#9397
JSONbored merged 2 commits into
mainfrom
feat/ledger-anchor-rekor

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Fourth sub-issue of #9267 (external ledger anchoring), stacked on #9271 (#9395). Primary anchoring mechanism per the mechanism research: hashedrekord is exactly built for a plain hash + signature — Rekor never sees the anchor payload itself. No Fulcio, no OIDC, no new dependency (ECDSA P-256/SHA-256 is native WebCrypto, the same keypair #9270 already produces for SignedLedgerAnchor).

A real bug caught and fixed during testing, not just a mock passing: this module was pre-stringifying its own error before handing it to recordLedgerAnchorAttempt, which then ran errorMessage() a second time on what was now a plain string — and errorMessage only extracts .message from actual Error instances, so every single failure path collapsed to the generic "unknown error" fallback regardless of what actually went wrong (a 429, a malformed response, a real network exception all looked identical on the public listing). Fixed by passing the raw unknown value through and teaching ledger-anchor-persistence.ts to handle both an already-built descriptive string and a genuine Error.

Closes #9272.

Test plan

  • npx vitest run test/unit/ledger-anchor-rekor.test.ts — 9/9, 100% statement/branch/function/line coverage
  • fetch is always injected — never a real network call to Rekor; the property under test is this module's own request/response/persistence logic
  • Request shape verified against the real hashedRekordRequestV002 schema (digest, signature content, verifier public key + keyDetails)
  • Response parsing handles Rekor's dynamic-uuid-keyed response shape and returns null (never throws) for 8 distinct malformed shapes
  • All three failure paths proven to record a status: 'failed' row with the real error text (not the "unknown error" bug described above) and to resolve, never reject, past the caller
  • Success path verified to record the full resolvable backendRef (shard URL + log index + key id + uuid)
  • No regression: ledger-anchor-persistence.test.ts (8/8) and the two route test files (22/22) still green after the persistence-layer fix
  • npx tsc --noEmit clean · npx tsx scripts/check-import-specifiers.ts clean

Base branch is feat/ledger-anchor-persistence (#9271, itself stacked on #9270) — merge order: #9392#9395 → this.

@JSONbored JSONbored self-assigned this Jul 27, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb

loopover-orb Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@JSONbored
JSONbored force-pushed the feat/ledger-anchor-rekor branch from 6d30a11 to c88544e Compare July 27, 2026 16:12
@JSONbored
JSONbored force-pushed the feat/ledger-anchor-persistence branch from 320257a to d61f18f Compare July 27, 2026 16:15
@JSONbored
JSONbored force-pushed the feat/ledger-anchor-rekor branch from c88544e to 1d00704 Compare July 27, 2026 16:16
@JSONbored
JSONbored force-pushed the feat/ledger-anchor-persistence branch from d61f18f to 67ea1fe Compare July 27, 2026 16:21
@JSONbored
JSONbored force-pushed the feat/ledger-anchor-rekor branch from 1d00704 to bd01dfc Compare July 27, 2026 16:22
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.62%. Comparing base (0a93e29) to head (1c7a6b8).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9397      +/-   ##
==========================================
- Coverage   89.52%   88.62%   -0.90%     
==========================================
  Files         840      842       +2     
  Lines      109777   109835      +58     
  Branches    26147    26159      +12     
==========================================
- Hits        98275    97342     -933     
- Misses      10239    11521    +1282     
+ Partials     1263      972     -291     
Flag Coverage Δ
backend 93.59% <100.00%> (-1.65%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/review/ledger-anchor-git.ts 100.00% <100.00%> (ø)
src/review/ledger-anchor-persistence.ts 100.00% <100.00%> (ø)
src/review/ledger-anchor-rekor.ts 100.00% <100.00%> (ø)
src/review/ledger-anchor.ts 100.00% <ø> (ø)

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 27, 2026
Base automatically changed from feat/ledger-anchor-persistence to feat/ledger-anchor-payload July 27, 2026 16:44
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

Base automatically changed from feat/ledger-anchor-payload to main July 27, 2026 16:53
Fourth sub-issue of #9267. Primary anchoring mechanism per the mechanism
research: hashedrekord is exactly built for a plain hash + signature — Rekor
never sees the anchor payload itself. No Fulcio, no OIDC, no new dependency
(ECDSA P-256/SHA-256 is native WebCrypto, the same keypair #9270 already
produces). Shard URL is env-configurable with a documented fallback, never
hardcoded, per the research's explicit "shards roll annually" warning.

Every path — non-2xx response, an unparseable response body, a network
exception — records status:'failed' via #9271's persistence rather than
throwing past the caller, so #9273's git backend still gets attempted even if
this one fails.

Caught and fixed a real bug during testing: this module was pre-stringifying
its own error before handing it to recordLedgerAnchorAttempt, which then ran
errorMessage() a SECOND time on what was now a plain string — and errorMessage
only extracts .message from actual Error instances, so every failure path
collapsed to the generic "unknown error" fallback regardless of what actually
went wrong. Fixed by passing the raw unknown error through and teaching
persistence to handle both an already-built string and a genuine Error, so the
real diagnostic (a 429, a shape mismatch, "network down") reaches the public
listing instead of being silently discarded.

Storing the full Rekor TransparencyLogEntry (inclusion proof + signed
checkpoint) for fully offline verification is deliberately deferred — online
verification via rekor-cli against the stored shard URL + uuid works
completely without it today.
…hive (#9273) (#9403)

Fifth sub-issue of #9267. Secondary, complementary to Rekor: appends one
JSONL line per anchor to a public repo via the GitHub Contents API and the
existing makeInstallationOctokit chokepoint -- never a direct fetch to the
GitHub API, matching every other GitHub write in this engine.

Alone this is weaker than Rekor (GitHub is a trusted third party, force-push
rewrites it). It becomes genuinely strong combined with mirrors nobody at
LoopOver controls: GH Archive's hourly PushEvent export and Software
Heritage's on-demand archival. A rewrite becomes independently detectable by
checking an archive this repo doesn't control -- documented as a runnable
cross-mirror verification procedure in the module's own header.

Commits the identical canonicalized payload + signature Rekor anchors, so
the two backends commit to the same fact, never a reshaped copy. Read-modify-
write via the file's own sha as a compare-and-swap guard; never throws past
the caller (missing repo, auth failure, rate limit, a raced sha all record
status:'failed' via #9271's persistence, same posture as the Rekor backend).

Also fixes a real bug found while building this: ledger-anchor.ts's re-export
statement only carried sha256Hex, dropping canonicalJson entirely -- any
consumer importing it (this backend needs it to commit the same payload Rekor
anchors) got undefined. Applied at the actual origin (#9270/PR #9392) and
rebased through the whole stack, not patched over downstream.
@JSONbored
JSONbored force-pushed the feat/ledger-anchor-rekor branch from 7f4eda6 to 1c7a6b8 Compare July 27, 2026 16:59
@JSONbored
JSONbored merged commit 6a7c1af into main Jul 27, 2026
4 checks passed
@JSONbored
JSONbored deleted the feat/ledger-anchor-rekor branch July 27, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ledger: Rekor v2 hashedrekord anchoring backend

1 participant