feat(05): Folder System - IPNS metadata, folder hierarchy, and operations - #39
Merged
Conversation
Phase 05: Folder System - Implementation decisions documented - Phase boundary established
Phase 05: Folder System - Standard stack: ipns, @libp2p/crypto, @libp2p/peer-id packages - Architecture: client-signed IPNS with delegated routing API relay - Folder metadata encryption patterns documented - Pitfalls: Ed25519 key format, sequence numbers, TTL vs lifetime - Code examples for IPNS record creation and publishing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 05-03: Add depth check in createFolder (FOLD-03) - 05-03: Add getDepth helper to exports - 05-03: Document VaultStore auth flow integration - 05-04: Add deleteFile function to Task 1 - 05-04: Add depth validation in handleCreate hook - 05-04: Update must_haves to include file deletion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add FolderIpns entity with unique (userId, ipnsName) constraint - Track ipnsName, latestCid, sequenceNumber for each folder - Store ECIES-wrapped IPNS keys for TEE republishing - Add PublishIpnsDto with validation decorators - Add PublishIpnsResponseDto for publish response
- Add ipns, @libp2p/crypto, @libp2p/peer-id, multiformats dependencies - createIpnsRecord: converts @noble/ed25519 keys to libp2p format - deriveIpnsName: derives k51... IPNS name from Ed25519 public key - marshalIpnsRecord/unmarshalIpnsRecord: protobuf serialization wrappers
- publishRecord() validates base64, relays to delegated-ipfs.dev - Exponential backoff retry on rate limits and network errors - upsertFolderIpns() tracks folder IPNS names and CIDs - getFolderIpns() and getAllFolderIpns() for TEE republishing - Configurable DELEGATED_ROUTING_URL env var
- FolderMetadata, FolderEntry, FileEntry types per RESEARCH.md spec - EncryptedFolderMetadata for storage format - encryptFolderMetadata: AES-256-GCM encryption with random IV - decryptFolderMetadata: restores metadata from encrypted form
…ient - Add IpnsController with POST /ipns/publish endpoint - Add IpnsModule with proper exports for future VaultModule integration - Register IpnsModule and FolderIpns entity in app.module.ts - Update generate-openapi.ts with IpnsController and IpnsService - Regenerate OpenAPI spec and web API client
Tasks completed: 3/3 - Create FolderIpns entity and DTOs - Create IpnsService with delegated routing - Create IpnsController and IpnsModule SUMMARY: .planning/phases/05-folder-system/05-01-SUMMARY.md
- IPNS record tests: createIpnsRecord, deriveIpnsName, marshal/unmarshal - Folder metadata tests: encrypt/decrypt round-trip, security tests - Tests verify compatibility with IPFS network expectations - All 132 tests passing
Tasks completed: 3/3 - Task 1: Add IPNS dependencies and create record functions - Task 2: Create folder metadata types and encryption - Task 3: Add tests for IPNS record and folder metadata SUMMARY: .planning/phases/05-folder-system/05-02-SUMMARY.md
- Add useVaultStore Zustand store for decrypted vault keys - Store rootFolderKey (AES-256) for folder encryption - Store rootIpnsKeypair (Ed25519) for IPNS signing - Store rootIpnsName derived from keypair - Implement memory-zeroing on clearVaultKeys (MEDIUM-02 security) - Document integration flow with auth.store.ts
- Add createAndPublishIpnsRecord for local signing and backend relay - Sign IPNS records locally with Ed25519 private key - Marshal and base64-encode records for API transport - Integrate with @cipherbox/crypto createIpnsRecord and marshalIpnsRecord - Add resolveIpnsRecord stub (Phase 7 implementation) - Create services/index.ts barrel export
- Add useFolderStore Zustand store for folder tree state - Export FolderNode type for folder representation - Track current folder, breadcrumbs, pending publishes - Implement memory-zeroing on clearFolders (MEDIUM-02 security) - Add folder.service.ts with CRUD operations - getDepth: calculate folder depth from root - createFolder: generate IPNS keypair, folder key, ECIES wrap - loadFolder: stub for Phase 05-04 implementation - updateFolderMetadata: encrypt metadata, upload, publish IPNS - Enforce FOLD-03 max depth of 20 in createFolder - Update services/index.ts barrel export
Tasks completed: 3/3 - Create vault store for key management - Create IPNS service for record publishing - Create folder store and service SUMMARY: .planning/phases/05-folder-system/05-03-SUMMARY.md
- Add renameFolder: updates folder name in parent metadata and publishes IPNS - Add deleteFolder: recursively collects CIDs, removes from parent, unpins - Add deleteFileFromFolder: removes file from parent metadata and unpins - Add folder.store removeFolder: clears folder keys and removes from state - Add folder.store updateFolderName: updates folder name in local state - Name collision check on rename with descriptive error
- Add moveFolder: add-before-remove pattern, depth limit check, self-move prevention - Add moveFile: add-before-remove pattern, name collision check - Add renameFile: updates file name in parent metadata with collision check - Add calculateSubtreeDepth: computes max depth for move validation (FOLD-03) - Add isDescendantOf: prevents moving folder into itself or descendants
- Add useFolder hook with createFolder, renameItem, moveItem, deleteItem - Each operation manages loading/error state - Depth limit validation (FOLD-03) on folder creation - Uses add-before-remove pattern for move operations - Updates local folder store state after operations - Add hooks barrel export file (hooks/index.ts)
Tasks completed: 3/3 - Task 1: Implement folder rename and delete operations - Task 2: Implement move operations for files and folders - Task 3: Create useFolder hook for UI integration SUMMARY: .planning/phases/05-folder-system/05-04-SUMMARY.md
Phase 5 verified: - 4/4 plans executed - 6/6 success criteria verified - 9 requirements marked complete (FOLD-01 through FOLD-06, FILE-04, FILE-05, API-05) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CRITICAL: - Add ValidationPipe to API main.ts to enable DTO validation HIGH: - Clear vault/folder stores on logout to zero crypto keys from memory - Clear all stores on token refresh failure - Add @nestjs/throttler rate limiting to IPNS publish endpoint MEDIUM: - Clear intermediate private key material after use in create-record.ts - Add sequence number validation (non-negative) in create-record.ts - Add runtime type validation for decrypted folder metadata - Use chunked base64 encoding for large folder metadata - Strengthen DTO validation (ipnsName, metadataCid, encryptedIpnsPrivateKey) - Sanitize error messages in ipns.service.ts to avoid leaking internals Documentation: - Add security review report (.planning/security/REVIEW-2026-01-21-phase5.md) - Document LOW severity issues for future work Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ThrottlerModule to OpenAPI generator module (required by IpnsController) - Add comprehensive unit tests for IpnsService (24 tests, 100% coverage) - Regenerate OpenAPI spec with proper module dependencies Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
Merged
FSM1
added a commit
that referenced
this pull request
Jul 20, 2026
…ks to trusted reader state F2: ascent authority moves from the candidate into ReaderContext.parent_node_seed; the gate derives the expected keypair from reader state and fails closed when the seed is absent (engine.md:405-406). F1: every seed-bearing structure must match the envelope scope/epoch or it is a cross-epoch replay, rejected at grant-section (engine.md:149-153, #39 D3). F3: the seed blob's AAD must match the envelope scope/epoch/v and the recovered seed must derive the reader's read key via constant-time compare, rejected at unseal (engine.md:406-408 cross-check discipline).
FSM1
added a commit
that referenced
this pull request
Jul 21, 2026
* feat(engine): adoption gate and floor law with sim harness Land the six-stage adoption gate as a pure pipeline over core's verify/unseal functions and the durable floor law, with an N-engine simulation harness on the fake record store. - gate/adoption.rs: the six stages (record verify, commitment verify, grant-section authentication under committed write pseudonyms, strictly-newer sequence, epoch-at-or-above-floor, unseal). Every cryptographic verdict is a core TrustViolation surfaced verbatim; the only engine-domain verdicts are the two floor comparisons. Fail-closed, whole-record rejection. - gate/floor.rs: the floor law — advance only on AAD-confirmed unseal, cold-seed from a re-point object's owner-vouched writeEpoch/minReadEpoch, writeEpoch advances on sight, grant-blob epoch advisory only, monotonic FloorStore regression fail-closed. - tests/adoption_gate.rs: the six-stage matrix (accept + every reject class with its named trust-violation error), the floor-law scenarios, the N-engine adversarial sim (replay/transplant/re-sign on one shared record store, virtual time), and table-mirroring anti-vacuity meta-tests. No crypto, codec, or new error code in the engine; time/entropy never touched. Covered by the existing "Engine Tests" CI gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(engine): fail-safe floor ordering and provenance docs Address the CodeRabbit CLI pre-pass on the adoption-gate floor law: - advance_on_unseal and cold_seed now commit the trust-critical read-epoch (revocation) floor before the sequence/write-epoch floor, so a partial FloorStore seam failure leaves the fail-closed state (idempotent monotonic raises re-converge on the caller's retry). - Document the floor-mutation provenance contract (adopt is the sole advance_on_unseal caller; cold_seed only takes an authenticated RepointObject) and the single-writer invariant that makes the stage-4/5 read-then-advance safe without a CAS pair. The cross-key transactional seam is FloorStore territory (frozen), out of scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(engine): bind adoption-gate ascent, structure, and seed-blob checks to trusted reader state F2: ascent authority moves from the candidate into ReaderContext.parent_node_seed; the gate derives the expected keypair from reader state and fails closed when the seed is absent (engine.md:405-406). F1: every seed-bearing structure must match the envelope scope/epoch or it is a cross-epoch replay, rejected at grant-section (engine.md:149-153, #39 D3). F3: the seed blob's AAD must match the envelope scope/epoch/v and the recovered seed must derive the reader's read key via constant-time compare, rejected at unseal (engine.md:406-408 cross-check discipline). * fix(engine): bind ascent-link AAD to the envelope in the adoption gate The reader-supplied ascent-link AAD (scope/id/epoch/v/structTag) is now cross-checked against the current envelope before open_ascent_link, closing a replay of a link minted under the same parent seed for another node/epoch/version (engine.md:405-406). Fail closed at grant-section on any mismatch. * fix(engine): bind the ascent-link seed and complete the adoption-gate cross-check audit G3: the ascent link's override seed is now cross-checked instead of dropped — it must belong to the envelope epoch and derive the scope root's read key (constant-time), the ascent-link half of the owner-blob/ascent-link/actual-unseal discipline (engine.md:406-408, CONTEXT.md "Ascent link"/"Override seed"). Audit: every candidate- and network-supplied gate input is now bound to the envelope + cross-checked, trusted reader state, or verified by an earlier stage; the only deferral is structure ciphertext_hash recompute (#687). * fix(engine): bind envelope scope to reader and complete the adoption-gate binding graph The scope UUID is not in the read-key KDF, so a foreign-scope envelope still unseals under the reader's key — assert candidate.envelope.scope == reader.scope_id once at unseal, joining envelope.scope, reader.scope_id, and every AAD/structure scope into one equivalence class (engine.md cross-check discipline). Close the seed-blob AAD's free id and structTag edges: aad.id == envelope.id and aad.structTag == the blob-type tag, so seed-blob id/tag join the same graph as scope/epoch/v. Binding graph is now one connected component per {scope, id, epoch, v}, with structTag pinned to its per-structure domain-separation constant. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FSM1
added a commit
that referenced
this pull request
Jul 22, 2026
…731) Binds the recipient X25519 public key into the mailbox sender-signature preimage, closing the cross-recipient relay-lift. Amends #39 D9 (ADR in FSM1/cipher-box-next#57). Reviews: independent trio clean (crypto-privacy, security, simplify) + Greptile clean (sole finding a moot pre-release compat note). CodeRabbit was infra-unavailable (org adaptive rate-limit); merged on the strength of trio + Greptile + full green CI per maintainer authorization. Closes #712.
This was referenced Aug 5, 2026
FSM1
added a commit
that referenced
this pull request
Aug 8, 2026
* docs: name the owner pseudonym seed edge in the KDF catalog #39 D2 left the owner's pseudonym-sign input as "her root secret", which names no field, so OwnerScopeKeys has no production implementor. ADR 0005 settles it as a dedicated owner-pseudonym-seed edge from the login secret, keeping the owner's structure-signing authority off its encryption subkey. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: qualify the cross-repo decision reference on the pseudonym bullet A bare #27 autolinks to this repository's issue 27, not the decision corpus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: qualify every decision-corpus reference across the blueprint A bare #NN in this repository autolinks to a cipher-box PR or issue, not to the wayfinder decision it names, and this repository's counter passed 1000 long ago. Qualifies the 255 corpus references across the blueprint corpus and leaves the 8 genuine cipher-box references bare. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
What's Included
Backend (apps/api)
IpnsModulewith POST /ipns/publish endpointFolderIpnsentity for tracking IPNS names/CIDs (TEE republishing)Crypto Package (packages/crypto)
ipnsnpm package with libp2p integrationFrontend (apps/web)
useVaultStore- memory-only key management with zeroinguseFolderStore- folder tree state managementipns.service.ts- local IPNS signing with backend relayfolder.service.ts- complete CRUD operationsuseFolderhook - React integration with loading/error stateRequirements Completed
Test plan
🤖 Generated with Claude Code