Add IPFS/IPNS relay endpoint details#4
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request updates the CipherBox documentation to reflect a shift from client-side direct IPFS/IPNS publishing to a relay-based architecture where the backend acts as an intermediary. The client now signs IPNS records locally using Ed25519 keys and submits them to the CipherBox backend, which relays them to the IPFS/IPNS network. This maintains the zero-knowledge principle while simplifying client implementation.
Changes:
- Added 4 new IPFS/IPNS relay endpoints: POST /ipfs/add, GET /ipfs/cat, GET /ipns/resolve, POST /ipns/publish
- Updated all sequence diagrams and code examples to reflect the relay architecture
- Incremented documentation version from 1.8.0 to 1.8.1 across all files
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| Documentation/TECHNICAL_ARCHITECTURE.md | Updated architecture principles, sequence diagrams, code examples, and tech stack to reflect relay-based IPFS/IPNS operations |
| Documentation/IMPLEMENTATION_ROADMAP.md | Updated deliverables and milestone descriptions to reflect relay endpoints instead of direct client publishing |
| Documentation/DATA_FLOWS.md | Updated all operation sequence diagrams (upload, download, sync, create, rename, move, delete, update) to show backend relay pattern |
| Documentation/CLIENT_SPECIFICATION.md | Updated tech stack table, FUSE operation descriptions, interface definitions, and questions to reference relay endpoints |
| Documentation/API_SPECIFICATION.md | Added comprehensive documentation for 4 new relay endpoints with request/response formats, rate limits, and error codes |
| 00_START_HERE.md | Updated endpoint count from 18 to 15, table count from 8 to 6, and added note about relay architecture |
| .github/copilot-instructions.md | Updated version to 1.8.1 and clarified ECDSA key usage for decrypting IPNS signing keys |
| .claude/claude.md | Updated version to 1.8.1 and noted that IPFS/IPNS operations are relayed via CipherBox API |
| C->>B: POST /ipfs/add (encrypted metadata) | ||
| B->>IPFS: Add metadata, return CID |
There was a problem hiding this comment.
The sequence diagram is missing a return arrow from Backend to Client after adding metadata to IPFS. After line 420 "B->>IPFS: Add metadata, return CID", there should be a line "B->>C: Return CID" to show that the client receives the CID before signing the IPNS record in line 421. The client needs this CID to create the IPNS record that points to it.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| version: 1.7.1 | ||
| last_updated: 2026-01-18 |
There was a problem hiding this comment.
The YAML frontmatter shows last_updated as 2026-01-18, but the document body still shows "Last Updated: January 16, 2026". These dates should be consistent.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
* Initial plan * Fix date inconsistency in TECHNICAL_ARCHITECTURE.md Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com>
…cture (#17) * Initial plan * Fix all PR review feedback items Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> * Improve documentation clarity per code review feedback Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com>
|
@copilot open a new pull request to apply changes based on the comments in this thread including bumping the version to 1.9 |
#18) * Initial plan * Add BASE64 encoding steps in IPNS sequences and bump version to 1.9.0 Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com>
|
@copilot open a new pull request to apply changes based on the comments in this thread |
* Initial plan * Fix mermaid diagram indentation to use consistent 4-space formatting Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: FSM1 <12774278+FSM1@users.noreply.github.com>
ENVIRONMENTS.md: - Rename section title to "Environment-Specific IPNS Key Derivation" - Replace "environment salt" terminology with "environment context" throughout - Remove unused VITE_WEB3AUTH_NETWORK env var (network derived from VITE_ENVIRONMENT) - Add URL schemes to environment variable reference table REVIEW-2026-01-25-environments.md: - Update findings #1, #3, #4 status to "Addressed" with dates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ion (#49) * docs: add comprehensive environment architecture for multi-env isolation Addresses the Web3Auth key isolation problem where all environments sharing the same project leads to IPNS sequence number conflicts. Documents four environments (local, CI, staging, production) with: - Environment-specific Ed25519 key derivation salt approach - Docker Compose configurations for offline/online IPFS modes - Web3Auth network switching (Devnet vs Mainnet) - Complete env var reference and implementation checklist * docs: add TEE infrastructure analysis to environment architecture Extends ENVIRONMENTS.md with detailed TEE layer implications: - Local/CI: TEE disabled, IPNS can expire, user resets to clean slate - Staging: TEE active with Phala testnet, needs DHT cleanup strategy for orphaned records when Web3Auth testnet IDs become unstable - Production: Full TEE with monitoring, alerting thresholds, and incident runbooks for IPNS staleness detection Includes: - TEE environment matrix and configuration per environment - Staging cleanup job interface for orphaned IPNS records - Production monitoring requirements (staleness, error rates, latency) - Implementation checklist for TEE infrastructure rollout - Incident response runbook for IPNS staleness * docs: add security review for environment architecture Security review of ENVIRONMENTS.md found: - 2 Medium: HKDF parameter naming, random vs derived key migration - 2 Low: Cross-env data accessibility, mock service /reset endpoint - 3 Info: TEE epoch design, Web3Auth shared identity, JWT placeholder Overall assessment: APPROVED WITH CONCERNS Core cryptographic approach is sound, needs clarification before impl. * docs: address CodeRabbit review comments - Clarify HKDF salt vs info usage to match RFC 5869 best practices - Add security warning about cross-environment CID leakage risks - Add safety note for /reset endpoint (local/CI only) - Add language tags to fenced code blocks (bash, text) - Add blank lines around alerting thresholds table (MD058) - Replace absolute paths with repo-relative paths Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: address Copilot and additional CodeRabbit comments ENVIRONMENTS.md: - Rename section title to "Environment-Specific IPNS Key Derivation" - Replace "environment salt" terminology with "environment context" throughout - Remove unused VITE_WEB3AUTH_NETWORK env var (network derived from VITE_ENVIRONMENT) - Add URL schemes to environment variable reference table REVIEW-2026-01-25-environments.md: - Update findings #1, #3, #4 status to "Addressed" with dates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: address CodeRabbit review comments for TEE architecture - Add comprehensive TEE Key Encryption Architecture section: - Document threat model and security goals - Add encryption flow diagram (client -> API -> TEE) - Specify ECIES over secp256k1 as encryption primitive - Document TEE public key distribution with attestation - Define key epoch rotation scheme with 1-week grace period - Add epoch validation logic and error handling - Document forward secrecy considerations - Update security review findings: - Mark TEE epoch rotation finding (#5) as addressed - Add comprehensive private key security test cases: - Test that private keys are never logged - Test that keys are not stored in localStorage/sessionStorage - Test that only encrypted keys are sent to TEE - Test IPNS signature verification prevents forgery Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
- Extract clearAllUserStores() helper used by both useAuth logout and apiClient 401 interceptor, ensuring all stores are cleared on every logout path (addresses Copilot comments #1 and #2) - Extract initialState constant in quota store so reset() and init values cannot drift (addresses Copilot comment #4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: ef84f2610701
* fix(web): clear share and quota stores on logout Share store's clearShares() was defined but never called during logout, causing stale share data to persist across sessions. Quota store had no reset method at all. Both are now cleared in the useAuth logout sequence. Closes M2 tech debt items from milestone audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: b5b5f70621d5 * docs(quick-023): M2 tech debt store logout cleanup Quick task completed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 89f45663f542 * fix(web): centralize store cleanup and address PR review comments - Extract clearAllUserStores() helper used by both useAuth logout and apiClient 401 interceptor, ensuring all stores are cleared on every logout path (addresses Copilot comments #1 and #2) - Extract initialState constant in quota store so reset() and init values cannot drift (addresses Copilot comment #4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: ef84f2610701 * fix(web): add session version guard to quota store fetchQuota Prevents in-flight fetchQuota() responses from repopulating cleared state after reset/logout. Flagged by both Copilot and CodeRabbit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 4d1d4cd4dad6 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Scope Tauri events per attempt via port-specific event names (review #1) - Validate POST requests with server-generated nonce (review #2) - Use absolute deadline instead of per-connection timeout (review #3) - Use actual popup counter value for cleanup (review #4) - Fail fast when all preferred ports are taken (review #6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 172d4ce72f72
…#459) * fix(desktop): use localhost callback server for Google OAuth in Tauri Google OAuth rejects custom URI schemes like `tauri://localhost` which is what `window.location.origin` resolves to in production Tauri builds. This caused Error 400: invalid_request when attempting Google login. Replace the localStorage-polling approach with a temporary localhost HTTP server (Rust-side) that receives the OAuth redirect, extracts the id_token from the URL fragment via an injected callback page, and emits it back to the main webview via a Tauri event. - Add `start_oauth_server` Tauri command with preferred port selection (14200-14202) and random fallback - Rewrite `getGoogleCredential()` to use the callback server + Tauri event listener instead of localStorage polling - Add `core:event:default` permission for Tauri event API access - Content-Length-aware HTTP body reading for reliable POST parsing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 51734dbce9b4 * chore(release): set release targets for PR #459 * fix(desktop): address PR review comments on OAuth callback server - Scope Tauri events per attempt via port-specific event names (review #1) - Validate POST requests with server-generated nonce (review #2) - Use absolute deadline instead of per-connection timeout (review #3) - Use actual popup counter value for cleanup (review #4) - Fail fast when all preferred ports are taken (review #6) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 172d4ce72f72 * docs: implement temporary localhost HTTP server for Google OAuth callback in Tauri Entire-Checkpoint: ac0f39da9ff3 * fix(desktop): address second round of PR review comments - Update JSDoc to reflect fixed-port strategy (no dynamic/loopback) - Validate OAuth state before handling errors (prevents DoS via crafted error) - Loop-read TCP until headers are complete before parsing Content-Length - Update debug doc to match current implementation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 72dff9156bda * fix(desktop): address CodeRabbit review on OAuth callback server - Move cleanup() after state validation to prevent promise hang on mismatch - Use OsRng (CSPRNG) instead of thread_rng for nonce generation - Remove Access-Control-Allow-Origin: * from GET responses (prevents local pages from fetching callback HTML to steal the nonce) - Close OAuth popup windows on server timeout - Gate callback HTML serving to /callback path only (404 for others) - Remove stale review-cycle comment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 60247aaac75c --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
#4 remove swallowed .catch handlers in write-body-reseal green-path tests — RED-phase workaround is no longer needed; let failures surface. #5 return fresh Uint8Array copies from crypto mocks in write-revocation tests so engine fill(0) zeroisation does not mutate shared fixture constants and pollute subsequent tests. #5 same treatment for generateRandomBytes write key mocks. Test 8 in write-revocation now captures fresh copies of sealChildWriteKey arguments via a custom mock implementation, since the engine zeroes the child write key after sealing. #20 zero the minted IPNS private key in publishWriteCapableNode e2e helper after publish in a finally block, narrowing the return type to only ipnsName. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#583) * docs(65): capture phase context Entire-Checkpoint: aed8bf6fa830 * docs(state): record phase 65 context session Entire-Checkpoint: f49b3b2fe198 * docs(phase-65): research write-chain, bin re-link, and invite claim Entire-Checkpoint: ef5c6ef6e756 * docs(65): add validation strategy Entire-Checkpoint: fe7e071404d5 * docs(65): map phase patterns Entire-Checkpoint: 3c9673822b65 * docs(65): create phase 65 write-chain plan (7 plans, 4 waves) WRITE-01 write-body Ed25519 under writeKey (role 0x04), WRITE-02 full Ed25519 write-revocation (child-first cascade), WRITE-03 co-writer re-wrap + offline error, WRITE-04 tombstone-intent; bin re-link + invite re-wrap (Success Criterion 4). Mock-seam discipline (D-02); sdk-e2e round-trip gate (D-04). OQ-1 (BinEntry.nodeReadKey) and OQ-2 (child-first cascade) resolved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUSYqMyCLbNGDLcn1cp1eh Entire-Checkpoint: 9638c8677236 * docs(65): address plan-checker warnings — surface e2e typecheck and mark research OQs resolved Entire-Checkpoint: e025a94f56f3 * docs(65): record planning complete and annotate roadmap waves Entire-Checkpoint: 7a6909081de9 * docs(phase-65): begin phase execution tracking Entire-Checkpoint: 2a373269bfea * test(65-01): add failing role-0x04 write-chain seal KAT and round-trip - round-trip: sealChildWriteKey then unsealChildWriteKey recovers 32-byte key - role isolation: role-0x02 blob rejected by role-0x04 unseal and vice versa - AAD binding: childId / childKind / childGeneration / wrong key all throw - terminal-owner: input buffers unchanged after both calls (D-09 / T-65-04) * feat(65-01): implement sealChildWriteKey and unsealChildWriteKey with role 0x04 - verbatim copy of sealChildReadKey / unsealChildReadKey with role byte 0x02 → 0x04 - AAD: buildNodeAad(childId, kindByte, childGeneration, 0x04) — child-writekey role frozen - no new imports: reuses sealAesGcmAad / unsealAesGcmAad / buildNodeAad from @cipherbox/crypto - D-09 terminal-owner: neither function zeros caller-supplied buffers - exported from packages/core/src/node/index.ts and packages/core/src/index.ts - all 9 role-0x04 tests green (round-trip, cross-role rejection, AAD-mismatch, terminal-owner) * test(65-03): add failing claimInvite service-flow test - RED: claimInvite import fails — function does not exist yet - Asserts getInviteDataFn called once per claim - Asserts insertShareFn called once with no encryptedChildKeys - Asserts two independent claims of same invite yield two separate grants * docs(65-01): complete plan summary for role-0x04 write-chain seal primitives * feat(65-03): implement claimInvite service flow and barrel export - Add claimInvite to packages/sdk-core/src/share/grant.ts composing claimInviteReadKey with injected getInviteDataFn and insertShareFn callbacks - Re-export claimInvite from packages/sdk-core/src/share/index.ts - One re-wrapped root readKey per claimer, no per-child key fan-out - Zero sdk-layer consumption of the fan-out path confirmed by grep gate * docs(65-03): complete invite-claim service flow plan summary * test(65-02): add failing bin re-link spec for addToBin and restoreFromBin - add nodeReadKey and nodeIpnsName fields to BinEntry in @cipherbox/core - un-skip addToBin and restoreFromBin describe blocks - remove legacy originalFolderKeyEncrypted and FolderChild references - add 4 addToBin tests covering happy-path, revoke ordering, abort-on-revoke-fail, and not-loaded guard - add 5 restoreFromBin tests including AEAD asymmetry proof via real sealChildReadKey - switch vi.mock to importOriginal spread keeping real AES-GCM primitives for asymmetry test * feat(65-02): implement addToBin and restoreFromBin as pure key re-link addToBin: - resolve child IPNS record to extract plaintext PublishedNode id and kind - unseal nodeReadKey from source parent folderKey via unsealChildReadKey - revoke shares before destructive folder mutation (fail-closed ordering) - capture nodeReadKey and nodeIpnsName on BinEntry for restore restoreFromBin: - re-seal entry.nodeReadKey under destination folderKey via sealChildReadKey (role 0x02) - build SealedChildRef with re-linked readKeySealed (no content re-encryption) - add restored ref to target folder and publish; remove entry from bin also fix test fixture: use valid UUID for nodeRef.id to satisfy uuidToBytes validator; replace vi.restoreAllMocks with targeted sealSpy.mockRestore to prevent resetting module-level vi.fn mocks (deriveBinIpnsKeypair) in subsequent test cases * docs(65-02): complete bin re-link plan * docs(phase-65): update tracking after wave 1 Entire-Checkpoint: 02004cb54e8b * test(65-05): add failing write-body-reseal tests for rotateOne write-plane preservation - Tests 1-4: assert unsealNode called with nodeWriteKey, sealNode receives real writeKey not all-zeros placeholder, writeBody.ipnsPrivateKey preserved post-rotation - Tests 5-6: fail-closed guard -- writeSealed present without valid writeKey throws - Test 7: nodeKeySource.writeKey threads through BFS to sealNode - All 6 write-plane assertions fail RED against placeholder engine * feat(65-05): thread real writeKey through rotateOne, remove PLACEHOLDER_WRITE_KEY - Add nodeWriteKey to RotateOneParams and nodeKeySource return type - unsealNode now called with nodeWriteKey to recover write-body on rotation - Fail-closed guard: throws when published.writeSealed present but nodeWriteKey is absent or all-zeros, mirroring the IPNS-key guard pattern - writeKeyForReseal = node.writeBody ? nodeWriteKey : empty; used at all three sealNode sites: main reseal and both CAS-409 merge paths - PLACEHOLDER_WRITE_KEY deleted from all three sites - rotateReadFromNode BFS threads nodeKeySource.writeKey at root, child, grandchild and dirty-resume enqueue sites - Existing IPNS fail-closed guard retained unchanged - Folds FLAG-63-U1 / rotateone-placeholder-writekey-phase65 * docs(65-05): complete write-body-reseal plan summary * feat(65-04): implement shared-write on write-body model with WRITE-01 security and WRITE-03 offline error - Reshape SharedWriteContext: writeKey + readKey + publishedNode replaces raw folderKey and ipnsPrivateKey fields - Add publishNodeFn and addToIpfsFn transport seams for mock-testable operations - Add buildChildWriteLink helper sealing child writeKey under parent writeKey via role 0x04 - Add walkChildWriteKey helper walking the write chain via unsealChildWriteKey - Prove WRITE-01 security: unsealNode without writeKey returns no writeBody; read-only holder cannot reach ipnsPrivateKey - Prove NODE-03: SealedChildRef carries no write field; write link lives only in parent writeBody.writeChildren - Implement createSharedSubfolder: mint child keypair, build write-body, seal, publish, add SealedChildRef + WriteChildRef to parent - Implement uploadToSharedFolder: same pattern for file node with AES-256-GCM content encryption via addToIpfsFn - Implement renameInSharedFolder: mutate read-body child name, preserve writeChildren, re-publish parent - Implement deleteFromSharedFolder: remove from children and writeChildren by IPNS name, re-publish parent - Implement updateSharedFile: caller-supplied fileReadKey + fileWriteKey + fileIpnsPrivateKey, re-seal + re-publish - Implement moveInSharedFolder: re-seal child readKey under dest readKey via sealChildReadKey, walk write chain for writeKey re-link - Add CannotWriteUntilRefetchError with stable code CANNOT_WRITE_UNTIL_REFETCH for tombstoned publish targets - addShareKeysFn never invoked across all six operations - All 29 shared-write tests pass including WRITE-01 security, write-link round-trip, and WRITE-03 tombstone tests * docs(65-04): fix stale Phase-65 convention comments in shared-write WriteChildRef.childId is a UUID from crypto.randomUUID matching Node.id, not the IPNS name. Clarify deleteFromSharedFolder itemId semantics and remove incorrect module-header annotation. * docs(65-04): complete plan 04 shared-write write-body model * fix(65-04): reconcile client SharedWriteContext consumers with write-body model * docs(phase-65): update tracking after wave 2 Entire-Checkpoint: 0c43e1d2e603 * test(65-06): add failing write-revocation driver contract test - Specifies rotateWriteFromNode behavior: per-node new Ed25519 keypair + k51 name + writeKey - Asserts child-first cascade, first-publish at 1n, tombstone-intent teeUnenrollFn calls - Asserts co-writer re-wrap via wrapKey and revoked grant drop via deleteWriteGrantFn - Asserts read-plane invariance: no generation bump, no new readKey minted - Asserts parent SealedChildRef.ipnsName re-pointed to new child name - Tests RED because rotateWriteFromNode is not yet exported from rotation engine * feat(65-06): implement rotateWriteFromNode with child-first write-revocation cascade - Exports WriteRevocationCallbacks type and rotateWriteFromNode function from rotation engine - Child-first bottom-up traversal: leaves get new Ed25519 keypair + k51 name + writeKey first - First-publishes each new k51 name at sequenceNumber 1n via createAndPublishIpnsRecord - Fires teeUnenrollFn for each old name after the new name is published - Re-seals write-body with new ipnsPrivateKey and re-pointed writeChildren under new parent writeKey - Updates parent SealedChildRef.ipnsName to new child name while leaving readKeySealed unchanged - Handles co-writer grants: wrapKey for survivors, deleteWriteGrantFn for revoked recipients - Read plane invariant: no readKey minted, no generation bump on any node - Zeros minted writeKey and Ed25519 seeds on failure paths only (D-09 / Pitfall 4) - Adds createAndPublishIpnsRecord and sealChildWriteKey/unsealChildWriteKey to engine imports - All 70 rotation suite tests pass including 8 new write-revocation tests * docs(65-06): complete write-revocation plan summary * docs(phase-65): update tracking after wave 3 Entire-Checkpoint: 1669fd50ff58 * feat(65-07): export rotateWriteFromNode and WriteRevocationCallbacks from sdk-core - Adds rotateWriteFromNode to the sdk-core top-level barrel so the sdk-e2e suite can import it via the package name rather than an internal subpath (Rule 3 fix: missing export blocked the D-04 gate test) - Adds WriteRevocationCallbacks type export alongside the function * test(65-07): add D-04 write-chain rotation E2E suite - Creates tests/sdk-e2e/src/suites/write-chain-rotation.test.ts as the D-04 phase gate for WRITE-02/03/04 against the live docker API stack - publishWriteCapableNode helper seals folder nodes with real write-bodies and first-publishes each new k51 name at sequenceNumber 1n - Test 1: builds a 2-level subtree (root + child), asserts pre-rotation baseline: write-body unseals under writeKey; parent write link unseals to child writeKey; generation 0 for both nodes - Test 2: drives rotateWriteFromNode with vi.fn() callbacks, asserts WRITE-02 (new k51 names + parent re-point cascade), WRITE-04 (teeUnenrollFn per old name), WRITE-03 (survivor re-wrap unwraps with bob private key; revoked grant dropped), and read-plane invariance (generation unchanged) - Derives new IPNS names from captured getRandomValues spy in child-first order (child-ed25519, child-writeKey, root-ed25519, root-writeKey) * docs(65-07): complete write-chain rotation plan summary * docs(phase-65): update tracking after wave 4 Entire-Checkpoint: c40a8c198c21 * docs(phase-65): complete phase execution Entire-Checkpoint: 77baeecc7244 * docs(phase-65): close rotateone-placeholder-writekey todo resolved by plan 65-05 Entire-Checkpoint: f69e0ef17961 * docs(phase-66): re-tag bin-pin-leak todo to phase 66 where apps/api/shares is in scope Entire-Checkpoint: f7bf47fb6251 * docs(65): security audit — all 29 threats closed Static analysis of Plans 01-07 threat models: 29 mitigations verified in implementation (seal.ts role-0x04, engine.ts writeKey threading + PLACEHOLDER removal, shared-write.ts write-body model, bin/index.ts pure re-link, grant.ts claimInvite). Two accepted risks (T-65-07 Q3 sub-share, T-65-09 invite link) documented. threats_open=0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(65): nyquist validation audit — fill coverage map, mark compliant Retroactive audit of phase 65 validation gaps. Maps all 15 tasks across 7 plans to their concrete test files and commands. Marks nyquist_compliant: true; records the one explicitly-deferred item (WRITE-04 live publish-gate) with Phase 66 evidence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Entire-Checkpoint: 4f25369a581a * docs(phase-68): defer shared-write context publishedNode wiring from phase-65 coderabbit review Entire-Checkpoint: 9406964c08bd * fix(65): resolve CR cluster-B findings in shared-write - F1 zeroization: fill(0) minted key buffers before null on success paths in createSharedSubfolder, uploadToSharedFolder, and updateSharedFile - F2 childId alignment: add fileNodeId param to updateSharedFile so Node.id stays aligned with WriteChildRef.childId across content updates - F3 publish order: publish dest before src in moveInSharedFolder so a partial failure leaves a duplicate rather than an orphaned item - F4 sequence number: pass swCtx.sequenceNumber + 1n in resealAndPublishParent so parent updates use the correct next sequence - F5 fail-closed: throw when neither childWriteKey nor a walkable src write link is available; zero derived key in finally (D-09) Tests: update callers for new fileNodeId param; add fail-closed test for moveInSharedFolder; 36 tests pass (was 35) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): fail-closed restoreFromBin and reorder addToBin for atomicity - restoreFromBin: throw instead of defaulting nodeIpnsName/nodeRef to empty values — prevents sealChildReadKey binding to wrong AAD - addToBin: persist bin entry before the destructive folder publish so a failed save cannot orphan the item with no restore key - grant.ts claimInvite: reject empty/whitespace inviteToken, rootNodeId, rootIpnsName before any I/O or crypto work Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): make revocation-failure test self-contained Add setupAddToBinMocks() to stub resolveIpnsRecord and fetchFromIpfs so the test reaches the revocation step independently of prior test cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): defer tombstones post-subtree, tighten key lifecycle, fix childKey zeroing Findings addressed: F5 CRITICAL, F6 major, F7 major, F4 major. F5: move teeUnenrollFn calls from inside rotateWriteSubtree to after the full subtree rotation in rotateWriteFromNode. Adds pendingTombstones[] collection so a failed ancestor publish cannot leave the TEE with a unenrolled child name the parent still references. F6: remove unused newIpnsPrivateKey from WriteRotationResult; zero newKeypair.privateKey immediately after createAndPublishIpnsRecord; zero rootResult.newWriteKey at end of rotateWriteFromNode after grants loop. F7: wrap both unsealChildReadKey and unsealChildWriteKey in a single try/finally so childReadKey is zeroed even when unsealChildWriteKey throws before childReadKey enters the inner try scope. F4: change rotateWriteFromNode return type from Promise<void> to Promise<{ newRootIpnsName: string }> so callers can persist the new root IPNS pointer alongside the grant descriptor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): add missing core mocks, strengthen write-key assertions in rotation unit tests Findings addressed: F3 major, F1 minor, F8 minor. F3: add sealChildWriteKey and unsealChildWriteKey to the @cipherbox/core mock in write-body-reseal.test.ts so the module does not silently export undefined for these symbols if the write-rotation code path is hit. F1: assert sealChildWriteKey call arguments in write-revocation Test 8 -- child write key identity, parent write key shape, and AAD binding args. F8: strengthen write-body-reseal Test 7 to assert both NODE_WRITE_KEY and CHILD_WRITE_KEY appear per-node in the BFS; remove the .catch that was masking rotation errors in GREEN. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): zero captured Ed25519 seeds early and verify survivor write key opens write body Findings addressed: F10 minor, F11 major. F10: call clearCapturedKeys immediately after deriving new IPNS names so Ed25519 seed material does not linger in the spy array until afterAll. F11: after unwrapping the survivor write-descriptor with bob private key, call unsealNode on the new root with the unwrapped key and assert writeBody is present; proves the persisted descriptor is the actual new root write key and not merely some 32-byte value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(65): defer write-chain e2e seed index-stability hardening from coderabbit review Entire-Checkpoint: 0a6ec17e80ef * fix(65): deleteFromSharedFolder write-body filter uses UUID not ipnsName WriteChildRef.childId is a UUID minted at creation time; filtering by params.itemId (an IPNS name) never matched, leaving a stale WriteChildRef that later crashed rotateWriteFromNode. Add childNodeId param to deleteFromSharedFolder (mirroring moveInSharedFolder) so read-body is filtered by ipnsName and write-body by UUID. Also extend updateSharedFile with optional originalCreatedAt / originalVersions so callers can preserve immutable metadata; Phase-68 will supply prior values. Update client.deleteFromSharedFolder signature to thread childNodeId through. Remove the misleading "childId === ipnsName" module-level comment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): cover write-body WriteChildRef removal on delete Add a test that seeds the parent write-body with a WriteChildRef, calls deleteFromSharedFolder with both itemId and childNodeId, then unseals the republished parent envelope and asserts writeBody.writeChildren is empty. Extend buildSealedParent to accept optional writeChildren so tests can seed real write-body state. Update existing delete tests to supply the new required childNodeId param. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): harden write-rotation engine against security edge cases #6 fail closed when unsealed node has no recoverable write body, preventing a read-only node from being promoted to write-capable without key proof. #2+#7 zero each child newWriteKey immediately after sealChildWriteKey (inline in the map callback) and add a post-Promise.all defensive sweep for any unclaimed child results — D-09 terminal-owner rule. #8 check createAndPublishIpnsRecord return value success flag before firing tombstones or grant mutations so a non-throwing rejection cannot leave the write plane inconsistent. #9 assert rootResult.nodeId matches caller-supplied rootNodeId before grant mutations to prevent rewrapping grants for a different node. #10 wrap tombstone and grant callbacks in try/finally so the root write key is zeroed even when a callback throws — D-09 terminal-owner rule. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(65): harden rotation test hygiene for zeroed buffer handling #4 remove swallowed .catch handlers in write-body-reseal green-path tests — RED-phase workaround is no longer needed; let failures surface. #5 return fresh Uint8Array copies from crypto mocks in write-revocation tests so engine fill(0) zeroisation does not mutate shared fixture constants and pollute subsequent tests. #5 same treatment for generateRandomBytes write key mocks. Test 8 in write-revocation now captures fresh copies of sealChildWriteKey arguments via a custom mock implementation, since the engine zeroes the child write key after sealing. #20 zero the minted IPNS private key in publishWriteCapableNode e2e helper after publish in a finally block, narrowing the return type to only ipnsName. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(65): address coderabbit round-2 findings for bin and grant - FIX #16 restoreFromBin: check target folder for existing child by ipnsName before appending restoredItem so a retry after saveBinMetadata failure cannot duplicate the restored child ref. - FIX #11 claimInvite: snapshot ephemeralPrivateKey and claimerPublicKey buffers before the first await so a caller zeroing the inputs mid-await cannot corrupt the subsequent re-wrap; zero the owned copy in finally. - FIX #12 claimInvite: trim rootNodeId/rootIpnsName before using and persisting them so whitespace-padded inputs are not stored as-is. - test(65): add idempotency test for restoreFromBin, trim-input test and snapshot-before-await test for claimInvite; update toBe → toStrictEqual for recipientPublicKey (now a snapshotted copy). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(phase-68): add SDK envelope-return enablers from coderabbit PR review Entire-Checkpoint: 4710f9a40648 * fix(65): hex-encode bin nodeReadKey on the wire so restoreFromBin survives JSON round-trip Entire-Checkpoint: 19c3208fe45e * fix(65): fail closed on non-32-byte bin nodeReadKey and missing deleteFromSharedFolder childNodeId Entire-Checkpoint: c7f2d3a74cdf * fix(65): route shared-write content uploads through pinWithMode to honor BYO pinning Entire-Checkpoint: e99a4ebfa963 * fix(65): fail closed on non-throwing IPNS publish rejection in publishNodeFn The shared-write publishNodeFn closure ignored pubResult.success, so a 2xx relay response carrying success:false would proceed as if the node were committed — leaving the parent's SealedChildRef pointing at an IPNS name that was never published. Guard on success and throw, mirroring rotateWriteSubtree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 9e04bdf55b05 * fix(65): use childRef.generation for bin AAD, not stale envelope generation addToBin reconstructed the child-readkey AAD from publishedNode.generation, sourced from an independent IPNS resolve of the child. A stale-CID serve makes that diverge from childRef.generation (the parent mirror the readKeySealed blob was sealed under), so unsealChildReadKey fails GCM auth closed even when the parent folder state is current. Pass childRef.generation per the §2.6 generation-source rule, matching moveItem and navigate.ts. Also capture nodeRef.generation from childRef so restoreFromBin re-seals under the same generation as the unsealed key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Entire-Checkpoint: 1af3242d5c34 --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
chore: remove duplicate encryptedIpnsKey rename todo entry The encryptedIpnsKey -> encryptedIpnsPrivateKey wire-contract rename is already tracked by the dedicated todo 2026-07-01-rename-encrypted-ipns-key-canonical-field.md (which also covers upgradedEncryptedKey). Finding #4 was added to the write-path hardening todo during PR #585 review resolution before that dedicated todo was noticed, duplicating the item. Remove the redundant entry. Entire-Checkpoint: ea2585e9cec3 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
No description provided.