Commit Graph
232 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Fable 5 3a50f57b66 docs: correct the capability location in the security model
It moved out of the app directory in the previous commit; the principals
table still described the old location.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:59:11 -04:00
thePR0M3TH3ANandClaude Fable 5 dafae50ca4 security: fix agent-boundary findings from the fourth reviewer
The reviewer that took longest found the most, including one bug that lies
to the user about a security-relevant outcome.

- `agent stop` never sent the capability and never checked the reply. The
  daemon refused the shutdown, the CLI printed {"agent":"stopped"} anyway,
  and the seed stayed in memory and still served owner-mnemonic. The command
  documents itself as wiping held seeds. It now authenticates and reports
  what actually happened.

- The owner capability lived at <appDir>/agent.sock.cap -- inside the very
  directory a scoped agent must be given to find fingerprints.json and the
  socket. A token holder could `unset SEEDPASS_TOKEN` and the CLI would read
  the capability on its behalf. It now lives under XDG_RUNTIME_DIR in a 0700
  parent, created with wx so a pre-planted file is never adopted. This raises
  the bar; it does not make a token a sandbox, and capabilities.ts no longer
  claims otherwise.

- Redaction was a three-key denylist over deliberately loose schemas, so
  `custom_fields` -- including values Python masks as is_hidden -- went to
  any read-scoped token verbatim. It is now an allowlist that recurses into
  custom_fields, reporting labels and presence but never values.

- Authorization ran after the vault was decrypted and after the entry
  existence check, giving an unauthenticated caller an entry-id enumeration
  oracle, no audit record for the misses, and a full PBKDF2 unlock per probe.
  Tokens are now checked first, denials are indistinguishable for missing
  and forbidden entries, and every denial is audited.

- The audit chain had no anchor: deleting the log, or a suffix of it, left
  something that verified perfectly. A signed head now pins the record count
  and tail signature. Separately, `loaded` was latched before the read that
  could throw, so one transient EACCES made every later append sign from
  genesis and broke the chain permanently.

- The exec allowlist -- documented as the only real containment for a `use`
  grant -- was bypassable via the clipboard (a session-wide channel) and had
  no CLI flag at all, so no shipped token could carry one. Both fixed.

- Unauthenticated connections could buffer without bound, growing to
  gigabytes while each chunk rescanned the accumulated string. Line size and
  concurrent connections are now capped.

- A token-supplied TOTP timestamp was unbounded, so one use minted codes
  valid decades ahead, outliving the token's TTL. Clamped to a small window.

- Prototype keys ("__proto__", "constructor") resolved to inherited
  properties, passed constraint checks against undefined values and burned a
  use; now an own-property check. The socket was created with the umask mode
  and chmod'd afterwards, leaving a connectable window; it is now created
  under a restrictive umask. TTL was enforced only when a request arrived, so
  an idle agent held the seed indefinitely past expiry; there is now a timer.

220 core + 78 CLI tests green; 36/36 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:58:58 -04:00
thePR0M3TH3ANandClaude Fable 5 5873ce8127 docs: state the agent trust boundary explicitly
Written after a security review had to infer the intended boundary from the
code. Records the principals and how each proves itself, what the capability
file does and does not defend against (nothing, against a same-uid
attacker), what each token scope grants, and the honest limit of the `use`
scope: a holder that chooses the sink command can read the secret, so `use`
buys containment only with an exec allowlist, and accountability always.

Also names the limits rather than burying them: no memory scrubbing, and the
two protocol-level issues shared with Python that cannot be fixed in one
implementation without breaking convergence.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:26:15 -04:00
thePR0M3TH3ANandClaude Fable 5 a1df3cfde0 security: keep secrets off argv, fix packaging, add JS supply-chain CI
Secret input:
- Values that are secrets can now be read from stdin: `entry add key-value
  --stdin`, `entry add totp --secret-stdin`, `entry add document --stdin`,
  and `entry modify --value-stdin`. Arguments are visible in shell history
  and process listings, so the positional forms now warn (without echoing
  the secret they are warning about) rather than silently accepting.

Packaging (the published package was broken):
- `exports` pointed at src/index.ts while `files` shipped only dist/, so an
  installed package had no entry point; exports now points at the bundle.
- A `workspace:*` runtime dependency would have failed any real install. The
  bundle inlines everything, so the package now declares no runtime
  dependencies at all. Verified by packing the tarball, installing it into a
  clean project and running the binary -- and CI now does the same on every
  change, because this class of mistake is invisible to build and test.
- The workspace globs cover apps/* so a future extension cannot sit outside
  CI.
- @types/node aligned with the advertised Node 22 runtime.

Release and supply chain:
- release-integrity now builds the CLI bundle into dist/ before checksums
  are generated and signed, so the executable TypeScript artifact is covered
  by the same manifest and cosign signature as the Python ones.
- A new javascript-supply-chain job runs `pnpm audit` and produces a
  CycloneDX SBOM; the JS tree previously had neither.

Also documents two findings that are protocol-level and shared with Python,
where changing the port alone would break convergence: tombstone retention
allowing deletion replay after the 2048-entry cap, and SSH/PGP sharing a
derivation domain (same index yields the same Ed25519 key). Both need a
versioned change in both implementations and are recorded in the
compatibility matrix rather than silently patched here.

193 core + 70 CLI tests green; pnpm audit clean.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:13:21 -04:00
thePR0M3TH3ANandClaude Fable 5 1864343084 cutover: close gates 5, 6 and 7
Gate 5 (feature parity) -- document import/export ported: `entry
import-document` reads a text file (label defaults to the file stem, type
from the extension) and `entry export-document` writes one back, reproducing
Python's filename sanitization, directory-vs-file destination handling and
overwrite refusal. That was the last concrete item; the three remaining
exclusions (PGP RSA, semantic, api) are now documented decisions rather than
open work, and none of them blocks cutover.

Gate 6 (packaging) -- js/packages/cli/build.mjs bundles the CLI into a
single self-contained ESM file with no runtime dependencies beyond Node 22,
writes a .sha256 beside it, and is wired up as the package `bin` and
`prepack` so npm install/pack produce a working command. --version now
reports a real version stamped at build time. CI builds the bundle and
smoke-tests it from a directory with no node_modules -- version,
capabilities, profile creation, entry creation, reveal -- then uploads it.
Verified locally that the bundle runs standalone outside the workspace.

Gate 7 (migration and rollback) -- docs/typescript_migration_guide.md: the
migration is a no-op (same profiles), so the guide covers what to back up,
how to rehearse against a copy with SEEDPASS_APP_DIR, the two behavioral
differences users will notice, the known exclusions, and seed-only recovery.
Rollback is proven rather than asserted: new cross-impl phase K drives a
profile through the TS CLI -- several entry kinds created, one edited, one
archived -- then checks Python reopens it, sees every change, derives the
same secrets, and can still write to it afterwards.

Also adds js/packages/cli/README.md documenting the reference-first output
model and the scoped-token workflow.

193 core + 49 CLI tests green; 36/36 cross-implementation checks green.
All eight cutover gates are now green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 21:33:28 -04:00
thePR0M3TH3ANandClaude Fable 5 c2d7306bb7 parity: PGP key derivation, byte-for-byte with PGPy
The last unported derivation, and the one the plan flagged as highest risk.
It turned out to be fully reproducible: the key material is deterministic,
EdDSA signatures are deterministic (RFC 8032), and the packet layout is
fixed -- so the armored blocks match exactly, with no OpenPGP dependency.

core/derive/pgp.ts emits the packets directly: new-format headers, the
secret-key packet (unencrypted MPI plus 16-bit checksum), the user-id
packet, and the self-certification signature with PGPy's exact subpacket
set and order, wrapped in ASCII armor with a CRC-24 footer. Verified
against PGPy across two seeds, three indices and three user-id shapes --
armored private key, armored public key, and fingerprint.

RSA is deliberately unsupported: PyCryptodome's seeded prime search is not
reproducible, so the TS port refuses the key type rather than silently
deriving a different key.

Two things worth recording from the build: the v4 fingerprint reproduces
from first principles (SHA-1 over the public-key packet), which is what
made byte-parity look feasible before committing to it; and the only
mismatch in the first run was packet framing -- PGPy uses new-format
headers where I had emitted old-format.

CLI: `entry add pgp`, reveal (armored private key), `entry pgp-public`.
Cross-impl phases A/B now cover a pgp entry in both directions, completing
coverage of every creatable entry kind.

193 core + 47 CLI tests green; 30/30 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:46:24 -04:00
thePR0M3TH3ANandClaude Fable 5 5dcd779a6f fix(python): TOTP codes must honor the entry's period and digits
TotpManager built pyotp.TOTP(secret) with library defaults (6 digits, 30s)
and ignored what the entry recorded, so any imported or derived entry using
other values produced codes the issuing service rejects. The service was
configured from the entry's values -- those govern the code.

- current_code_from_secret and current_code take period/digits
- EntryManager.get_totp_code passes the entry's values for both the
  derived and imported paths
- display_service, menu_handler (totp_list now carries digits) and the GUI
  updated at every call site
- regression tests for imported and deterministic entries with custom
  parameters, asserting against pyotp with the matching interval/digits,
  and confirming default entries are byte-identical to before

Found by scripts/cross_impl_check.py, which flagged it as a divergence
against the TypeScript port. That allowance is now removed: the suite
asserts TOTP equality and reports 30/30 with zero divergences.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:41:04 -04:00
thePR0M3TH3ANandClaude Fable 5 b4f20d417a cross-impl: full feature coverage (30 checks) and two more compatibility fixes
Expanded the cross-implementation suite from 19 to 30 checks so every
feature is exercised against the other implementation, not just the ones
that were convenient:

  G  edits (modify / archive / links) made by either side are seen by the other
  H  deterministic conflict merge produces byte-identical output on both sides
  I  an Argon2id-protected Python profile unlocks in TS
  J  the encrypted config file interoperates both directions

Entry coverage in phases A/B now spans every creatable kind, including a
policy-constrained password and an imported (non-deterministic) TOTP.

Two real bugs this surfaced, both fixed:

- Password entries carrying a `policy` block derived the WRONG password in
  TS. Python's _generate_password_for_entry merges the entry's overrides
  onto the base policy before deriving; TS ignored the block entirely, so
  any entry created with policy flags produced a different password.
  core now exposes passwordPolicyFromRecord and the CLI applies it.

- TS could not read a Python-written config file: Python wraps it in the
  kdf/ct JSON envelope (same class as the snapshot bug fixed last commit).
  loadConfig now parses the envelope; regression test included.

One behavioral divergence found and deliberately NOT papered over:
Python generates TOTP codes with pyotp defaults (6 digits / 30s) even when
the entry records different values, so an imported 8-digit/45s secret
yields different codes in each implementation -- and Python's would be
rejected by the issuing service. The suite now reports it as a [DIVERGE]
line rather than a pass or a failure, and the matrix records it as awaiting
a product decision. TS behavior is believed correct.

173 core + 47 CLI tests green; 30/30 cross-implementation checks green
with 2 documented divergences.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:31:49 -04:00
thePR0M3TH3ANandClaude Fable 5 3ebaced2e3 cross-impl: live Python-publishes -> TS-restores relay round trip
Phase F starts the in-process NIP-01 relay, has the real Python NostrClient
publish a chunked snapshot to it, then restores that snapshot with the TS
CLI on a clean profile and checks the secrets survive.

It immediately caught a genuine restore bug: Python's index files wrap the
ciphertext in a kdf/ct JSON envelope, and a published snapshot carries
whatever the local file held. TS decrypted the fetched bytes directly, so
it failed on anything Python published -- invisible to the TS-only tests
because TS writes bare ciphertext. Restore (and delta replay) now parse the
wrapper first, exactly as decrypt_and_save_index_from_nostr does.

Regression test in relay.test.ts publishes a wrapper-format payload through
a live relay and restores it.

Cutover plan updated: gate 4 (sync protocol) now cites this end-to-end
evidence, and gate 5's remaining list drops SSH and migrations.

171 core + 46 CLI tests green; 19/19 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:24:56 -04:00
thePR0M3TH3ANandClaude Fable 5 1eee053ff0 parity: SSH key derivation (Ed25519, PEM byte-for-byte)
- core/derive/ssh.ts: BIP-85 app 32 entropy used directly as the Ed25519
  private key, serialized as PKCS#8 and SubjectPublicKeyInfo PEM. Both DER
  shapes are fixed for Ed25519, so they are emitted from literal prefixes
  rather than adding an ASN.1 dependency to the crypto path.
- Output matches Python's `cryptography` byte-for-byte across two seeds and
  three indices, including PEM line wrapping.
- CLI: `entry add ssh`, reveal (private key PEM), and `entry ssh-public`
  which prints the public key without touching secret material. The
  OpenSSH authorized_keys line is a TS-only convenience -- Python emits
  only PEM, which ssh(1) cannot consume directly -- and is marked as such
  in the compatibility matrix.
- Cross-impl phases A and B now include an ssh entry in both directions.

One harness fix: PEM values carry a trailing newline that the CLI capture
strips, so the cross-impl comparison normalizes trailing whitespace. The
unit tests still compare exactly.

170 core + 46 CLI tests green; 16/16 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:22:27 -04:00
thePR0M3TH3ANandClaude Fable 5 7407d65294 parity: schema migrations 0->4, so legacy Python vaults open in TypeScript
This was the one remaining gap that could strand a real user: TS refused
any index below schema_version 4, which is every profile written before
tags were added.

- core/schema/migrations.ts ports seedpass.core.migrations exactly,
  including the quirks: v1->v2 copies `website` to `label` and drops it
  only for password entries; `kind` is NOT backfilled (Python leaves
  pre-v2 entries `type`-only and falls back on read). Migration works on a
  copy, so a failure can never leave a half-converted vault.
- parseVaultIndex now migrates on open and still refuses future versions;
  pass {migrate:false} where silent upgrades are unwanted.
- Two bugs this surfaced: the version probe could not read a v0 index (no
  schema_version field at all), and migrated legacy entries failed schema
  validation because the discriminator `kind` was missing. The parser now
  fills `kind` from `type` at validation time -- the same shape Python
  writes for new entries, documented in the matrix.

Evidence: fixtures generated by running Python's own apply_migrations over
v0/v1/v2/v3 payloads, plus cross-impl phase E, which writes a real legacy
v2 index into a Python profile, opens it with the TS CLI, reveals a
migrated secret, and confirms Python still reads the profile afterwards.

162 core + 46 CLI tests green; 16/16 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:19:30 -04:00
thePR0M3TH3ANandClaude Fable 5 0f8ce35c47 cross-impl: prove Python/TS profile interop, fix two compatibility bugs
Fixtures show both implementations compute the same values. This adds the
missing half: proof they can read each other's on-disk profiles and
backups, using each side's real code paths.

scripts/cross_impl_check.py (12 checks, wired into CI):
  A. Python creates a profile -> TS opens the index, derives every secret
     identically, and unlocks it with the master password alone
  B. TS creates a profile -> Python decrypts parent_seed.enc and the index,
     recovers the same seed, derives the same secrets
  C. Portable backups round-trip both directions (Python verifies the
     checksum of a TS-written export)
  D. Both implementations reject a bad-checksum mnemonic

Two real compatibility bugs it caught, both fixed:

- TS-created profiles were unopenable by Python. Python only honors the
  recorded parent-seed KDF parameters when kdf.name is exactly "pbkdf2";
  TS wrote "pbkdf2-sha256" with an empty salt and 100k iterations, so
  Python fell back to its config defaults (200k, fingerprint salt) and
  derived a different key. TS now writes exactly what
  PasswordManager._build_seed_kdf_config does, and its own unlock path
  honors the recorded salt and iteration count.

- TS accepted invalid BIP-39 mnemonics. Seed derivation takes any string,
  so a typo'd phrase silently produced a *different* vault -- unopenable
  by Python and unrecoverable by the user. Mnemonics are now validated
  wherever they enter (profile creation and SEEDPASS_MNEMONIC), with an
  error that says why it matters.

Also: docs/typescript_cutover_plan.md records the gate list for promoting
TS to main and retiring Python to legacy/reference -- P0 gates 1-4 are
green, 5-7 (feature parity tail, packaging, migration/rollback) are not.
The Python tree stays in CI after cutover as the compatibility oracle.

Regression tests for both bugs. 151 core + 46 CLI tests green,
12/12 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 16:15:19 -04:00
thePR0M3TH3ANandClaude Fable 5 dcb8fc9219 fix: four real bugs found by end-to-end manual testing
Ran the full CLI surface against a freshly generated throwaway seed
(profile -> agent unlock -> all 8 entry kinds -> reveal/sinks -> tokens ->
audit -> backup -> relay sync -> seed-only recovery). Four defects that no
unit test had caught:

- clipboard sink hung the CLI forever. xclip/wl-copy fork a resident
  process to own the X/Wayland selection; it inherited our stdio, so any
  reader of the CLI's stdout blocked indefinitely, and waiting for process
  exit never returned. Now detached with ignored stdio, success declared
  once stdin is flushed.
- 'vault import' read the mnemonic straight from the env, so it failed
  whenever the vault was unlocked through the agent -- the normal path.
- 'vault import' never wrote anything; it only printed a summary. It now
  restores into the active vault, refuses to overwrite a non-empty vault
  without --yes, and keeps the old behavior behind --inspect.
- A corrupt/foreign vault file surfaced the raw codec error "invalid token
  encoding". It now names the actual likely causes.

Also documented the variadic-option limitation that a trailing flag is
lost (commander stops collecting at the next dash-prefixed token): a
quoted command spec now splits on whitespace, no shell involved.

Regression tests for all four. 151 core + 44 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 15:56:34 -04:00
thePR0M3TH3ANandClaude Fable 5 274bc0e1b8 TS port slice 10: scoped tokens, agent-side enforcement, audit chain
The section-9.3 permission model is now working code, enforced where the
keys live:

- Token model (aligned with the Python agent surface): bearer tokens with
  read/use/reveal scopes, entry-kind and label-regex constraints, TTL and
  use counts. Issued and held by the session agent — hash-stored, plaintext
  shown exactly once at issuance; list/revoke commands.
- Token mode: with SEEDPASS_TOKEN set (and no mnemonic), the CLI reads the
  decrypted index and materializes secrets AGENT-SIDE. The constrained
  process never receives the mnemonic, and resolveMnemonic refuses owner
  fallback in token mode — a token can never silently escalate (the test
  suite caught exactly this hole and it is now closed).
- Denials are precise and audited: wrong scope, wrong kind, unmatched
  label, expired, exhausted, revoked. Reads don't consume uses; secret
  deliveries do.
- Audit chain in Python AuditLogger's scheme: sig = HMAC-SHA256(key_index,
  prev_sig + canonical_payload), genesis 64 zeros, per-profile audit.log;
  agent records unlocks, token lifecycle, index reads, deliveries and
  denials (never secret values). audit-verify/audit-tail commands; a forged
  record breaks the chain in tests.
- 151 core + 41 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 15:06:02 -04:00
thePR0M3TH3ANandClaude Fable 5 e84a7e1c0e TS port slice 9: relay transport + nostr sync/restore — Milestone 5 complete
- core RelayPool over the platform WebSocket (global in Node 22+/browsers,
  no dependency): multi-relay publish with per-relay OK collection, fetch
  aggregation until EOSE with dedupe and signature verification on ingest,
  connect timeouts, dead-relay tolerance.
- Sync flows mirroring nostr/snapshot.py: publishSnapshot (chunk events ->
  manifest with HMAC identity + nonce), fetchLatestSnapshot (newest-first,
  per-chunk hash verify, falls through incomplete manifests), publishDelta,
  fetchDeltasSince (created_at replay order).
- CLI nostr group: get-pubkey (matches app-1237 fixture npub), list/add/
  remove-relay (config-backed, ws:// validation, last-relay guard), sync
  (publish local vault), restore (snapshot + delta merge -> local vault).
- Tested against an in-process NIP-01 mock relay: core round trip incl.
  corrupted-chunk rejection, and CLI end-to-end sync -> destroy local
  vault -> restore with secrets intact. Transport tests are Node-only
  (jsdom WebSocket can't connect; real browsers come with Milestone 6).
- 151 core + 32 CLI tests green. Milestone 5 CLI surface is complete.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 14:51:18 -04:00
thePR0M3TH3ANandClaude Fable 5 841d0984b3 TS port slice 8: profiles, config, and the session agent
- Python-compatible ~/.seedpass layout: fingerprints.json (same shape,
  indent=4), per-profile dirs with parent_seed.enc (kdf/ct wrapper; unlock
  honors argon2id metadata and falls back across pbkdf2 iteration counts),
  index-key-encrypted seedpass_config.json.enc with ConfigManager defaults.
- CLI: fingerprint list/add/switch/remove (remove requires --yes),
  config get/set (JSON-parsed values, pin/password hashes redacted on read),
  profile-aware vault resolution (--vault override, else active profile).
- Session agent (the chosen unlock model): unix-socket daemon holding
  unlocked seeds in memory with TTL, 0600 socket; agent start/status/stop,
  vault unlock (password -> parent seed -> agent, never argv) and vault
  lock. Seed resolution order: SEEDPASS_MNEMONIC env, then agent. This
  daemon is the designed enforcement point for the section-9.3 lease/token
  layer.
- Tests cover the full no-env-mnemonic lifecycle: locked error -> unlock
  with password only -> provision/reveal through the agent -> status ->
  lock -> locked again. 148 core + 29 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 14:15:51 -04:00
thePR0M3TH3ANandClaude Fable 5 4f2b8a79bf TS port slice 7: entry modify/archive/links + util generate-password
- core entryMod: modify_entry port with the per-kind allowed-field matrix,
  policy merging, blacklisted-field cleanup and timestamp touching;
  archive/restore; typed links (add/remove/get with Python's normalization,
  dedupe on target/relation/note, self-link and missing-target refusal).
- Parity fixture entry_mods.json: the real Python EntryManager applies a
  10-op modification sequence at a later pinned clock; TS replays the same
  ops from the creation state and matches byte-for-byte.
- CLI: entry modify/archive/unarchive/links/link-add/link-remove,
  entry totp-codes (explicit egress, honors --at and archived state),
  util generate-password (Python parity: index 0 + gen v1 defaults, full
  policy flags — v1/v2 outputs verified against password fixtures).
- capabilities: egress command list updated.
- 148 core parity tests (Node + jsdom) + 22 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 13:14:25 -04:00
thePR0M3TH3ANandClaude Fable 5 774c992153 TS port slice 6: Nostr event protocol layer with rust-nostr cross-fixtures
- NIP-01 event serialization/id computation, BIP-340 signing and
  verification via @noble/curves, SeedPass event builders (manifest 30070,
  chunk 30071, delta 30072 with d/e tags), and REQ/EVENT/EOSE/OK/NOTICE/
  CLOSED client-relay framing. Transport-agnostic; the WebSocket adapter
  lands with the interface milestones.
- Cross-implementation fixtures generated through the Python side's
  nostr_sdk (rust-nostr) with pinned created_at: TS ids match theirs
  byte-for-byte, their signatures verify in TS, and TS-signed events
  reproduce identical ids. The generator caches signatures across runs
  (BIP-340 aux randomness) so fixture output stays byte-deterministic and
  the CI drift job holds.
- 143 core parity tests (Node + jsdom) + 16 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 21:34:40 -04:00
thePR0M3TH3ANandClaude Fable 5 e91d5d0be2 TS port slice 5: agent-blind provisioning, entry-secret parity, nostr app fix
- core entryOps: add* functions for all creatable kinds rebuild the Python
  EntryManager fixture index byte-for-byte under a pinned clock — field
  shapes, id allocation, independent TOTP derivation-index allocation, and
  Python's ISO timestamp format all verified.
- CLI 'entry add password/totp/key-value/document/seed/managed-account/
  nostr': provision-blind — returns the reference and metadata, never the
  secret the entry represents; tests assert the derived password / imported
  secret / child mnemonic appear nowhere in provisioning output.
- Parity bug found and fixed by the new entry_secrets fixtures: nostr ENTRY
  keys derive via the default BIP-85 app 39 path (EntryManager), not app
  1237 (sync client identity); the CLI had used 1237. Reveal values for
  password/TOTP/nostr/seed/managed-account now match Python retrieval.
- 129 core parity tests (Node + jsdom) + 16 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 21:31:32 -04:00
thePR0M3TH3ANandClaude Fable 5 ab569fe900 TS port slice 4: portable backup, CI, and the agent-blind CLI MVP
- Portable backup (format_version 1): TS imports Python exports, re-exports
  byte-identically under pinned nonce/timestamp, verifies the canonical
  checksum, rejects tampering and unknown versions.
- .github/workflows/ts-parity.yml: typecheck + Node/jsdom parity suite on
  js/ changes, plus a fixture-drift job that regenerates fixtures from
  Python and fails if the committed ones don't match (manifest commit field
  excluded by construction).
- packages/cli — seedpass-js MVP implementing plan section 9.3:
  * reference-first output: entry list/get/search emit sp://entry/<id> refs
    and metadata; secret-bearing fields become has_* flags
  * 'entry reveal' is the only plaintext egress command
  * 'use --clipboard|--exec|--stdin-to' delivers secrets to sinks; --exec
    injects SEEDPASS_SECRET into the child env, never argv or stdout
  * vault export/import over portable backups; capabilities --format json
  * mnemonic comes from SEEDPASS_MNEMONIC env, never the command line
- CLI tests prove the blindness properties: derived password matches the v2
  fixture, TOTP at pinned time, secrets absent from all default output and
  from sink-delivery confirmations.
- 125 core parity tests (Node + jsdom) + 11 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 10:40:56 -04:00
thePR0M3TH3ANandClaude Fable 5 2345f61431 TS port slice 3: Nostr sync model — snapshots, manifests, conflict merge
- Snapshot model parity (kinds 30070/30071/30072): gzip chunking via
  platform CompressionStream, chunk-hash verification, reassembly of
  Python-produced chunks, manifest JSON schema, manifest id
  HMAC-SHA256(key_index, 'manifest|'+nonce) with the key_index HKDF chain.
- Full merge_index_payloads port (modified_ts_hash_tombstone_v2):
  Python-compatible canonical JSON (sort_keys + ensure_ascii) for hash
  parity, equal-ts field-level union, tombstone create/prefer/apply/cap,
  _sync_meta bookkeeping. Populated _system.index0 content refuses to merge
  loudly (atlas-milestone work) instead of silently dropping data.
- Delta replay: encrypted V3 deltas decrypt and merge in order with
  source_tag = sha256(payload)[:16]; final state matches Python.
- New fixtures: nostr_snapshot.json, sync_merge.json (8 scenarios),
  delta_replay.json; generator remains byte-deterministic.
- 117 parity tests green in Node and jsdom.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 10:33:42 -04:00
thePR0M3TH3ANandClaude Fable 5 6d6ad5e2c5 TS port slice 2: entry schemas, password KDF, legacy vault formats
- Zod schemas for all 9 entry kinds (schema_version 4): validate and
  roundtrip the Python EntryManager-generated fixture unchanged; refuse
  future schema versions.
- Password KDF parity: PBKDF2-SHA256 (NFKD+strip, fingerprint salt, 50k/100k
  iters) and Argon2id via @noble/hashes, both fixture-verified.
- Legacy vault decrypt chain matching EncryptionManager.decrypt_data:
  V3 GCM -> V2 GCM -> V2-header-over-Fernet -> raw Fernet (decrypt-only),
  plus the JSON kdf/ct file wrapper with bare-ciphertext fallback; the
  parent-seed file decrypts end-to-end from password.
- Fixture generator grows password_kdf.json and legacy_payloads.json
  (pinned IVs/nonces, fixture-only).
- 100 parity tests green in Node and jsdom.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 10:23:56 -04:00
thePR0M3TH3ANandClaude Fable 5 f720d16208 TS port first slice: fixture generator, js/ workspace, P0 derivation parity
- scripts/generate_ts_port_fixtures.py: deterministic fixtures from public
  test mnemonics (passwords v1+v2, BIP-39/85, TOTP, Nostr keys, managed
  seeds, fingerprints, index keys, entry schemas, V3 vault payload).
  Byte-identical across runs; v1 values match the frozen entropy-integrity
  vectors.
- js/: pnpm workspace with @seedpass/core and @seedpass/test-vectors.
  Core implements BIP-85, password gen v1 (frozen) + v2, TOTP, Nostr keys,
  fingerprints, vault index key, and V3 AES-GCM decrypt using only
  noble/scure crypto + WebCrypto. Strict TS, no Node/DOM deps in core.
- 82 parity tests pass in Node and a jsdom browser-like environment.
- docs/typescript_port_compatibility_matrix.md tracks per-area status.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 10:16:08 -04:00
thePR0M3TH3ANandClaude Fable 5 9a4299d1ae docs: add agent-blind secret lifecycle (provision/reference/use) to TS port plan
Agents provision entries and get back references, never values; use goes
through one-time leases to sinks (clipboard/exec/stdin/extension fill);
reveal is a separate high-risk grant. Reference-first JSON output becomes a
design constraint on the Milestone 5 CLI.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-13 10:03:19 -04:00
thePR0M3TH3ANandClaude Opus 5 3192c9f306 Offer a 24-word master seed, and put the entropy draw beyond reach of any handler
L2: generate_bip85_seed and create_profile_from_generated_seed now take
words_num, accepting 12 or 24 and rejecting anything else with a clear
message. The default stays 12 -- every existing profile uses it, 128 bits is
entirely adequate, and moving the default would change nothing about
existing vaults while surprising anyone creating a new one. 24 is offered
because the master seed protects the whole vault and was the one secret
shorter than the seeds derived beneath it, which already default to 24.

L3: fixed structurally rather than by narrowing exception types.
os.urandom(32) now sits OUTSIDE the try block, so no handler can stand
between the entropy draw and the caller -- an entropy failure is unreachable
by anything that could substitute a value. Also removes a redundant
`except Exception` whose body was identical to the `Bip85Error` branch above
it.

The structural form is stronger than an exception list, and the test says so
precisely: it asserts the OSError reaching the caller is the SAME OBJECT
os.urandom threw, which holds only if nothing caught and repackaged it.
Listing exception types would still leave a handler in the path for someone
to widen later.

Adds five tests: the identity assertion above, 12- and 24-word generation,
the default staying 12, and rejection of 0/11/18/25/-12.

Determinism gate green (89 tests, up from 84). The seed/profile regression
subset is 143 passed with one failure --
test_profile_export_import_round_trip -- confirmed against the pre-change
baseline as pre-existing.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HtNMsKzzFTPURemX7tT19u
2026-07-31 17:43:24 -04:00
thePR0M3TH3ANandClaude Opus 5 7047a5dfed Add an SBOM, and fix the torch randomness nits (L4, L5)
L5: dependency-audit.yml now generates a CycloneDX SBOM from
requirements.lock and uploads it. pip-audit answers "are there known CVEs
today"; an SBOM answers "which exact versions were in this build", which is
the question that arises when something is disclosed later against a release
already in the wild.

Verified against the real tool rather than written from memory -- the first
flag spelling (--output-format / --outfile) does not exist in cyclonedx-py
and would have failed in CI. The working form is
`--of json --output-reproducible -o <file>`, which emits CycloneDX 1.6 with
96 components and records [email protected], [email protected],
[email protected], [email protected], [email protected], [email protected] and
[email protected].0 at exact versions.

L4: torch/services/memory/index.js built its temp filename from Date.now()
plus Math.random() before an atomic rename -- predictable enough to
pre-create or symlink between the write and the rename. Now randomBytes(8).

Also renames the Math.random-derived value in relay-health.mjs from `nonce`
to `probeSuffix` and draws it from randomBytes(6). It is a public probe tag
used to correlate a publish with its readback, not a cryptographic nonce,
and nothing was wrong with it -- but the name invited someone to copy the
pattern somewhere it would matter.

torch has no test/ directory in the repo, so there is no suite to run for
these two files; both were syntax-checked and confirmed to import cleanly.
The SeedPass determinism gate is green (84 tests).

Not done, and recorded as a decision in TODO.md instead: torch/_backups/
holds 5240 tracked files across six dated snapshots. Untracking them is a
large deletion and not something to do as a side effect of an entropy audit.
They currently duplicate every security-relevant grep hit, and bare
`node --test` in torch/ discovers them and fails 18 tests from stale
snapshot code.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HtNMsKzzFTPURemX7tT19u
2026-07-31 17:34:45 -04:00
thePR0M3TH3ANandClaude Opus 5 44935219d1 docs: entropy and RNG integration audit
Audits every security-sensitive random value in src/ against a
Coldcard-class checklist: where each bit of unpredictability originates,
whether that source is present in the production artifact, and what happens
when it fails.

No predictable-fallback defect. Master seed, AES-GCM nonces, TOTP secrets,
Argon2 salts, API tokens and agent leases all come from os.urandom or
secrets and fail closed. The random module is not imported anywhere in
non-test source.

The findings are about how much entropy survives the deterministic
derivation in core/password_generation.py, not about whether it should be
deterministic. Chief among them: _balance_distribution forces exactly L/4 of
each character class into every password, verified 300/300 at five lengths.
That costs 5.7-11.5 bits and, more materially, fingerprints the generator --
a password with exactly a quarter of each class is a strong signal it came
from SeedPass.

Records the compatibility constraint that governs all of it: passwords are
never stored, they are re-derived on demand, and entries carried no
algorithm version. Any in-place change to generate_password was therefore a
data-loss event, not an improvement.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01HtNMsKzzFTPURemX7tT19u
2026-07-31 16:27:05 -04:00
thePR0M3TH3AN 736ad1395c docs: refine TypeScript port plan 2026-06-05 14:58:40 -04:00
thePR0M3TH3AN 5869db84ba docs: add TypeScript web extension port plan 2026-06-05 14:54:17 -04:00
thePR0M3TH3AN 2ab73c50a0 update 2026-03-10 21:39:50 -04:00
thePR0M3TH3AN e79bfd7ba8 docs: update dev control center and v3 plan for 2026-03-10 shipped work
- Health gate: 1145 tests collected, focused suite 181 passed
- Bug fixes: SIGSEGV exit, non-TTY preflight guard, Binding import, profile assertion
- V3 polish: Profiles rename, restore warning guidance, maintenance status icons
- Search graph: multi_hop_neighbors, filtered_neighbors, LinkedItemsPanel enhancements
- Sort/filter: s/f/backslash shortcuts, active toolbar, Clear All button
- Mark Slice A complete; define Slice B deep graph + atlas handoff
- Update recommended next steps to reflect completed work
2026-03-10 21:15:54 -04:00
thePR0M3TH3ANandClaude Opus 4.6 ca31abf33e Fix Overstory multi-agent setup and add quick reference docs
- Update agent models from gpt-5.3-codex to claude-compatible names
  (opus for coordinator/lead, sonnet for builder/scout/reviewer/merger,
  haiku for monitor)
- Switch runtime from codex to claude (codex requires interactive TTY,
  incompatible with tmux-based agent spawning)
- Add Overstory ecosystem links to AGENTS.md and CLAUDE.md
- Add docs/overstory_quick_reference.md with startup, monitoring, and
  CLI commands
- Sync seeds issue state

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-06 13:12:46 -05:00
thePR0M3TH3AN 46ffff577b Improve v3 grid navigation and graph browsing 2026-03-06 07:10:36 -05:00
thePR0M3TH3AN 4011464c31 Advance v3 search, atlas, and comms planning 2026-03-05 23:26:05 -05:00
thePR0M3TH3AN e8a2b81e7b Make TUI v3 the default and add index0 foundations 2026-03-05 21:52:39 -05:00
thePR0M3TH3AN 33e727841c feat(tui_v3): full end-to-end slice implemented for managed sub-accounts including UI breadcrumbs and mock test validations 2026-03-03 10:27:08 -05:00
thePR0M3TH3AN 30b61e76ea test(tui_v3): expand interactive test suite with sub-profile, data io, and editing parity checks 2026-03-03 08:49:11 -05:00
thePR0M3TH3AN 1987d7707e style(tui_v3): full 1:1 ui mockup parity pass with retro black and white theme 2026-03-03 08:29:15 -05:00
thePR0M3TH3AN 55384c1a2f feat(tui_v3): full protocol parity via generalized edit screen and vault I/O 2026-03-03 08:08:10 -05:00
thePR0M3TH3AN 9b3680d45a feat(tui_v3): implement nostr relay management and notification usage parity 2026-03-03 07:56:12 -05:00
thePR0M3TH3AN c822eeb602 feat(tui-v3): implement core functional parity (Phase A/B)
- Implement secure data parity (Reveal/QR/Copy) for SSH, PGP, Nostr, Key-Value, and Documents.
- Update derive_pgp_key and API to support PGP Public Key retrieval and copying.
- Implement managed account loading/exiting (ml/mx) with reactive profile updates.
- Integrate semantic search (keyword/hybrid/semantic) into the entry grid.
- Create AddEntryScreen (unified wizard), SeedPlusScreen (BIP-85), and DocumentEditScreen.
- Add grid filter presets (secrets, docs, keys, 2fa) and archive view toggle.
- Update ActionBar and GridMetrics to be dynamic and context-aware.
- Add comprehensive v3 parity tests and update existing core tests for PGP tuple changes.
2026-03-03 07:40:34 -05:00
thePR0M3TH3AN d3920ccc4c Update TUI v3 plan and land parity fixes with smoke tests 2026-03-02 23:43:07 -05:00
thePR0M3TH3AN 4eeb936e6d docs(test): centralize test infrastructure and add interactive agent harness
Added docs/TEST_INVENTORY.md as a central index for all tests and runners.
Created scripts/interactive_agent_tui_test.py for password-free TUI v2
exploratory testing. Integrated standalone permission tests into main
suite and fixed TUI v2 parity regression tests broken by auto-selection
removal. Updated AGENTS.md and CLAUDE.md with strategic plan references.
2026-03-02 22:26:35 -05:00
thePR0M3TH3ANandClaude Opus 4.6 6f2b9a6d18 fix(tui-v2): handle non-existent entries and tree nav focus feedback
Opening a non-existent entry now shows "Entry not found" instead of
rendering a broken empty board. Tree navigation commands now give
clear feedback when focus is on wrong pane or sidebar is collapsed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-02 21:26:30 -05:00
thePR0M3TH3ANandClaude Opus 4.6 08e71bd1b4 feat(tui-v2): cap card width, fix hint casing, verb-first compact strip
Add max_width=72 to _board_card so Content cards don't stretch across
viewport. Capitalize Nostr QR Private hint. Compact mode global strip
now uses verb-first labels matching normal and dense modes.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-02 21:07:20 -05:00
thePR0M3TH3ANandClaude Opus 4.6 de12289d3e feat(tui-v2): tighten action strip labels and board hint lines
Normal-mode global row now uses verb-first labels with key hints.
Seed and TOTP action hints shortened from raw palette commands to
user-facing verbs for consistent readability.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-02 21:02:40 -05:00
thePR0M3TH3ANandClaude Opus 4.6 5af052499b feat(tui-v2): tighten board density by removing redundant headers
Remove plain-text section labels above card frames since card titles
are self-descriptive. Remove compact-mode noise line. Cap note preview
at 100 chars. Saves 2-4 lines per board for better viewport fit.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-02 20:59:37 -05:00
thePR0M3TH3ANandClaude Opus 4.6 d1807ddab6 feat(tui-v2): extend card framing to all remaining inspector boards
TOTP, SSH, PGP, Nostr, and Seed/Managed Account boards now use
_board_card sections matching Password/Note treatment for consistent
visual language across all 9 board types.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-02 20:54:17 -05:00
thePR0M3TH3AN 0ec4b86076 feat(tui-v2): polish action strip copy and rebalance dense layout 2026-03-02 20:24:58 -05:00
thePR0M3TH3AN 64a8b9f8b5 tui2: harden pane-state flow and dense layout parity 2026-03-02 19:03:21 -05:00