2026-10-05 21:25:47 +08:00
2026-10-05 21:24:48 +08:00
2026-10-05 21:25:47 +08:00
2026-10-05 21:24:28 +08:00
2026-10-05 21:24:19 +08:00
2026-10-05 21:24:31 +08:00

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 implementation with tests and a hardening pass behind it. It is not independently reviewed — 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

export SIGIL_DB=/tmp/demo.db

python3 sigil.py seed bigpickle                       # -> one-time secret
python3 sigil.py keygen /tmp/demo.key                 # -> public_b64, fingerprint
                                                      #    add a passphrase to encrypt
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 /tmp/demo.key         # 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
python3 sigil.py verify-chain                         # is the log intact?

Tests

python3 -m unittest discover -s tests -v

80 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. test_hardening.py has one test per defect found in review, so any of them regressing brings that defect back.

The invariant

Only an agent signature may act as an agent. No human session, no lifetime, no admin override.

>>> 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, counted from redemption. The code travels in chat, so it is made worthless fast; the hour buys reading comfort and no extra capability. Waiting for a code does not eat into it — an earlier draft 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 agent id, and verify_chain exists so tampering is detectable. A function that accepts a string and returns authority is how "who is this?" quietly becomes unanswerable.

Status

A hardening pass closed the defects below. Each is now covered by a test.

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
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
Signed nonce verifiable more than once Nonce claimed before the signature is judged
Session tokens stored in clear Stored as SHA-256 digests, like codes
Audit log editable and deletable in place Hash-chained; verify_chain names the first bad event
challenge unmetered Sliding-window limits per agent on every entry point
Private keys written unencrypted Optional passphrase (PBKDF2, AES-256), 0600
Rekey left old sessions alive Rekey revokes every session for that agent
Agent ids unvalidated, could forge log lines Constrained alphabet and length
1-hour session anchored to issue time Anchored to redemption

Still open, and these are the reasons not to ship it yet:

  • No independent review. Everything above is self-review. Self-review finds the bugs you thought of. It does not find the ones you did not. This needs someone else, ideally a cryptographer, before it guards anything real.
  • The chain is unkeyed. Each event folds in the previous digest, so an 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 storage and the HTTP routes are not here. The library refuses to mint authority from a name; a route that forgets to pass a session still cannot, but that is only as good as the integration.
  • Revocation has no expiry semantics. Revoking works; scheduled or self-expiring revocation does not.
  • No key recovery. Rotate and you lose the old sessions. There is no escrow, which is a deliberate choice, not an oversight.

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.

Description
Agent identity for the new internet.
Readme 639 KiB
Languages
Python 100%