Skip to content

docs: land v2.0.0 plan -- fragmented vault + mediated trust#19

Open
johnzfitch wants to merge 3 commits into
mainfrom
docs/v2.0-plan
Open

docs: land v2.0.0 plan -- fragmented vault + mediated trust#19
johnzfitch wants to merge 3 commits into
mainfrom
docs/v2.0-plan

Conversation

@johnzfitch

Copy link
Copy Markdown
Owner

Summary

v1.1.0 audit-cleanup is merged (#17). This PR lands the planning artifact for v2.0.0 in-tree at docs/v2.0-plan.md so the architectural direction is reviewable alongside the code that will implement it.

The plan captures four shifts surfaced during the v1.1.0 review:

  1. The single-file vault is itself the target. v2 splits storage into small fragments (boot.shard, keys.shard, N content shards named by BLAKE3 pseudonyms) composed at unlock time via a door-knock ordering derived from (boot_seed, session_id). boot_seed is committed at vault creation and immutable; session_id is fresh per unlock. Single-fragment compromise reveals nothing. Replay and partial-restore both fail the chain.

  2. Conventional IPC is insufficient. UDS, systemd socket activation, and D-Bus all leave the channel readable to root + ptrace. v2's mediated-trust direction is hardware-backed (TEE / TPM / Secure Enclave). The deliverable for v2.0 is a technical note surveying the available platforms, not a daemon binary.

  3. UEFI is itself an attack surface. The pre-boot daemon idea stays a research outline. Pre-boot chain-of-trust is achieved via TPM 2.0 seal-to-PCR on boot.shard, not via a custom UEFI app.

  4. Code-level hardening rides with v2.0. Trailing-ellipsis ban in UX strings, substantive (non-roadmap) generate_salt rationale, #[serde(deny_unknown_fields)] on every deserialized struct, side-channel posture documented in every crypto module's //! header, exact-length checks on every shard struct.

The historical v1.1.0 plan is preserved as an appendix below the v2.0 sections so the audit trail is intact.

Test plan

  • LC_ALL=C grep -P '[^\x00-\x7F]' docs/v2.0-plan.md -- no output (ASCII-only)
  • Trailing-ellipsis check on UX-string sections of the doc -- only syntactic elisions in Rust/JS code samples remain (same exemption already documented for Rust range syntax and C FFI varargs)
  • Reviewer reads §1 ("Does the fragmentation upgrade security? Honest assessment") and either confirms the construction or pushes back on specific caveats before any code lands

What's NOT in this PR

No Rust code, no Cargo.toml change, no test additions. This is a docs-only PR. Implementation slices will land in follow-up PRs against main once the plan is approved.

https://claude.ai/code/session_01HsmWopQGNx17aGRNx2Dqf4


Generated by Claude Code

v1.1.0 audit-cleanup is merged. This commit lands the planning artifact
for v2.0.0 in-tree so the architectural direction is reviewable
alongside the code that will implement it.

The plan documents four shifts the user surfaced after the v1.1.0
review:

1. The single-file vault is itself the target. v2 splits storage into
   small fragments (boot.shard, keys.shard, N content shards named by
   BLAKE3 pseudonyms) composed at unlock time via a door-knock
   ordering derived from (boot_seed, session_id). The boot_seed is
   committed at vault creation and immutable; the session_id is fresh
   per unlock. Single-fragment compromise reveals nothing. Replay and
   partial-restore both fail the chain.

2. Conventional IPC (UDS, systemd socket activation, D-Bus) is
   insufficient. v2's mediated-trust direction is hardware-backed
   (TEE / TPM / Secure Enclave). The deliverable for v2.0 is a
   technical note surveying the available platforms, not a daemon
   binary.

3. UEFI is itself an attack surface. The pre-boot daemon idea stays a
   research outline. Pre-boot chain-of-trust is achieved via TPM 2.0
   seal-to-PCR on boot.shard, not via a custom UEFI app.

4. Code-level hardening rides with v2.0: trailing-ellipsis ban in UX
   strings, substantive (non-roadmap) generate_salt rationale,
   #[serde(deny_unknown_fields)] on every deserialized struct,
   side-channel posture in every crypto module's //! header, exact-
   length checks on every shard struct.

The plan also captures the historical v1.1.0 plan as an appendix so
the audit trail is preserved.

File is ASCII-only and conforms to the repo's UX-ellipsis ban.

https://claude.ai/code/session_01HsmWopQGNx17aGRNx2Dqf4

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an in-tree planning artifact for the upcoming v2.0.0 architecture changes, documenting the intended security and storage direction so it can be reviewed alongside the implementation work that will follow.

Changes:

  • Adds a new docs/v2.0-plan.md plan covering fragmented vault storage, TPM sealing, and mediated-trust (TEE/TPM) research scope.
  • Preserves the historical v1.1.0 release plan as an appendix for audit trail continuity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/v2.0-plan.md Outdated
Comment on lines +68 to +79
```
mk = Argon2id(passphrase, boot.shard.salt, ...) # boot-bound, immutable for vault life
session_id = OsRng::next() XOR boot.shard.boot_seed # set when unlock() is called
order_seed = HKDF(mk, "dota-v2-order-seed", session_id)
order[i] = BLAKE3(order_seed, i.to_be_bytes()) # pseudorandom permutation
```

The chain is "set at boot" because `boot_seed` is committed at vault
creation and cannot change. It is "determined dynamically when called"
because `session_id` is fresh on every unlock. The temporal binding is
`(boot_seed, session_id)` -- the first immutable, the second
per-session.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2fa51c2 -- option (a). Split into addr_seed = HKDF(mk, "dota-v2-addr-seed", boot_seed) for the stable storage address (filename never changes for a given shard index) and walk_seed = HKDF(mk, "dota-v2-walk-seed", session_id) for the per-session traversal permutation used for padding and I/O-pattern unlinkability. Per-shard key derivation is now HKDF(mk, "dota-v2-shard", addr[i] || suite) so the encryption key is also stable across unlocks. Updated the migration walkthrough and the v2_shard_construction KAT description to match.


Generated by Claude Code

Comment thread docs/v2.0-plan.md Outdated
Comment on lines +99 to +103
- **Replay resistance**: yesterday's snapshot cannot be replayed
against today's running session because `session_id` is fresh.
- **Partial restoration is impossible**: every legitimate unlock walks
the full `order[]`; a missing shard fails the chain. No subset-of-
secrets downgrade.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Right -- the bullet was claiming something the construction does not deliver. Fixed in 2fa51c2: rewrote the bullet to claim what the per-session walk_seed actually provides (I/O-pattern unlinkability across unlocks) and added a "What this construction does NOT provide" subsection that explicitly calls out anti-rollback as a gap. Section 3 (TPM) now closes that gap with a TPM 2.0 NV_COUNTER allocated at init, incremented on every write transaction, and verified on every unlock against the value stored in the resealed boot.shard. The TPM does not permit the counter to be decremented, so snapshot replay fails the equality check rather than silently succeeding.


Generated by Claude Code

Comment thread docs/v2.0-plan.md Outdated
Comment on lines +320 to +339
- `/home/user/dota/src/crypto/kdf.rs` -- substantive `generate_salt`
comment (2.2)
- `/home/user/dota/src/crypto/aes_gcm.rs`,
`/home/user/dota/src/crypto/hybrid.rs`,
`/home/user/dota/src/crypto/x25519.rs`,
`/home/user/dota/src/crypto/mlkem.rs`,
`/home/user/dota/src/crypto/legacy_kyber.rs`,
`/home/user/dota/src/security.rs`,
`/home/user/dota/src/vault/ops.rs` -- `//!` posture lines (2.4)
- `/home/user/dota/src/vault/format.rs`,
`/home/user/dota/src/vault/legacy.rs` -- `deny_unknown_fields` on
every struct (2.3)
- `/home/user/dota/src/vault/ops.rs:316`,
`/home/user/dota/src/cli/commands.rs:356`,
`/home/user/dota/.github/workflows/ci.yml:50` -- ellipsis cleanup
(2.1)
- `/home/user/dota/Cargo.toml` -- bump to `2.0.0`; add `tpm` feature
(off by default); `tss-esapi` as optional dep; add `blake3`
- `/home/user/dota/src/vault/mod.rs` -- `pub mod shard;`,
`pub mod composition;`, `pub mod migration_v1_to_v2;`

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Stripped the /home/user/dota/ prefix throughout in 2fa51c2. Sections 6 and 7 and the v1.1.0 appendix all use repo-relative paths now.


Generated by Claude Code

Comment thread docs/v2.0-plan.md Outdated
Comment on lines +342 to +352
- `/home/user/dota/src/vault/shard.rs` -- shard struct defs,
encryption/decryption, `boot.shard` / `keys.shard` / content shard
layouts (section 1)
- `/home/user/dota/src/vault/composition.rs` -- door-knock chain:
`mk` + `session_id` -> `order_seed` -> BLAKE3-keyed permutation
- `/home/user/dota/src/vault/migration_v1_to_v2.rs` -- single-file
to fragmented migration
- `/home/user/dota/src/security/tpm.rs` -- TPM 2.0 sealing wrapper
(section 3), `tpm` feature
- `/home/user/dota/docs/v2-threat-model.md` -- the technical note
from section 4's research, published with the v2.0 PR

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2fa51c2 (same sweep as the section 6 paths above).


Generated by Claude Code

Comment thread docs/v2.0-plan.md Outdated
Comment on lines +601 to +618
- `/home/user/dota/Cargo.toml` -- version bump, dep changes, feature gate
- `/home/user/dota/src/vault/migration.rs` -- tombstone helper, tempfile-persist backup, legacy gating
- `/home/user/dota/src/vault/ops.rs` -- call-site insertions for tombstone, M8 fix, L2 path fix, M9 tty gate
- `/home/user/dota/src/cli/commands.rs` -- M1 read_passphrase routing, H1 `--copy` wiring
- `/home/user/dota/src/cli/clipboard.rs` -- **new file** for arboard + std::thread auto-clear
- `/home/user/dota/src/cli/mod.rs` -- clap `Get { copy: bool }`, env-var docs, stale "v6" `about` fix
- `/home/user/dota/src/cli/export.rs` -- M1 read_passphrase routing
- `/home/user/dota/src/crypto/hybrid.rs` -- M2 Zeroizing wrap, H4 legacy gating
- `/home/user/dota/src/crypto/legacy_kyber.rs` -- H4 feature gate
- `/home/user/dota/src/crypto/mod.rs` -- H4 feature gate on `pub use`
- `/home/user/dota/src/crypto/kdf.rs` -- M6 OsRng salt
- `/home/user/dota/src/crypto/x25519.rs` -- L1 rename
- `/home/user/dota/src/crypto/mlkem.rs` -- L3 comment refresh
- `/home/user/dota/src/main.rs` -- M7 non-Linux warning
- `/home/user/dota/src/tui/mod.rs` -- H1 add `copy` shell command, M1 read_passphrase routing
- `/home/user/dota/src/tui/app.rs` -- **delete**
- `/home/user/dota/README.md` -- H1, M4, M7, L5 verbiage, env-var section
- `/home/user/dota/SECURITY-AUDIT.md` -- per-finding resolution annotations

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2fa51c2 (same sweep as the section 6 paths above). The appendix is repo-relative throughout now.


Generated by Claude Code

Five comments, all valid:

1. The "Replay resistance" bullet (section 1) was wrong as written.
   A fresh per-unlock session_id does not prevent snapshot rollback;
   it only randomises the in-session I/O traversal order. Rewrote the
   bullet to claim what it actually delivers ("I/O-pattern
   unlinkability across unlocks") and added a "What this construction
   does NOT provide" subsection that calls out anti-rollback as a
   gap requiring a separate hardware-rooted mechanism.

2. The composition pseudocode coupled the shard *storage address* and
   the per-session traversal order through a single session_id-keyed
   permutation. A fresh session_id would have made yesterday's shard
   files undiscoverable on the next unlock. Split into two derivations:
     - addr_seed = HKDF(mk, "dota-v2-addr-seed", boot_seed)
       deterministic across unlocks; produces stable shard filenames.
     - walk_seed = HKDF(mk, "dota-v2-walk-seed", session_id)
       fresh per unlock; produces the in-memory traversal permutation
       used for I/O-pattern unlinkability and padding.
   Updated the per-shard key derivation to use addr[i] instead of the
   old order[i]; fixed the dangling order[] references in the
   migration walkthrough (section 1.3) and the v2_shard_construction
   KAT description (section 7).

3. Section 3 (TPM sealing) now closes the anti-rollback gap from #1.
   Added an "Anti-rollback monotonic counter" subsection that
   describes a TPM 2.0 NV_COUNTER allocated at init, incremented on
   every write transaction, and verified on every unlock against the
   value stored in the resealed boot.shard. Snapshot replay fails
   the equality check; the TPM does not permit the counter to be
   decremented.

4 & 5. Section 6's Critical Files lists and the v1.1.0 appendix's
   file references used absolute local paths (/home/user/dota/...)
   instead of repo-relative paths. Stripped the prefix throughout
   so the document is consistent with how files are referenced
   elsewhere in-repo and copy/pasteable for contributors.

File is still ASCII-only and still conforms to the trailing-ellipsis
UX-string ban; the residual `...` characters are Rust function-arg
elision inside code samples (same exemption already documented for
the C FFI varargs binding).

https://claude.ai/code/session_01HsmWopQGNx17aGRNx2Dqf4
@johnzfitch

Copy link
Copy Markdown
Owner Author

@claude is this plan truly remarkable?

Sections 1-7 specify primitives and storage. Any sufficiently capable
adversary can model that specification fully. The strength of the
primitives is not the issue -- the *deployment* is mappable.

Section 8 adds a layer whose specific parameterization is chosen by
the user at deploy time and never appears in source. It does not add
cryptographic strength; it adds deployment unpredictability so that
an attacker who has read every line of dota still does not know what
this particular vault actually looks like.

Companion insight: bound the adversary's strategy space by making
the game's termination structure unknown to them. An attacker who
cannot precompute "I get N attempts in T hours, against M shards of
which K are real" cannot run a fully-optimized strategy.

Six mechanisms, all opt-in, all defaulted off:

  8.1 Decoy shards (indistinguishable from real ones, M and K chosen
      by the user, recorded only in TPM-sealed boot.shard).
  8.2 Knock pattern (user-defined choreography required at unlock in
      addition to the passphrase; stored as a Merkle commitment).
  8.3 Honey-token re-shard trigger (touching a tripwire decoy
      re-derives addr_seed on the next save, invalidating the
      attacker's reconnaissance).
  8.4 Duress passphrase + honey-vault (standard plausible-deniability
      primitive; coerced unlock yields plausible-fake secrets).
  8.5 Variable-time unlock backoff (failed attempts trigger backoff
      drawn from a user-configured distribution).
  8.6 Cipher-suite selection from a permitted set (per-vault choice;
      attacker has to compromise boot.shard before knowing which
      primitives are in use).

Honest scoping included:
- The "Recovery is the hard secondary problem" subsection acknowledges
  the 20-year UX failure mode of plausible-deniability systems and
  describes the ritual-recovery medium + dry-run command that mitigate
  it.
- The "What this layer does NOT provide" subsection disclaims fully-
  compromised-host scenarios, the user-choreography-equals-passphrase
  footgun, and that this is a protocol-layer defense not a primitive-
  strength improvement.

New tests, new source files, and a separate v2-variability-recovery.md
documentation file are listed in the section's "Critical files" block.

ASCII-only; no trailing-ellipsis UX strings introduced.

https://claude.ai/code/session_01HsmWopQGNx17aGRNx2Dqf4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants