Document the second pass and what is still open

This commit is contained in:
bigpickle 2026-10-05 21:51:23 +08:00 committed by leora
parent 43c55682c7
commit 53136cc38f

View File

@ -41,7 +41,10 @@ python3 sigil.py code bigpickle /tmp/demo.key # hand a human a code
python3 sigil.py redeem X3ZB-NTLG # -> human_via_agent token python3 sigil.py redeem X3ZB-NTLG # -> human_via_agent token
python3 sigil.py whoami "$TOKEN" # kind, scopes, may_act_as_agent python3 sigil.py whoami "$TOKEN" # kind, scopes, may_act_as_agent
python3 sigil.py events bigpickle # audit trail python3 sigil.py events bigpickle # audit trail
python3 sigil.py verify-chain # is the log intact? python3 sigil.py checkpoint /tmp/demo.key bigpickle # sign the audit head
python3 sigil.py verify-chain # chain + checkpoints
python3 sigil.py revoke bigpickle --reason "incident" # or --until UNIX
python3 sigil.py sweep # prune stale rows
``` ```
## Tests ## Tests
@ -50,10 +53,10 @@ python3 sigil.py verify-chain # is the log intact?
python3 -m unittest discover -s tests -v python3 -m unittest discover -s tests -v
``` ```
80 tests. They cover the refusals, not just the happy paths: replayed 106 tests. They cover the refusals, not just the happy paths: replayed
signatures, wrong keys, expired nonces, single-use codes, scope escalation, and signatures, wrong keys, expired nonces, single-use codes, scope escalation, and
the invariant below. `test_hardening.py` has one test per defect found in the invariant below. `test_hardening.py` and `test_production.py` have one test
review, so any of them regressing brings that defect back. per defect found in review, so any of them regressing brings that defect back.
## The invariant ## The invariant
@ -85,9 +88,14 @@ no extra capability. Waiting for a code does not eat into it — an earlier draf
anchored the session to issue time, which quietly handed out less than promised. anchored the session to issue time, which quietly handed out less than promised.
**Nothing is minted from a name.** `issue_code` takes an agent *session*, not an **Nothing is minted from a name.** `issue_code` takes an agent *session*, not an
agent id, and `verify_chain` exists so tampering is detectable. A function that agent id. A human session requires a `GrantedCode`, which only a redeemed code
accepts a string and returns authority is how "who is this?" quietly becomes can produce. A function that accepts a string and returns authority is how
unanswerable. "who is this?" quietly becomes unanswerable.
**Suspension and death are different statements.** `revoke` with no end time is
permanent; `revoke --until` is a pause that lapses on its own. An incident
response that ends should not need a re-enrolment, and a dead identity should
not come back because a clock ran out.
## Status ## Status
@ -96,6 +104,11 @@ A hardening pass closed the defects below. Each is now covered by a test.
| Found in review | Now | | Found in review | Now |
|---|---| |---|---|
| `issue_code()` trusted an agent name, so it minted authority from a string | Takes an agent session; agent derived from it | | `issue_code()` trusted an agent name, so it minted authority from a string | Takes an agent session; agent derived from it |
| Human sessions were mintable from a bare agent name | Requires a `GrantedCode`, constructible only by redeeming |
| Audit log recorded 4 of 8 code characters (20 bits left) | Logs a truncated digest instead |
| No `revoke` operation existed, only an unused column | `revoke` with optional end time; sessions killed, challenges dropped |
| Chain was unkeyed, so a database-level attacker could rewrite it | Signed checkpoints; `verify-chain` checks both |
| No busy timeout, so concurrent writers failed instantly | `busy_timeout`, WAL, rollback on error |
| One-time code redeemable many times in a race (753 times in 200 trials) | Atomic compare-and-set; 300 races, one success each | | One-time code redeemable many times in a race (753 times in 200 trials) | Atomic compare-and-set; 300 races, one success each |
| Bootstrap secret spendable more than once | Atomic compare-and-set | | Bootstrap secret spendable more than once | Atomic compare-and-set |
| Signed nonce verifiable more than once | Nonce claimed before the signature is judged | | Signed nonce verifiable more than once | Nonce claimed before the signature is judged |
@ -109,21 +122,25 @@ A hardening pass closed the defects below. Each is now covered by a test.
Still open, and these are the reasons not to ship it yet: Still open, and these are the reasons not to ship it yet:
- **No independent review.** Everything above is self-review. Self-review finds - **No independent review.** Everything above is self-review, twice over. Self-
the bugs you thought of. It does not find the ones you did not. This needs review finds the bugs you thought of; it does not find the ones you did not.
someone else, ideally a cryptographer, before it guards anything real. This needs someone else, ideally a cryptographer, before it guards anything
- **The chain is unkeyed.** Each event folds in the previous digest, so an real. This is the blocking item, and the only one that code cannot close.
accidental edit or deletion is caught. An attacker with write access to the
database can recompute the chain. For a real commitment, checkpoint the head
digest somewhere the database cannot reach, or sign it.
- **The service around this is unwritten.** TLS, CSRF, session transport, secret - **The service around this is unwritten.** TLS, CSRF, session transport, secret
storage and the HTTP routes are not here. The library refuses to mint storage and the HTTP routes. The library refuses to mint authority from a
authority from a name; a route that forgets to pass a session still cannot, name, so a route that forgets to authenticate fails closed, but that is only
but that is only as good as the integration. as good as the integration.
- **Revocation has no expiry semantics.** Revoking works; scheduled or - **Checkpoints need somewhere to live.** A signed checkpoint inside the same
self-expiring revocation does not. database is still only as good as the database. Real deployment wants the head
- **No key recovery.** Rotate and you lose the old sessions. There is no escrow, digest anchored off-box — published, or countersigned elsewhere — and this
which is a deliberate choice, not an oversight. does not do that for you.
- **No key recovery.** Rotate and the old sessions die, which is the point. There
is no escrow. Deliberate, not an oversight.
- **Rate limits are in-process.** They are per-`Store`, so several processes
behind a load balancer each get their own quota. Fine for a single writer,
wrong for a fleet.
- **SQLite, single node.** Fine for one agent or a modest service. It does not
shard, and there is no replication story.
The tests assert the logic is correct. They do not make it safe. The tests assert the logic is correct. They do not make it safe.