Skip to content

fix: IPNS signed-record verify coverage chokepoint and non-CAS sequence gate#544

Merged
FSM1 merged 45 commits into
mainfrom
feat/ipns-signature-verify-coverage
Jun 22, 2026
Merged

fix: IPNS signed-record verify coverage chokepoint and non-CAS sequence gate#544
FSM1 merged 45 commits into
mainfrom
feat/ipns-signature-verify-coverage

Conversation

@FSM1

@FSM1 FSM1 commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Phase 58 — IPNS Signature-Verify Coverage (HARD-09)

Finishes the IPNS signed-record verification story left after Phase 51 / #529: binds every resolved record to its signed CBOR-embedded CID/sequence, folds verification into a single safe-by-default Rust chokepoint, validates the embedded publish sequence even when CAS is omitted, de-duplicates the web vs sdk-core resolve copies, and adds shared cross-language verify vectors.

What changed

  • CBOR cid/sequence binding (D-07/D-08)crates/core/src/ipns.rs gains decode_ipns_cbor_data; both the Rust verify path (crates/fuse/src/verify.rs) and sdk-core resolveIpnsRecord now decode the signed CBOR data and reject when the embedded Value(cid)/Sequence does not match the response cid/sequenceNumber. A mismatch is classified identically to an invalid signature (fail-closed). Closes the swap gap on both languages.
  • resolve_ipns_verified chokepoint (D-01/D-02) — new crates/fuse/src/verify.rs wrapper; all 9 FUSE resolve sites (events, fs, publish ×2, metadata ×3, replay parent-merge + folder-key) route through it. Per-operation scoped fail-closed (a verify failure refuses the unverified CID but fails only that operation; the 30s poll self-heals). The folder-key descent keeps its existing hard fail-closed posture (D-03). Legacy all-absent records still allowed and flagged (D-04).
  • Non-CAS embedded-sequence gate (D-09/D-10)apps/api/src/ipns/ipns.service.ts upsertFolderIpns now validates the embedded sequence unconditionally (no longer gated on expectedSequenceNumber): first publish allows {0,1}, forward N+1 increments, embedded == N is an idempotent re-sign (no DB increment but latestCid/signedRecord updated — protects the TEE 6-hour path), rollback < N and wild-jump > N+1 are rejected. Every non-CAS publish path was enumerated and proven DB+1-or-idempotent first.
  • Web/sdk-core dedup (D-13)apps/web/src/services/ipns.service.ts deletes its local verifyIpnsSignature/resolveIpnsRecord and delegates to @cipherbox/sdk-core (ctx axios injection + withPerf preserved). The web now inherits the CBOR binding it previously lacked.
  • Shared cross-language verify vectors (D-11/D-12)tests/vectors/ipns/verify.json (7 cases: valid, tampered-sig, name-mismatch, cid-swapped, seq-mismatch, partial-fields, legacy-absent) consumed by both crates/fuse/tests/ipns_verify_vectors.rs (cargo) and the sdk-core vitest suite. Rust↔JS byte-construction drift now fails an already-required CI check.

Verification

Gate Result
cargo test (core / fuse + vectors) 75 / 87+1 green
apps/api jest 913/913 green (9 new D-09 cases)
sdk-core vitest 243 green (7 shared-vector consumers)
full SDK E2E (real client→API round-trip) 89/89 green, 0 D-09/sequence regressions
  • Phase verifier: passed, 7/7 must-haves.
  • Security audit (58-SECURITY.md): SECURED, 19/19 threats closed (ASVS L1).
  • Nyquist validation (58-VALIDATION.md): compliant, 0 gaps, 35-row coverage map.
  • TDD: RED→GREEN gates present for the binding, the D-09 rule, and the decode helper.

Trust model

DB CID remains the authoritative trust root; signature verification is defense-in-depth (Medium). That is why resolve-side failures are scoped (not whole-mount) and legacy records stay allowed.

Deferred (captured as todos, not in this PR)

  • Route the 6 remaining unverified resolve_ipns sites in apps/desktop/src-tauri/ (prepopulate.rs, vault.rs) through the verified wrapper — out of scope for this phase, no regression from baseline.
  • Minor simplify follow-ups (dead signature_verified field, a test-string clarity rename, a dead journal_entry branch, unused fixture key fields).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced IPNS record verification with strict cryptographic binding validation preventing CID/sequence tampering
    • Unconditional embedded-sequence validation for IPNS publications to prevent sequence rollback and jump attacks
    • Unified cross-language signature verification with shared test vectors between Rust and JavaScript implementations
    • Legacy IPNS records without signatures remain supported but explicitly marked as unverified

FSM1 added 30 commits June 22, 2026 15:30
Entire-Checkpoint: 46695c75b0c7
Entire-Checkpoint: 88f4300e1d31
Entire-Checkpoint: cdc7df5fa516
Entire-Checkpoint: 473de6ab6524
Entire-Checkpoint: a31b672a7238
Entire-Checkpoint: 1b8650057230
- Wave-0 probe: parseCborData not importable from 'ipns' main entry
- Fallback: import decode from 'cborg' directly
- cborg 4.5.8 matches ipns transitive version

Entire-Checkpoint: e76f5aaacd8e
- Round-trip with build_cbor_data for known value and sequences
- Rejects non-map CBOR, missing Value key, missing Sequence key
- Rejects negative sequence (u64::try_from fails)

Entire-Checkpoint: 8f70cb7f36a7
- Inverse of build_cbor_data: decodes CBOR map to extract Value and Sequence
- Matches CborValue::Bytes for Value key (not Text, per build_cbor_data)
- Converts CborValue::Integer via i128->u64 two-step (ciborium Integer newtype)
- Returns CborEncodingFailed for missing fields, non-map, bad UTF-8, negative seq
- All 6 unit tests pass

Entire-Checkpoint: cb7bcf6e8475
…e/src/verify.rs

- New verify.rs with VerifyError enum and VerifiedResolve struct
- bind_verified pure helper drives unit tests without network dependency
- resolve_ipns_verified wraps resolve_ipns + verify_ipns_resolve_signature + bind_verified
- D-08: VerifiedResolve.cid is the embedded value with /ipfs/ stripped
- D-07: embedded seq must match response sequence_number
- D-04: Legacy variant for all-absent records
- V7: error messages reference ipns_name/cid only, no signature bytes
- All 5 bind_verified unit tests pass

Entire-Checkpoint: 4b30e061e435
…fied

- events.rs spawn_metadata_refresh: D-02 scoped fail on Invalid
- fs.rs FilePointer resolve: D-02 scoped fail on Invalid
- metadata.rs remote_merge, bin entry, file-metadata resolve: D-02 scoped
- publish.rs resolve_sequence soft: Invalid falls back to cache
- publish.rs resolve_sequence_strict: Invalid returns Err
- replay.rs resolve_folder_key: D-03 hard fail-closed preserved
- replay.rs fetch_merge_publish_parent: Invalid retains journal entry
- All Legacy paths warn and re-resolve with DB CID per D-04

Entire-Checkpoint: 43681d634a9b
…veIpnsRecord

RED: three tests verify that D-07/D-08 binding mismatches throw; positive
and legacy-not-bound tests also added. Binding not yet implemented.

Entire-Checkpoint: 2cc9dde893b6
After Ed25519 signature verification, decode the signed CBOR `data` field
via cborg and compare embedded Value and Sequence against the response cid
and sequenceNumber. Mismatch throws with a descriptive error that propagates
through the catch block. Legacy records remain unbound per D-04. Mirrors
the Rust bind_verified posture in verify.rs for D-05 convergence.

Entire-Checkpoint: 495bb45f65df
…onfig

Cargo.lock reflects ciborium added to cipherbox-fuse for verify.rs test module.

Entire-Checkpoint: 572bed975484
…FolderIpns

- Red tests covering all 9 D-09 behavior cases:
  first-publish seq>1 rejected, seq 0 and 1 accepted,
  idempotent embedded=N (no DB increment, latestCid updated),
  forward embedded=N+1 (increment), rollback embedded<N (400),
  wild-jump embedded>N+1 (400), unconditional without CAS,
  CAS-409 precedence over D-09 (conflict wins)
- Gate is NOT yet implemented; all 5 new D-09 reject tests fail

Entire-Checkpoint: ac0b25833ab6
…psertFolderIpns

Replace the CAS-gated S1 sequence block with the D-09 unconditional gate:
- First publish (no existing row): allows embedded seq 0 or 1; rejects higher
  values (wedge-poison prevention, T-58-08)
- Existing row at N, embedded=N: idempotent republish; skips DB increment
  but still updates latestCid and signedRecord (TEE 6-hour re-sign path)
- Existing row at N, embedded=N+1: forward publish; increments DB sequence
- Existing row at N, embedded<N: rollback rejected (T-58-09)
- Existing row at N, embedded>N+1: wild-jump rejected (T-58-10)
- CAS 409 check stays before D-09 gate so concurrent-modification keeps its 409

Update existing tests to supply D-09-valid sequences for existing-row scenarios
(forward = DB_seq+1); add isIdempotentRepublish flag to guard the DB increment.

Entire-Checkpoint: 0d07a4a79846
- Delete local verifyIpnsSignature and resolveIpnsRecord verify body
- Import resolveIpnsRecord from @cipherbox/sdk-core with apiAxios/apiUrl/getAccessToken ctx
- Remove now-unused imports: verifyEd25519, concatBytes, deriveIpnsName, IPNS_SIGNATURE_PREFIX, ipnsControllerResolveRecord, logger
- Web inherits 58-01 CBOR binding and partial-fields fail-closed check automatically
- All 14 callers unchanged (same public signature)

Entire-Checkpoint: d3676149211a
- scripts/gen-ipns-verify-vectors.mjs: one-shot Node ESM generator using
  @noble/ed25519 and cborg to produce deterministic signed vectors
- tests/vectors/ipns/verify.json: 7-case shared cross-language fixture
  covering valid, tampered-sig, name-mismatch, cid-swapped, seq-mismatch,
  partial-fields, and legacy-absent (D-11)
- cid-swapped and seq-mismatch carry genuine Ed25519 signatures over their
  mismatching CBOR data so only the binding check (not the sig check) fails

Entire-Checkpoint: c5294ad08b17
- crates/fuse/tests/ipns_verify_vectors.rs: loads shared verify.json and
  asserts all 7 cases using the real verify_ipns_resolve_signature
  (cipherbox-api-client) and decode_ipns_cbor_data (cipherbox-core)
- Located in crates/fuse to avoid the dependency cycle: api-client and
  core both depend on crypto, so crypto cannot dev-depend on either
- cid-swapped and seq-mismatch assert invalid via the binding path
  (their Ed25519 sigs are valid; only the binding check rejects them)
- cargo test -p cipherbox-fuse --test ipns_verify_vectors passes all 7

Entire-Checkpoint: fa0b1b811df9
- packages/sdk-core/src/__tests__/ipns.test.ts: append D-11/D-12 describe
  block loading verify.json and asserting all 7 cases via resolveIpnsRecord
- cid-swapped and seq-mismatch cases pass REAL fixture data bytes through
  cborDecode so the binding layer is exercised against genuine vector bytes
- partial-fields asserts fail-closed before verifyEd25519 is called
- legacy-absent asserts signatureVerified=false (D-04 path)
- All 26 ipns tests pass; JSON import resolves via resolveJsonModule=true

Entire-Checkpoint: 3c64c98730bc
- 58-04-SUMMARY.md: documents generator approach, Rust consumer location
  rationale, JS fixture import path, and D-12 CI gate compliance
- STATE.md: advance completed_plans to 186, add P04 metrics row
- ROADMAP.md: mark 58-04 plan checkbox complete

Entire-Checkpoint: 41b69f9f0e44
…n bind_verified

Entire-Checkpoint: b7240dae8c2e
Entire-Checkpoint: 7b22b6f3c1cc
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@FSM1, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 55 minutes and 23 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9133b57a-1429-4957-bd85-e5471d71d083

📥 Commits

Reviewing files that changed from the base of the PR and between 7e4567c and fc0c5a7.

📒 Files selected for processing (13)
  • .planning/todos/pending/2026-06-22-ipns-first-publish-sequence-convention.md
  • .planning/todos/pending/2026-06-22-phase58-simplify-cleanup.md
  • .planning/todos/pending/2026-06-22-verify-rs-carry-legacy-response.md
  • crates/core/src/ipns.rs
  • crates/fuse/src/verify.rs
  • crates/fuse/tests/ipns_verify_vectors.rs
  • eslint.config.js
  • package.json
  • packages/sdk-core/src/__tests__/ipns.test.ts
  • packages/sdk-core/src/ipns/index.ts
  • scripts/gen-ipns-verify-vectors.ts
  • tests/vectors/ipns/verify.json
  • tsconfig.scripts.json

Walkthrough

Phase 58 adds end-to-end IPNS signature verification coverage: a new Rust CBOR decode helper and verified resolve chokepoint route all nine FUSE resolve sites through fail-closed binding checks; the API's upsertFolderIpns gains an unconditional embedded-sequence gate; sdk-core adds CBOR binding assertions and the web service delegates to sdk-core; a shared 7-case cross-language fixture is generated and consumed by both Rust and Vitest suites.

Changes

Phase 58 IPNS Signature-Verify Coverage

Layer / File(s) Summary
Rust CBOR decode helper and verified resolve chokepoint
crates/core/src/ipns.rs, crates/fuse/src/lib.rs, crates/fuse/src/verify.rs
decode_ipns_cbor_data is added to cipherbox-core with round-trip and error-case tests. A new verify module exposes VerifyError (Legacy/Invalid/Api), VerifiedResolve, bind_verified (base64 → CBOR → CID/sequence binding), and the public async resolve_ipns_verified entry point, all with unit test coverage.
FUSE call-site routing
crates/fuse/src/events.rs, crates/fuse/src/fs.rs, crates/fuse/src/metadata.rs, crates/fuse/src/publish.rs, crates/fuse/src/replay.rs
All direct resolve_ipns calls in the FUSE runtime are replaced with resolve_ipns_verified. Each site handles Legacy by warning and re-resolving via the raw API, Invalid as a fail-closed error, and Api as an error. replay.rs adds a checked_add overflow guard and corrects the unpin CID source.
API unconditional D-09 embedded-sequence gate
apps/api/src/ipns/ipns.service.ts, apps/api/src/ipns/ipns.service.spec.ts
upsertFolderIpns replaces the CAS-conditional S1 block with an unconditional gate enforcing first-publish (0n/1n), forward publish (dbSeq+1), and idempotent republish (no DB sequence increment while still updating latestCid/signedRecord); rollback and wild-jump sequences are rejected. Existing tests are updated with forward-publish mock sequences and a new D-09 describe block covers all gate cases plus CAS-409 precedence.
SDK-core CBOR binding and web delegation
packages/sdk-core/package.json, packages/sdk-core/src/ipns/index.ts, apps/web/src/services/ipns.service.ts, packages/sdk-core/src/__tests__/ipns.test.ts
cborg is added as a runtime dependency. resolveIpnsRecord in sdk-core decodes the signed CBOR data and throws on embedded Value/Sequence mismatches. The web service removes verifyIpnsSignature and its local resolve logic, replacing them with a thin delegation to resolveIpnsRecordCore using apiUrl, getAccessToken, and apiAxios. sdk-core tests are updated with real CBOR payloads and a new D-07/D-08 binding test suite.
Shared verify vectors, generator, and cross-language consumers
scripts/gen-ipns-verify-vectors.mjs, tests/vectors/ipns/verify.json, crates/fuse/tests/ipns_verify_vectors.rs, crates/fuse/Cargo.toml, release-please-config.json
A deterministic generator script produces 7 IPNS verify vectors covering valid, tampered-signature, name-mismatch, CID-swapped, sequence-mismatch, partial-fields, and legacy-absent cases. The committed verify.json is consumed by a new Rust integration test that runs the full signature-verify and CBOR binding pipeline, and by D-11/D-12 Vitest cases in sdk-core. crates/fuse gains a ciborium dev-dependency and crates/core bumps to 0.6.0.
Phase 58 planning, audit, and tracking documentation
.planning/PROJECT.md, .planning/ROADMAP.md, .planning/STATE.md, .planning/config.json, .planning/phases/58-ipns-signature-verify-coverage/*, .planning/todos/pending/2026-06-22-*.md
All four plan/summary documents, context, research, patterns, discussion log, security, review, verification, and validation reports for Phase 58 are added. Roadmap, state, and project milestone markers are updated to reflect completion on 2026-06-22. Two pending TODO files track desktop call-site coverage and post-phase cleanup.

Sequence Diagram(s)

sequenceDiagram
  participant FUSESite as FUSE Call Site
  participant resolve_ipns_verified
  participant ApiClient
  participant bind_verified
  participant decode_ipns_cbor_data

  rect rgba(70, 130, 180, 0.5)
    note over FUSESite,decode_ipns_cbor_data: Verified resolve path (events/fs/metadata/publish/replay)
    FUSESite->>resolve_ipns_verified: resolve_ipns_verified(api, ipns_name)
    resolve_ipns_verified->>ApiClient: resolve_ipns(ipns_name)
    ApiClient-->>resolve_ipns_verified: IpnsResolveResponse
    resolve_ipns_verified->>ApiClient: verify_ipns_resolve_signature(response)
    ApiClient-->>resolve_ipns_verified: verdict
    resolve_ipns_verified->>bind_verified: verdict + response
    bind_verified->>decode_ipns_cbor_data: base64-decoded CBOR bytes
    decode_ipns_cbor_data-->>bind_verified: embedded Value and Sequence
    bind_verified-->>resolve_ipns_verified: VerifiedResolve or VerifyError
    resolve_ipns_verified-->>FUSESite: Ok(VerifiedResolve) or Err(Legacy/Invalid/Api)
  end

  rect rgba(180, 100, 60, 0.5)
    note over FUSESite: Error dispatch per site
    FUSESite-->>FUSESite: Legacy → warn + fallback raw resolve
    FUSESite-->>FUSESite: Invalid → fail-closed error (hard-fail for folder-key)
    FUSESite-->>FUSESite: Api → propagate error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • FSM1/cipher-box#529: Phase 51 established the IPNS signatureV2/data/pubKey verification flow that Phase 58 (this PR) directly extends with CBOR CID/sequence binding checks and the Rust resolve_ipns_verified chokepoint.
  • FSM1/cipher-box#448: Added backend-stored signed_record/public_key and pubKey→ipnsName checks that this PR builds on by further decoding signed CBOR data to enforce embedded cid/sequence binding in both Rust and sdk-core.
  • FSM1/cipher-box#255: Modified upsertFolderIpns initial DB sequenceNumber semantics (0→1), which directly shapes the allowed first-publish embedded-sequence values (0n/1n) enforced by the new D-09 gate in this PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing IPNS signature verification coverage through a chokepoint and non-CAS sequence gate, which are the core deliverables of Phase 58.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ipns-signature-verify-coverage

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 and usage tips.

@github-actions github-actions Bot added release:cipherbox-core:feat Minor version bump (new feature) for cipherbox-core release:cipherbox-fuse:feat Minor version bump (new feature) for cipherbox-fuse release:sdk-core:feat Minor version bump (new feature) for sdk-core release:api:feat Minor version bump (new feature) for api release:web:feat Minor version bump (new feature) for web release:cipherbox-sdk:fix Patch version bump (bug fix) for cipherbox-sdk release:desktop:fix Patch version bump (bug fix) for desktop release:sdk:fix Patch version bump (bug fix) for sdk release:tee-worker:fix Patch version bump (bug fix) for tee-worker labels Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Release Preview

Package Bump Label Source
api minor release:api:feat Direct (feat commit)
cipherbox-core minor release:cipherbox-core:feat Direct (feat commit)
cipherbox-fuse minor release:cipherbox-fuse:feat Direct (feat commit)
cipherbox-sdk patch release:cipherbox-sdk:fix Cascade (cipherbox-core minor)
desktop minor release:desktop:fix Cascade (cipherbox-core minor)
sdk patch release:sdk:fix Cascade (sdk-core minor)
sdk-core minor release:sdk-core:feat Direct (feat commit)
tee-worker patch release:tee-worker:fix Cascade (sdk-core minor)
web minor release:web:feat Direct (feat commit)

Cascade Details

  • cipherbox-core minor -> cipherbox-sdk patch (direct dependency)
  • cipherbox-core minor -> desktop patch (direct dependency)
  • sdk-core minor -> sdk patch (direct dependency)
  • sdk-core minor -> tee-worker patch (direct dependency)

github-actions Bot and others added 3 commits June 22, 2026 16:29
Rename scripts/gen-ipns-verify-vectors.mjs → .ts with strict TypeScript
annotations (VectorEntry interface, typed helpers, async ed25519 API),
drop the unused createIpnsRecord binding, update the run instruction to
npx tsx, add the file to tsconfig.scripts.json include, and update the
Rust doc comment and planning todo to reference the new filename.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: c477ad416aa9
Extend the eslint files glob from *.{js,mjs,cjs,ts,tsx} to include mts
and cts, and widen the lint-staged key to match the same set of
extensions so pre-commit linting fires on all TypeScript module variants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entire-Checkpoint: df8ebf8e98fc

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/core/src/ipns.rs`:
- Around line 89-99: The loop currently allows duplicate known keys like "Value"
and "Sequence" to be silently overwritten by the last matching entry. Add
validation to detect and reject duplicates: before assigning to value_bytes when
matching the "Value" key, check if value_bytes is already Some and return an
error if so; similarly, before assigning to sequence when matching the
"Sequence" key, check if sequence is already Some and return an error if so.
This ensures that ambiguous or duplicate entries fail closed rather than
silently accepting the last occurrence.

In `@crates/fuse/src/verify.rs`:
- Around line 21-23: The VerifyError::Legacy variant currently carries no data,
forcing callers to re-resolve IPNS when encountering legacy records. Modify the
Legacy variant to include the raw CID and sequence fields from the response so
the data can be reused. Update all locations where VerifyError::Legacy is
constructed (likely around lines 66-67 and 130-131) to pass these fields when
creating the error. Then update all match branches handling VerifyError::Legacy
to use the carried CID and sequence directly instead of calling
cipherbox_api_client::ipns::resolve_ipns again, eliminating the redundant second
resolution.

In `@packages/sdk-core/src/ipns/index.ts`:
- Around line 262-270: Add explicit type validation for the embeddedSeq variable
before BigInt coercion. Instead of relying only on the typeof 'bigint' check and
then using BigInt(embeddedSeq as number), first validate that embeddedSeq is
either a bigint type or a safe integer using Number.isSafeInteger() for number
types. Throw an error if embeddedSeq is a string, boolean, or unsafe number.
After creating embeddedSeqBigInt, add a bounds check to ensure it falls within
the valid u64 range (0n to (1n << 64n) - 1n) to match Rust's validation and
prevent signature validation bypass from malformed CBOR records.

In `@scripts/gen-ipns-verify-vectors.mjs`:
- Line 67: Remove the unused `createIpnsRecord` variable from the destructuring
assignment in the import statement on the line where both createIpnsRecord and
deriveIpnsName are imported from the pathToFileURL(CORE_PATH) module. Keep only
deriveIpnsName in the destructuring since createIpnsRecord is never referenced
in the script and is causing the linting error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f5d9ff4f-c307-49c9-a419-40f2ff610bcd

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5daaa and 7e4567c.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • .planning/PROJECT.md
  • .planning/ROADMAP.md
  • .planning/STATE.md
  • .planning/config.json
  • .planning/phases/58-ipns-signature-verify-coverage/58-01-PLAN.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-01-SUMMARY.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-02-PLAN.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-02-SUMMARY.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-03-PLAN.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-03-SUMMARY.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-04-PLAN.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-04-SUMMARY.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-CONTEXT.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-DISCUSSION-LOG.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-PATTERNS.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-RESEARCH.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-REVIEW.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-SECURITY.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-VALIDATION.md
  • .planning/phases/58-ipns-signature-verify-coverage/58-VERIFICATION.md
  • .planning/todos/completed/2026-06-20-ipns-publish-validate-embedded-sequence-without-cas.md
  • .planning/todos/completed/2026-06-20-ipns-resolve-verify-coverage-and-web-sdk-dedup.md
  • .planning/todos/pending/2026-06-22-desktop-resolve-ipns-verified-coverage.md
  • .planning/todos/pending/2026-06-22-phase58-simplify-cleanup.md
  • apps/api/src/ipns/ipns.service.spec.ts
  • apps/api/src/ipns/ipns.service.ts
  • apps/web/src/services/ipns.service.ts
  • crates/core/src/ipns.rs
  • crates/fuse/Cargo.toml
  • crates/fuse/src/events.rs
  • crates/fuse/src/fs.rs
  • crates/fuse/src/lib.rs
  • crates/fuse/src/metadata.rs
  • crates/fuse/src/publish.rs
  • crates/fuse/src/replay.rs
  • crates/fuse/src/verify.rs
  • crates/fuse/tests/ipns_verify_vectors.rs
  • packages/sdk-core/package.json
  • packages/sdk-core/src/__tests__/ipns.test.ts
  • packages/sdk-core/src/ipns/index.ts
  • release-please-config.json
  • scripts/gen-ipns-verify-vectors.mjs
  • tests/vectors/ipns/verify.json

Comment thread crates/core/src/ipns.rs
Comment thread crates/fuse/src/verify.rs
Comment thread packages/sdk-core/src/ipns/index.ts Outdated
Comment thread scripts/gen-ipns-verify-vectors.mjs Outdated
@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR completes the IPNS signature-verify coverage story by adding a CBOR cid/sequence binding check (D-07/D-08), introducing a single resolve_ipns_verified chokepoint for all 9 FUSE resolve sites, adding an unconditional embedded-sequence gate in the API service (D-09), deduplicating web vs sdk-core resolve logic, and adding shared cross-language test vectors.

  • CBOR binding and chokepoint (verify.rs, ipns.rs)decode_ipns_cbor_data extracts the signed Value/Sequence from the CBOR data field; bind_verified and resolveIpnsRecord in sdk-core both reject a mismatch as Invalid; a first-publish sequence skew (resp_seq==1, embedded==0) is explicitly allowed on both language paths.
  • D-09 unconditional sequence gate (ipns.service.ts)upsertFolderIpns now validates the embedded sequence regardless of whether expectedSequenceNumber is provided; first-publish restricts embedded ∈ {0,1}, forward-publish requires N+1, idempotent re-sign at N is allowed (TEE 6-hour path), and rollback/wild-jump are rejected.
  • Web/sdk-core dedup and shared vectors — web resolveIpnsRecord delegates to sdk-core, and tests/vectors/ipns/verify.json is consumed by both the Rust cargo test runner and the sdk-core vitest suite (8 cases including the first-publish-skew case).

Confidence Score: 5/5

Safe to merge; the core CBOR-binding and D-09 sequence-gate logic is correct on both language paths and backed by 8 shared cross-language vectors.

The three open items carried over from the prior review are captured in deferred todos, scoped as non-regressions from baseline, and were already present before this PR. The new code — CBOR binding, D-09 gate, web dedup — is correctly implemented and well-tested.

crates/fuse/src/events.rs and the other five FUSE resolve sites still make a second network call in the Legacy fallback arm; tracked in the pending todo.

Important Files Changed

Filename Overview
crates/fuse/src/verify.rs New chokepoint for all FUSE IPNS resolves; bind_verified correctly performs CBOR cid/seq binding and documents the first-publish skew exception; Legacy arm still triggers a second network call (deferred todo).
apps/api/src/ipns/ipns.service.ts D-09 unconditional sequence gate correctly handles first-publish, forward-publish, idempotent re-sign, rollback, and wild-jump; anti-rollback check and D-09 gate use complementary sources, both secure.
packages/sdk-core/src/ipns/index.ts CBOR binding and first-publish-skew logic mirror the Rust path exactly; partial-fields fail-closed and public-key derivation checks present.
apps/web/src/services/ipns.service.ts Correctly delegates resolveIpnsRecord to sdk-core with apiAxios injection; web callers now inherit CBOR binding previously missing.
apps/api/src/ipns/ipns.service.spec.ts 9 new D-09 gate cases added; batch test file-IPNS first-publish path no longer covered (previously flagged).
tests/vectors/ipns/verify.json 8 shared cross-language vectors covering all key tamper scenarios including first-publish-skew.
crates/fuse/src/events.rs Legacy arm makes a second resolve_ipns call (TOCTOU); pattern present in 5 other FUSE sites, tracked as deferred todo.
crates/core/src/ipns.rs decode_ipns_cbor_data correctly extracts Value/Sequence with duplicate-key, negative, and overflow rejection all tested.

Reviews (3): Last reviewed commit: "docs: capture IPNS first-publish sequenc..." | Re-trigger Greptile

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.34579% with 174 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.22%. Comparing base (5d5daaa) to head (fc0c5a7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/fuse/src/publish.rs 5.76% 49 Missing ⚠️
crates/fuse/src/metadata.rs 0.00% 47 Missing ⚠️
crates/fuse/src/replay.rs 0.00% 33 Missing ⚠️
crates/fuse/src/events.rs 0.00% 17 Missing ⚠️
crates/fuse/src/fs.rs 0.00% 14 Missing ⚠️
crates/fuse/src/verify.rs 92.41% 11 Missing ⚠️
crates/core/src/ipns.rs 97.16% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #544      +/-   ##
==========================================
+ Coverage   65.94%   68.22%   +2.27%     
==========================================
  Files         148      177      +29     
  Lines       11473    20331    +8858     
  Branches     1300     1302       +2     
==========================================
+ Hits         7566    13871    +6305     
- Misses       3662     6216    +2554     
+ Partials      245      244       -1     
Flag Coverage Δ
api 86.04% <100.00%> (+0.03%) ⬆️
api-client 86.04% <100.00%> (+0.03%) ⬆️
core 86.04% <100.00%> (+0.03%) ⬆️
crypto 86.04% <100.00%> (+0.03%) ⬆️
desktop 16.66% <ø> (-14.78%) ⬇️
rust 65.14% <57.97%> (?)
sdk 86.04% <100.00%> (+0.03%) ⬆️
sdk-core 86.04% <100.00%> (+0.03%) ⬆️

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

Files with missing lines Coverage Δ
apps/api/src/ipns/ipns.service.ts 88.71% <100.00%> (+1.21%) ⬆️
crates/fuse/src/lib.rs 98.45% <ø> (ø)
crates/core/src/ipns.rs 98.53% <97.16%> (ø)
crates/fuse/src/verify.rs 92.41% <92.41%> (ø)
crates/fuse/src/fs.rs 34.00% <0.00%> (ø)
crates/fuse/src/events.rs 0.00% <0.00%> (ø)
crates/fuse/src/replay.rs 47.75% <0.00%> (ø)
crates/fuse/src/metadata.rs 34.07% <0.00%> (ø)
crates/fuse/src/publish.rs 50.58% <5.76%> (ø)

... and 78 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

FSM1 and others added 5 commits June 22, 2026 19:12
The Phase 58 D-07 sequence binding compared the signed CBOR embedded
Sequence against the API response sequenceNumber with strict equality.
That is stricter than the publish-side D-09 gate it mirrors: D-09 accepts
an embedded sequence of 0 OR 1 on first publish and unconditionally stores
DB sequenceNumber=1. The Rust/FUSE publish paths embed the IPNS-native 0
(next_file_publish_sequence, first child-folder publish) while the TS SDK
embeds 1 — both legitimate. So a resolved first-generation record carries
embedded=0 with response sequenceNumber=1, which the strict binding wrongly
rejected as a tamper.

This broke the desktop E2E round-trip on all platforms (FilePointer verify
and FUSE replay self-verify) with "IPNS sequence binding mismatch:
embedded=0, response seq=1". The folder SDK E2E path passed because folders
embed 1 (== DB).

Relax both the Rust (verify.rs bind_verified) and JS (sdk-core
resolveIpnsRecord) bindings to accept the single documented skew
(resp_seq == 1 && embedded == 0); strict equality is still required for
every other sequence, and the cid binding stays strict. bind_verified now
returns the DB-authoritative resp_seq (not embedded_seq) so downstream
forward math (next publish = seq + 1) computes 2, not an idempotent re-sign
at 1. Adds a first-publish-skew cross-language vector (case 8) plus Rust and
sdk-core unit tests, including negative scoping tests (embedded=0 with
resp>=2 still rejected).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: e0c89e0b5ef1
Records the publish-path inconsistency surfaced by the #544 resolve-binding
hotfix (FUSE embeds Sequence=0 on first publish, TS SDK embeds 1, API stores
DB=1) and the open question of whether the TEE re-sign path trips the D-09
rollback gate for embed-0 records. Deferred as future hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entire-Checkpoint: 2d090124fa17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:api:feat Minor version bump (new feature) for api release:cipherbox-core:feat Minor version bump (new feature) for cipherbox-core release:cipherbox-fuse:feat Minor version bump (new feature) for cipherbox-fuse release:cipherbox-sdk:fix Patch version bump (bug fix) for cipherbox-sdk release:desktop:fix Patch version bump (bug fix) for desktop release:sdk:fix Patch version bump (bug fix) for sdk release:sdk-core:feat Minor version bump (new feature) for sdk-core release:tee-worker:fix Patch version bump (bug fix) for tee-worker release:web:feat Minor version bump (new feature) for web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant