Document the second pass and what is still open
This commit is contained in:
parent
a59e668ca2
commit
b24236f740
59
README.md
59
README.md
@ -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.
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user