Files
SeedPass/js/packages/cli/test
thePR0M3TH3ANandClaude Opus 5 7f095cba86 feat(agent): port job profiles and recovery split; fix Shamir's sub-threshold secrecy
Recovery split is Shamir over GF(257), one polynomial per secret byte, shares
encoded as sprec1:<label>:<threshold>:<total>:<index>:<digest>:<payload>.
Verified interoperable in both directions on real shares, including multi-byte
UTF-8 and payloads containing colons: Python recovers TS-produced shares and
TS recovers Python-produced ones. Confirmed end to end by splitting a live
parent seed 3-of-5 with the TS binary and recovering it in Python.

**Python's coefficients were derived from the secret, and that is now fixed.**
`_coef` HMAC'd the secret to produce every polynomial coefficient, which made
the entire share set a deterministic function of it. Shamir's defining
property is that any sub-threshold set of shares is information-theoretically
independent of the secret; deriving coefficients from the secret destroys it,
because one share becomes an offline verifier — guess a secret, re-run the
split, compare. Demonstrated recovering "hunter2" from one share of five at
threshold three, before changing anything. It also meant re-splitting produced
byte-identical shares, so a leaked share could never be rotated out without
changing the secret.

The determinism was load-bearing in the wrong way: coefficients were
recomputed inside the per-share loop, so they HAD to be reproducible or the
points would not lie on one polynomial. Fixed by hoisting them out and drawing
from `secrets`. The share token format is unchanged and `recover_secret` is
untouched — recovery is Lagrange interpolation over (x, y) pairs and never
recomputes coefficients — so old shares keep recovering and both versions
interoperate. Zero interop cost for a real security property.

TypeScript never had the defect and its test suite pins it: reintroducing
secret-derived coefficients turns three tests red, including the guessing
attack. Coefficients use rejection sampling because 257 does not divide 2^16,
and a modulo draw would bias them.

Job profiles port straight across, with policy_stamp as the security-relevant
field: sha256 of the canonical agent_policy.json at creation, so a policy
tightened afterwards shows as a mismatch instead of leaving the job running
under rules nobody reviewed it against. The canonical form matches Python's
`sort_keys=True, separators=(",",":"), ensure_ascii=True` — including the
ensure_ascii escaping, which is easy to miss and would otherwise make a
non-ASCII policy hash differently in the two implementations. Verified
byte-identical stamps for both an empty policy and one with non-ASCII values.
An unreadable policy hashes to its own distinct value rather than to the empty
policy, so jobs read as stale rather than silently current.

Recovery drills use the same HMAC-chained log design as the audit log, with
tests that an edited record, a removed record and an unsigned appended record
are all detected.

Every Python API endpoint now has a TypeScript equivalent: UNPORTED_PREFIXES
is empty. The 501 mechanism stays, because the honest answer for a dropped
subsystem is 501 naming the feature rather than 404.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
2026-08-19 10:41:45 -04:00
..