mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-27 07:52:03 +00:00
docs: record the API, CLI, QR and semantic ports, and the index secret leak
Also rewrites capabilities().not_yet_ported so each remaining entry says WHY it is not ported, since automation branches on that list: PGP RSA is a hard constraint (seeded prime search is not reproducible, so a port would derive a different key from the same seed), index0 is carried verbatim but not recomputed, and the agent authorization subsystems and Textual TUIs are choices. Flags one thing that needs a decision: existing semantic index files written before today still contain plaintext secrets, and the fix does not rewrite them. The model id distinguishes them, but relying on a user to read a status field is weak. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
1 parent
39c4e45376
commit
fdbdb12596
2 files changed
+75
-6
No files matched your search
@@ -380,6 +380,52 @@ inverts the point of the review. Do it after.
|
||||
code to `legacy/` (or are scoped down); `ts-parity` and
|
||||
`release-integrity` become the primary gates.
|
||||
|
||||
### Ported on 2026-08-19
|
||||
|
||||
- [x] **CLI gap closure** — `vault stats`, `vault change-password`,
|
||||
`vault reveal-parent-seed`, `entry export-totp`, `config
|
||||
toggle-secret-mode`, `config toggle-offline`, `api stop`. Passwords and
|
||||
tokens come from the environment, never argv. `reveal-parent-seed`
|
||||
follows `fingerprint create`'s egress rule: it refuses to print into a
|
||||
pipe.
|
||||
- [x] **QR codes in the TUI** — byte mode, versions 1-40, all four EC levels,
|
||||
zero dependencies. The block-structure table was extracted
|
||||
programmatically from the reference library rather than transcribed, and
|
||||
the tests compare every module against it. One documented divergence:
|
||||
mask SELECTION, because the reference scores candidates with their
|
||||
format modules blanked (a symbol that is not legal); all eight masks are
|
||||
valid, so the tests pin the mask and check the selection rule
|
||||
separately. Secret Mode suppresses the code — a QR is the secret in a
|
||||
form a camera reads across a room.
|
||||
- [x] **Semantic (retrieval) index** — it was never vector search; it is
|
||||
Jaccard overlap over tokenized metadata, which is why it was portable.
|
||||
Ported to core, the CLI (`semantic build/status/search`) and the API.
|
||||
**Two Python defects found and fixed while porting:** the index wrote
|
||||
stored secrets to a plaintext 0664 file beside the encrypted vault, and
|
||||
entry 0 — the first entry any profile creates — was silently
|
||||
unsearchable. See the security note below.
|
||||
|
||||
### Security note: the semantic index leaked secrets (fixed 2026-08-19)
|
||||
|
||||
`SemanticIndex._extract_text` appended a `key_value` entry's `value` — the
|
||||
stored secret — and `build()` wrote it to `semantic_index/records.json` in the
|
||||
clear, plus a tokenized copy that leaked it just as well. The file was created
|
||||
at the process umask (0664 on a default install), next to a vault that is
|
||||
encrypted and 0600. Anything able to read the profile directory — a backup, a
|
||||
sync client, another user on a shared machine — obtained the secret without
|
||||
the master password.
|
||||
|
||||
Exposure is limited to profiles that actually built an index (it is opt-in and
|
||||
off by default), but **any such index on disk still contains the secrets**:
|
||||
the fix does not rewrite existing files. `MODEL_ID` is bumped to
|
||||
`seedpass-token-overlap-v2` so a stale index is identifiable — `semantic
|
||||
status` reporting v1 means "rebuild this, it holds secrets".
|
||||
|
||||
- [ ] **Decide whether to auto-detect and delete v1 index files on upgrade**,
|
||||
rather than relying on the user to notice the model id. Leaning yes: the
|
||||
file has no value once stale, and leaving it is leaving plaintext
|
||||
secrets on disk.
|
||||
|
||||
### Unbuilt milestones
|
||||
|
||||
- [ ] **Milestone 6 (static/PWA web app)** and **Milestone 7 (browser
|
||||
@@ -387,8 +433,19 @@ inverts the point of the review. Do it after.
|
||||
started — `js/packages/` holds `core`, `cli` and `test-vectors` only. The
|
||||
branch is named for a web extension that does not exist yet; the CLI was
|
||||
the proving ground for the core.
|
||||
- [ ] **Decide whether the `api` (FastAPI) surface gets a TypeScript port at
|
||||
all.** It is currently excluded from cutover gate 5 as "a separate
|
||||
- [x] **(done 2026-08-19) The `api` surface is ported.** `seedpass-js api
|
||||
start` serves /api/v1 on `node:http` with no framework — the CLI ships
|
||||
one audited bundle with an empty production dependency list, and a web
|
||||
framework in a seed-holding process would be the largest supply-chain
|
||||
change in the project. Loopback unless `--allow-remote`; bearer token
|
||||
printed once; routes that produce plaintext also require the master
|
||||
password header; body cap, rate limit, and a tighter unlock-attempt
|
||||
budget that returns 429 even for the right password once spent.
|
||||
Verified end to end that Python, the TS CLI and the TS API derive the
|
||||
same secret for the same entry. Routes belonging to unported subsystems
|
||||
answer 501 naming the feature, not 404.
|
||||
- [ ] ~~Decide whether the `api` (FastAPI) surface gets a TypeScript port at
|
||||
all.~~ (superseded by the above) It is currently excluded from cutover gate 5 as "a separate
|
||||
surface, not vault behavior", and the session agent (unix socket, 0600,
|
||||
scoped tokens) covers agent automation without binding a port — a smaller
|
||||
attack surface for a process holding unlocked seeds. `src/seedpass/api.py`
|
||||
|
||||
@@ -95,11 +95,23 @@ export function capabilities(): Record<string, unknown> {
|
||||
// Automation branches on this list, so it has to describe this build
|
||||
// rather than an earlier one. SSH and ed25519 PGP entries are at
|
||||
// byte-for-byte parity with Python and were wrongly listed here.
|
||||
// Automation branches on this, so each entry says WHY, not just what.
|
||||
// Three of these are choices; one is a hard constraint.
|
||||
not_yet_ported: [
|
||||
"pgp RSA keys (ed25519 is supported; RSA generation is not reproducible)",
|
||||
"index0/atlas",
|
||||
"approval gates and high-risk partitions",
|
||||
"Python's v2/v3 TUIs (interactive mode follows the legacy v1 menus)",
|
||||
// Hard constraint: PyCryptodome's seeded prime search is not
|
||||
// reproducible, so a TS implementation would derive a DIFFERENT key
|
||||
// from the same seed. Refusing beats silently diverging.
|
||||
"pgp RSA keys (ed25519 is at byte parity; RSA generation is not reproducible)",
|
||||
// Python-derived state, recomputed on load. Carried through verbatim by
|
||||
// the TS port so a Python profile round-trips, but not computed here.
|
||||
"index0/atlas (preserved verbatim on read/write, never recomputed)",
|
||||
// Deliberate: agent approval gates, high-risk partitions, job profiles
|
||||
// and recovery split. The session agent's scoped tokens cover the
|
||||
// automation cases; these add a second authorization model.
|
||||
"agent approval gates, high-risk partitions, job profiles, recovery split",
|
||||
// Deliberate: the TS interactive mode follows Python's legacy v1 menu
|
||||
// tree. See docs/tui_v2_cutover_decision.md.
|
||||
"Python's v2/v3 Textual TUIs (interactive mode follows the legacy v1 menus)",
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user