Commit Graph
245 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 5 f0a775d83c docs: restate the review gate around oracles rather than people
The gate asked for a third party; there isn't one. What it was actually for
was catching what the author's model misses, which needs an oracle that is not
the author's judgement — not necessarily another person.

Records the four substitutes in order of how little they depend on already
knowing what to look for, marks differential fuzzing done, and states the
technique's blind spot plainly: it can only find bugs where the two
implementations DIFFER, so a flaw both share by design (like the recovery-split
secrecy defect, found by reading) and anything TypeScript implements alone are
both invisible to it. Those need mutation testing and adversarial framing,
which is what remains.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
2026-08-19 11:45:01 -04:00
thePR0M3TH3ANandClaude Opus 5 dd60eabbcf docs: bring the cutover gate list back in line with what is actually ported
Gate 5 still listed api, semantic, QR and index0 as exclusions, and the
non-gates section still said TS "deliberately does not emit" index0 — all
untrue as of today. Two exclusions remain, both choices rather than unfinished
work: PGP RSA (not reproducible, so refusing beats diverging) and the Textual
TUIs.

Also marks the 2026-08-17 self-review blocker as done — all ten findings are
closed, as are all seven from the 2026-08-18 audit — and notes that transport
is no longer a blocker for the browser extension now that the API exists,
leaving only the design question of whether an extension should talk to the
API over loopback or embed the core.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
2026-08-19 11:19:09 -04:00
thePR0M3TH3ANandClaude Opus 5 8653ee8006 fix(agent): validate put at the daemon boundary; state the two scope semantics (L-5, L-6, L-7)
L-7 — `put` is the only way a fingerprint enters `held`, and every later
handler joins that string onto a path (the vault it opens, the audit log it
writes). The CLI validated the format first, which is exactly why the daemon
could not rely on it: agent.ts's own header says the CLI is untrusted, and the
browser extension speaks this protocol next. `put` now requires 16 uppercase
hex, and additionally that the fingerprint actually belongs to the supplied
seed — a mismatch would unlock a vault the caller did not name and file its
audit records under the wrong profile.

Validation order matters and is now deliberate: cheap format check, then ttl,
then the seed match last, because deriving a fingerprint runs the BIP-39 KDF
and a malformed ttl should not pay for it. This keeps the existing
non-numeric-ttl test exercising the ttl path rather than short-circuiting on
the new check.

L-5 — `label_regex` is unanchored, so a token issued for `prod` also reaches
`not-prod-db`. Left as-is on purpose: Python matches with `re.search`, so
anchoring the TypeScript side alone would make one token mean two different
things depending on which implementation holds it, and would silently narrow
every token already issued. The audit asked for whichever behavior Python has,
documented and in the parity matrix — so the semantics are now stated in
--label-regex help, reported machine-readably as
capabilities().tokens.label_regex_semantics, explained at the enforcement
point, recorded in the compatibility matrix, and pinned by tests that assert
BOTH labels are visible to an unanchored token.

L-6 — the exec allowlist constrains the command word, never its arguments.
The token holder writes the argv and the child gets the secret in its
environment, so an allowlisted binary with an output-file or network flag
hands the secret straight back. Documented in the same three places rather
than changed: argument-level containment means allowlisting full argv
templates instead of command words, which is a token-format change that has to
land in both implementations together.

Tests: malformed and mismatched fingerprints refused with nothing held (path
traversal, wrong case, wrong length, trailing component); unanchored vs
anchored label_regex; capabilities reporting both semantics. Mutation-verified
— disabling either put guard turns the matching tests red.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
2026-08-18 21:54:21 -04:00
thePR0M3TH3ANandClaude Opus 5 eb67dac576 docs: bridge README to the TypeScript rebuild; banner the Python-era docs
The README had zero mentions of the TypeScript implementation -- on the
branch proposing to make it the main line, a reader (including PR #989's
reviewers) would find only the Python product. A bridge section near the top
now says what the rebuild is, its status (under independent review; do not
put irreplaceable secrets in it yet), and where the migration guide, the
vault/identity spec, and the cutover plan live. Deliberately a bridge, not a
rewrite: everything below it still accurately describes the Python version,
which is what install.sh installs today. Advertising the TS implementation
as the product before its review lands would be docs describing an
aspirational state -- the exact failure mode this branch has been stamping
out.

The 14 Python-era planning docs most likely to mislead (tui_v2/v3,
index0/atlas, semantic) carry a one-line legacy banner so a reader can tell
a historical record from a living document.

The full TS-first rewrite -- README, landing page, docs triage, CI scoping,
dependabot config -- is specced as a merge-day checklist under the cutover
item in TODO.md, so main never advertises a stale product in either
direction.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
2026-08-18 14:07:53 -04:00
thePR0M3TH3ANandClaude Opus 5 0482bc3dfd feat(cli): .seedpass backup naming (phase 1 -- convention only, zero format change)
A portable backup is an encrypted, checksummed, cross-impl-verified file;
its only sin was landing on disk as an anonymous .json. Exports now
self-identify: point `vault export` (or the TUI database export) at a
directory and the file is named seedpass-<fingerprint>-<date>.seedpass --
tied to the profile it restores and sortable by date. The TUI also appends
.seedpass to an extensionless destination, since interactive input has no
script-compatibility concerns.

The zero-risk boundary, kept deliberately: an explicit file path is used
exactly as typed -- a script that exports to backup.json still gets
backup.json -- and imports sniff content, never extensions, so any old
export restores unchanged. No byte of the format moved, which is what makes
this safe to ship ahead of the review gate while format_version 2 (magic
header) and the OS icon association stay queued behind it.

Tests pin all three behaviours in both surfaces: directory target gets the
generated name, extensionless TUI input gets the suffix, explicit paths pass
through untouched. 158 CLI tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
2026-08-18 13:48:36 -04:00
thePR0M3TH3ANandClaude Opus 5 7748d6b5e9 docs: capsule-unlock spec section (11.1) and backup/UX roadmap items
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
2026-08-18 13:41:29 -04:00
thePR0M3TH3ANandClaude Opus 5 cdd74f379f feat(cross-impl): Phase L proves foreign data survives a full A->B->A cycle
The round-trip conformance check from spec section 8.6, now enforced in CI:
a vault holding a foreign record (unknown kind, nested foreign data, and
trap fields) plus an unknown top-level index key goes through an interleaved
Python -> TypeScript -> Python read-modify-write cycle, and every foreign
byte must come back exactly as planted. Allocation must skip past the
foreign id, and TS must refuse to reveal the record rather than guess.

Landing the check forced the normalization decision it existed to force.
Python's _normalize_entry_defaults ran its legacy renames on every entry,
foreign ones included -- and those renames are migrations for OUR old
shapes: the negative control shows the old code turning a foreign
"blacklisted" string into our "archived" flag, which is not backfill but
reinterpretation of another application's data. The normalizer now skips
unknown kinds entirely, so both implementations carry foreign records
byte-for-byte untouched and produce identical canonical state -- the
property sync convergence is defined by. Entries with neither kind nor
type are pre-kind legacy Python data and still normalize as before.

The spec's gap ledger entry for the divergence is resolved, and the Python
tolerance tests are tightened from field-subset to exact equality, plus a
trap-field test pinning that blacklisted/website/words on a foreign record
mean whatever the other application says they mean.

Verified: with the normalizer gate stashed, Phase L fails on the exact
mangling ("archived": "foreign meaning, not our archive flag"); with it in
place, 38/38 cross-impl checks and 1183 Python tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
2026-08-18 13:36:46 -04:00
thePR0M3TH3ANandClaude Opus 5 e434b0abc1 feat(schema): carry unknown entry kinds through instead of refusing the vault
Spec section 8.2: a record whose kind this build does not understand -- one
written by a newer build, or by another application sharing the vault, such
as a future BitLogin namespaced kind -- must be carried through untouched,
never dropped, and must not brick the whole index. TS previously failed the
entire parse on the first unrecognized kind, stranding any shared vault on
older SeedPass builds.

The entry schema is now a union: known kinds validate strictly against the
discriminated union exactly as before, and anything else parses as an opaque
loose record. The refine on the opaque branch is the load-bearing detail --
a malformed record of a KNOWN kind must still fail its own schema, never
fall through and escape validation. Records with no kind or type at all
remain rejected as garbage. Statically the entries map stays typed to the
known kinds (exhaustive switches keep their meaning); the module comment
states the runtime widening plainly.

At the CLI surface, foreign records list with unrecognized fields redacted
to has_* flags -- another application's data is not ours to hand out -- and
reveal refuses with "unsupported entry kind" instead of guessing. Allocation
counts foreign ids, so neighbours never collide with them.

Python's natural tolerance is now pinned by test, which surfaced a real
nuance: Python backfills base-field defaults (tags, links, date_modified)
onto foreign records on load, where TS carries them verbatim. No data is
lost, so both conform to the preservation rule as now worded (no loss, no
reinterpretation; base-field normalization permitted) -- but the two produce
different canonical bytes for the same foreign record, which matters once
foreign kinds sync. Recorded in the spec's gap ledger to align before any
application ships them.

Verified: 4 of the 6 new TS tests fail against the previous schema; 230
core + 152 CLI tests and the cross-impl suite pass with the change.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
2026-08-18 13:26:32 -04:00
thePR0M3TH3ANandClaude Opus 5 df4262e4b6 docs: draft the shared vault & identity spec (v1)
The interop contract for multiple applications operating one deterministic
account tree -- SeedPass as the reference superset, narrower identity
clients (BitLogin) implementing a subset. Extracted from working code, not
designed on paper: every derivation path, container format, and merge rule
in it is the one the cross-impl suite already enforces between Python and
TypeScript, so a new implementation tests against ground truth rather than
prose.

The load-bearing sections: the index-immutability rule (now backed by the
allocation watermark), the two index namespaces (entry id vs totp), the
interop rules that keep applications from needing each other (preservation,
namespacing, no shadow account numbers, round-trip conformance), and a
deliberately scoped-out list -- policy, orgs, payments, workflows are
application features that store data IN the format, not format features.

Includes an honest gap ledger: TS currently fails-closed on unknown entry
kinds where the spec prefers carry-through; that and a round-trip CI check
are recorded as follow-ups in TODO.md.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
2026-08-18 13:17:29 -04:00
thePR0M3TH3ANandClaude Fable 5 363d0edffd docs: record current state of the TypeScript port
Cutover plan gains a session log for 2026-08-15 and an honest status row:
every listed gate is green, and the remaining blocker is that no third party
has reviewed the code. Two rounds of subagent review found nine
criticals/highs that self-review missed, which is why that is treated as
load-bearing rather than optional.

Compatibility matrix gains rows for seed generation -- absent until this
session, so a new user could not create a vault at all -- and for the
interactive mode now that it follows Python's legacy v1 menus.

The exclusion lists in both documents now name TUI v2/v3, QR display and
script checksums, rather than leaving the earlier 'no TUI' claim standing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 18:05:12 -04:00
thePR0M3TH3ANandClaude Fable 5 dce30d669a feat: rebuild interactive mode as Python's legacy TUI menus
The list-and-arrow-keys interface I wrote was not the thing to build. What
`seedpass` opens is a numbered menu tree, and navigating it is typing a
number and pressing Enter, with a blank line to go back -- so an interface
driven by arrow keys and single-letter bindings was unnavigable to anyone who
already knows SeedPass, however well it worked on its own terms.

This follows src/main.py item for item: the same eight main-menu entries in
the same order, the same nine entry types under Add Entry, the same eighteen
settings under the same numbers, the Profiles and Nostr submenus, the entry
action and edit menus with Python's letter keys. Numbering is part of the
interface, not decoration -- 7 then 1 has to still be Settings then Profiles
-- so items this build cannot perform stay in place and say why when
selected, rather than vanishing and shifting everything below them.

Line-based means no raw mode, no alternate screen, and no terminal to
restore, which removes a whole class of failure the previous version had to
defend against. Raw mode is now entered only around a password prompt, where
echo must be off.

The agent-blind rule survives the change: listings and details show metadata
with stored secrets as has_* flags, and a secret appears only through Show
Secret or Copy. Secret Mode -- a setting Python already has -- routes both to
the clipboard instead of the screen, so it means the same thing in both
implementations. The key/value field, hidden custom fields, and seed phrases
are read without echo.

Three settings needed new profile operations: changePassword re-wraps only
parent_seed.enc, since the index is keyed by the seed rather than the
password and a half-applied change would leave the two disagreeing;
writeParentSeed and setProfileName back the profile menu.

Verified through a pty against a real vault -- every menu, adding a password
entry, editing a label, revealing a secret -- and the derived values match
the CLI. 28 menu tests drive the tree through a scripted UI, including that
listings never contain a stored secret and that a non-numeric length is
refused rather than stored as NaN.

128 CLI tests, 220 core tests, 36/36 cross-impl checks. test/nostr.test.ts
is flaky under parallel load (it uses a live relay); it failed once during
this work and passed on rerun and three times in isolation.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 17:15:17 -04:00
thePR0M3TH3ANandClaude Fable 5 1eab3bf137 feat: interactive mode, so the bare command opens the vault
Python's `seedpass` launches its TUI when invoked with no subcommand. The
port printed a help dump instead, which is why running the bare command
looked broken: nothing was wrong with it, there was simply no way in. This
adds the interactive mode that was missing.

Scope is the daily path -- browse, search, copy, reveal, add a password /
TOTP / key-value entry, archive, switch profile -- not a port of Python's v2
and v3 TUIs, which are around 20k lines of Textual between them. Piped or
redirected, the bare command still prints the help: there is no terminal to
drive.

No new runtime dependencies. Node's tty and ANSI directly, rather than a TUI
framework, because everything bundled here shares a process with unlocked
seed phrases and the CLI's runtime surface is currently commander plus the
noble/scure crypto libraries.

The agent-blind rule from plan section 9.3 carries over intact, and matters
more here than in the CLI because this screen renders the whole vault at
once: list and detail views show metadata only, `c` copies without ever
drawing the value, and `r` is the single explicit reveal -- on screen until
the next keypress, then gone from the following frame. Everything is drawn
on the alternate screen buffer, so a revealed secret cannot be recovered by
scrolling back after quitting, and never lands in a terminal log that
captures the main buffer. The key-value field is masked while being typed
for the same reason.

terminal.ts owes the user one thing above all: their shell back. Raw mode
with a hidden cursor is an unusable terminal, so restoration is idempotent
and wired to exit, SIGINT, SIGTERM, SIGHUP, and the uncaught-exception paths
-- verified through a pty for each. Holding raw mode across the whole unlock
sequence rather than toggling it per prompt also fixes a narrow leak: echo
came back on between password attempts, so a password retyped after a typo
was echoed in clear text.

Tests drive the real screens through an injected terminal, including the
redaction properties above. Two harness bugs worth recording, because both
produced confident nonsense: sampling the frame count after dispatching a
key made a synchronous redraw look like no redraw at all, and waiting only
for "the screen stopped changing" returned during the second-long PBKDF2 a
mutation performs, so the next keystroke landed on the previous screen and
typed "q" into a form field instead of quitting.

122 CLI tests, 220 core tests, 36/36 cross-impl checks.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 15:02:06 -04:00
thePR0M3TH3ANandClaude Fable 5 b0e2a309a4 feat: generate a master seed, and fix three bugs found by using the CLI
Driving the CLI from an empty app directory turned up a gap that no test
covered: there was no way to create a vault. Every path assumed you already
had a seed and told you to export SEEDPASS_MNEMONIC, so a new user could not
get started at all. Python has generate_bip85_seed; the port did not.

fingerprint create fills that in with the same construction Python uses --
32 bytes of OS entropy through BIP-85 app 39 index 0. The entropy call sits
outside any try/catch on purpose, carried over from Python's entropy audit:
nothing may catch a CSPRNG failure and substitute a value.

Where the phrase goes is a deliberate choice rather than a default. This is
the one secret in SeedPass with no other copy, so:

  - the destination is validated BEFORE generating, and delivery happens
    BEFORE the profile is created -- a failure at either point leaves
    nothing behind rather than a vault whose seed nobody has;
  - --out writes 0600 with O_EXCL, so an existing file is never clobbered;
  - with no destination and a non-TTY stdout it refuses outright, instead of
    writing the only copy of a seed into a pipe -- a log, a transcript, an
    agent's context.

Also fixed, all found the same way:

  - use --exec always exited 0, so chaining a deploy command after it with
    && reported success even when the deploy failed. The sink child's exit
    status is now the CLI's own.
  - the sink command spec split on whitespace and ignored quotes, so
    --exec 'sh -c "..."' -- the most common form there is -- handed sh the
    fragments of the script and died on an unterminated string.
  - --tags work,dev silently stored a single tag named "work,dev" that no
    search would ever match. Commas and spaces both separate now.

Two normal states reported raw errno: a stopped agent said
'connect ENOENT /.../agent.sock', and an occupied --out path said EEXIST.

The capability map claimed ssh/pgp were unported. Both work and are at
parity with Python; automation branches on that list, so it now describes
this build.

Verified against Python on a TS-generated seed: same fingerprint, same
password, same TOTP code. 320 JS tests and 36/36 cross-impl checks pass.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-15 14:35:39 -04:00
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