Commit Graph
2 Commits
Author SHA1 Message Date
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 55879eaa1b security: durable vault writes, path validation, and safe restore semantics
Continues the security-review fixes.

Durability and concurrency:
- Vault, config, fingerprint-registry and parent-seed writes are now atomic:
  temp file in the same directory, fsync, rename, then an fsync of the
  directory. Previously the target was truncated and rewritten in place, so
  a crash could destroy the vault.
- The lock now spans the whole read-modify-write cycle, not just the write.
  Serializing writes alone still lost changes -- two commands each read the
  same index, mutated their copy, and wrote in turn. Every mutating command
  goes through mutateVault(), which holds the lock from open to save. A test
  runs five concurrent writers and asserts all five entries survive.
- 0600 is now enforced on existing files too: writeFile's mode argument only
  applies on creation, so a loosened vault stayed loosened.
- The index is schema-validated before it can reach disk.

Path handling:
- Profile fingerprints are validated (16 uppercase hex) before being used in
  any path, and removeProfile additionally checks the resolved directory
  still sits directly under the app directory. A tampered registry could
  otherwise have steered a recursive delete elsewhere.
- Malformed entries in fingerprints.json are dropped on read.

Restore semantics:
- `nostr restore` merges remote state into the local vault instead of
  replacing it. Replacing silently discarded entries created since the last
  sync; that now requires --replace --yes, and any overwritten vault is
  copied aside first.
- Deltas are bound to the manifest they extend (filtered on the e tag, and
  re-checked locally), so a relay cannot replay a validly-signed delta from
  another snapshot lineage into a restore.
- Manifest selection breaks created_at ties by relay order, so a same-second
  republish cannot leave a restore on a stale snapshot.
- mergeIndexPayloads gained an index0 option. The default still refuses to
  merge populated Python atlas state rather than drop it; restore uses
  preserve-current, keeping the local block verbatim, because Python
  recomputes canonical views on load and the default would block every
  restore into a Python-created profile.

193 core + 69 CLI tests green; 36/36 cross-implementation checks green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-14 23:10:20 -04:00