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.
100 lines
3.8 KiB
Markdown
100 lines
3.8 KiB
Markdown
# Sigil
|
|
|
|
Agent identity for the new internet.
|
|
|
|
An agent proves who it is by **signing**, not by holding a passcode. A passcode
|
|
is a credential, so anyone who copies it becomes you. A signature is evidence,
|
|
so forging one requires the private key — which never leaves the agent.
|
|
|
|
```
|
|
a passcode is the identity -> can be copied, leaked in a URL, replayed
|
|
a signature proves the identity -> cannot be forged, cannot be replayed
|
|
```
|
|
|
|
This is a working reference implementation with tests. It is **not hardened for
|
|
production** — see *Status* below.
|
|
|
|
## What it does
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Enrolment** | Bind an Ed25519 public key to an agent, once, using a one-time secret |
|
|
| **Challenge-response** | Platform issues a nonce, agent signs it, platform verifies |
|
|
| **Sessions** | Short-lived, scoped, and typed as `agent` or `human_via_agent` |
|
|
| **View codes** | A human looks at what their agent sees, through a single-use code |
|
|
| **Audit trail** | Append-only record of every identity and access event |
|
|
|
|
## Try it
|
|
|
|
```sh
|
|
export SIGIL_DB=/tmp/demo.db
|
|
|
|
python3 sigil.py seed bigpickle # -> one-time secret
|
|
python3 sigil.py keygen /tmp/demo.key # -> public_b64, fingerprint
|
|
python3 sigil.py enrol bigpickle "$PUB" "$SECRET" # bind key, consume secret
|
|
python3 sigil.py challenge bigpickle # -> nonce
|
|
python3 sigil.py sign /tmp/demo.key "$NONCE" # -> signature
|
|
python3 sigil.py verify "$NONCE" "$SIG" # -> agent session token
|
|
|
|
python3 sigil.py code bigpickle # hand a human a code
|
|
python3 sigil.py redeem X3ZB-NTLG # -> human_via_agent token
|
|
python3 sigil.py whoami "$TOKEN" # kind, scopes, may_act_as_agent
|
|
python3 sigil.py events bigpickle # audit trail
|
|
```
|
|
|
|
## Tests
|
|
|
|
```sh
|
|
python3 -m unittest discover -s tests -v
|
|
```
|
|
|
|
54 tests. They cover the refusals, not just the happy paths: replayed
|
|
signatures, wrong keys, expired nonces, single-use codes, scope escalation, and
|
|
the invariant below.
|
|
|
|
## The invariant
|
|
|
|
**Only an agent signature may act as an agent.** No human session, no lifetime,
|
|
no admin override.
|
|
|
|
```python
|
|
>>> sessions.assert_may_act_as_agent(view_session)
|
|
sigil.errors.AgentImpersonationDenied: human_via_agent session may not act as
|
|
'bigpickle': only an agent signature can
|
|
```
|
|
|
|
This is why human access is a *window*, not a costume. A human who redeems a
|
|
view code is logged as `bigpickle's human`, permanently distinguishable from
|
|
`bigpickle`. If that guarantee erodes, every conversation an agent has becomes
|
|
unreliable, because nothing distinguishes the agent from someone holding its
|
|
credentials.
|
|
|
|
## Two properties worth stating out loud
|
|
|
|
**Expiry and authority are separate axes.** A long session is not a privileged
|
|
session. Scopes come from the issuer and never widen with time. There is a test
|
|
asserting a 24-hour human session has exactly the scopes of a 5-minute one.
|
|
|
|
**Code lifetime ≠ session lifetime.** A code is a key and expires in 5 minutes.
|
|
The session it opens lasts an hour. The code travels in chat, so it is made
|
|
worthless fast; the hour buys reading comfort and no extra capability.
|
|
|
|
## Status
|
|
|
|
Reference implementation. Deliberately not yet:
|
|
|
|
- **Reviewed by anyone.** This is security-critical code. It needs an
|
|
independent cryptographic review before it guards anything real.
|
|
- **Hardened at rest.** Keys and DB are stored unencrypted; the private key file
|
|
is `0600` but that is all.
|
|
- **Rate limited.** `challenge` is unmetered, so brute-force and
|
|
nonce-exhaustion are open.
|
|
- **Transport.** No TLS concerns addressed; assumes a trusted channel.
|
|
- **Agent revocation** exists but has no expiry semantics.
|
|
|
|
The tests assert the logic is correct. They do not make it safe.
|
|
|
|
## See also
|
|
|
|
`DESIGN.md` — the design page: rationale, flows, session stamps, the invariant
|
|
and its reasoning, and what is deferred. |