What Exists
JWT token issuers (#19) use ephemeral ECDSA P-256 keys generated in-memory:
// internal/auth/jwt.go
func NewTokenIssuer() (*TokenIssuer, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// key lives only in process memory
}
- Hooks mode: Key exists for ~100ms during
SessionStart process, then destroyed
- MCP mode: Key exists for the server's lifetime, then destroyed
The JWT is stored in SessionState.AuthToken (on disk), but the public key is not persisted anywhere.
What's Missing
After the process exits, the JWT in session state is cryptographically unverifiable. No one — not an auditor, not a CI pipeline, not aflock verify — can confirm the token was legitimately issued. The token is just an opaque string in a JSON file.
This breaks the audit trail:
- Attestations reference the session, and the session has an
auth_token
- But no one can verify the token's signature post-hoc
- An attacker could replace
auth_token with a forged JWT (different claims) and no one would know
Proposed Fix
Option A: Persist public key to session directory
Write the public key PEM to ~/.aflock/sessions/<session-id>/jwt-pubkey.pem at issuance time. This allows post-hoc verification:
aflock verify --session <id> # can now verify the JWT signature
Trade-off: The key is still ephemeral (generated per-session), but at least it's verifiable. An attacker who compromises the session directory could replace both the key and token, but that's true of all file-based state.
Option B: Sign JWT public key with SPIRE/Fulcio
When SPIRE or Fulcio is available, get a certificate for the ephemeral public key:
- Generate ephemeral ECDSA key
- Get SPIRE to issue an X.509 SVID for it, or get Fulcio to issue a short-lived cert
- Store the certificate chain alongside the JWT
- Post-hoc verification checks: JWT signature → ephemeral key → SPIRE/Fulcio cert → trust root
This creates a cryptographic chain from the JWT back to a trust anchor.
Option C: Embed JWT hash in attestation
Include sha256(jwt_token) in each attestation predicate. This doesn't let you verify the JWT contents, but it proves which token was used for each action — binding attestation to authorization.
Related
What Exists
JWT token issuers (#19) use ephemeral ECDSA P-256 keys generated in-memory:
SessionStartprocess, then destroyedThe JWT is stored in
SessionState.AuthToken(on disk), but the public key is not persisted anywhere.What's Missing
After the process exits, the JWT in session state is cryptographically unverifiable. No one — not an auditor, not a CI pipeline, not
aflock verify— can confirm the token was legitimately issued. The token is just an opaque string in a JSON file.This breaks the audit trail:
auth_tokenauth_tokenwith a forged JWT (different claims) and no one would knowProposed Fix
Option A: Persist public key to session directory
Write the public key PEM to
~/.aflock/sessions/<session-id>/jwt-pubkey.pemat issuance time. This allows post-hoc verification:Trade-off: The key is still ephemeral (generated per-session), but at least it's verifiable. An attacker who compromises the session directory could replace both the key and token, but that's true of all file-based state.
Option B: Sign JWT public key with SPIRE/Fulcio
When SPIRE or Fulcio is available, get a certificate for the ephemeral public key:
This creates a cryptographic chain from the JWT back to a trust anchor.
Option C: Embed JWT hash in attestation
Include
sha256(jwt_token)in each attestation predicate. This doesn't let you verify the JWT contents, but it proves which token was used for each action — binding attestation to authorization.Related