Drop misleading "or re-enrol" after permanent revoke; enrol refuses an
existing agent before spending the seed. checkpoint() refuses a currently
revoked/suspended signer. README (and DESIGN) test count updated to 156.
Add requirements.txt (cryptography>=43.0.0,<45) and docs/PROD_RUNBOOK.md
requiring SIGIL_SEAL_PASS and SIGIL_ANCHOR on separate storage. Document
info findings R9/R12/R13 in DESIGN; leave R10 domain separation as a
versioned follow-up (not backward-safe).
Plaintext working copy and export backup were creatable world-readable
under a 0700 temp dir. Write via os.open(mode=0600), fsync, unlink on
failure; pin temp dir 0700; remove export after flush. Document SQLCipher
as deferred — seal-on is not an encrypted DB while the process is open.
Tokens/codes/secrets were already digests; a stolen cleartext SQLite file
still leaked the agent roster, revocation state and audit history.
SealedStore / SIGIL_SEAL_PASS wraps on-disk bytes in AES-256-GCM
(PBKDF2-HMAC-SHA256, 480k iters). Working copy is plaintext only while
open (temp file); flush/close re-seals. Incremental path short of
SQLCipher page-level encryption.
11 new tests in test_sealed.py (stolen seal has no SQLite header / no
agent rows; wrong passphrase fails; works with off-box anchor). Suite
130 green.
Part of Quarry 18a5d762. Independent crypto review + platform glue remain.
A signed checkpoint inside the same database is still only as strong as that
file. Store(..., anchor_path=) / SIGIL_ANCHOR now writes the signed tip to a
filesystem path outside the DB (atomic tip file + append-only .log).
verify_external_anchors catches a rewrite even after in-DB checkpoints are
deleted. 8 new tests in test_anchor.py; suite 119 green.
Part of Quarry 18a5d762 (Sigil prod build). Do not merge to main yet:
sealed store and independent crypto review still open.
- put_agent is INSERT OR IGNORE (enrol create-if-absent); update_agent_key
preserves revocation columns so rekey cannot un-revoke
- enrol_rekey and seed_bootstrap refuse while the agent is revoked
- redeem_code uses per-agent code_redeem / code_redeem_fail; anonymous
guesses no longer lock valid codes for other agents
- remove dead duplicate revoke_agent; tests for each finding (111 total)
Do not merge to main yet: independent crypto review, off-box chain head,
sealed store, and platform glue still open.
Replace passcode identity with Ed25519 challenge-response.
- Enrolment consumes a one-time bootstrap secret, after which no static
secret is used on the wire again.
- Nonces are single-use and consumed before the signature is judged, so a
captured or bad signature cannot be retried.
- Sessions are short-lived, scoped, and typed agent vs human_via_agent.
- Human view access uses one-time codes with a 5 minute redemption window
and a 1 hour session, deliberately different numbers: the code is a key,
the session is the door.
- Invariant: only an agent signature may act as an agent. Enforced and tested.
- Append-only audit trail.
54 tests, covering refusals rather than only the happy path.
Reference implementation, not reviewed or hardened. See README Status.