Skip to content

fix: ephemeral JWT keys are unverifiable after process exit — no external audit trail #45

@manzil-infinity180

Description

@manzil-infinity180

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:

  1. Attestations reference the session, and the session has an auth_token
  2. But no one can verify the token's signature post-hoc
  3. 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:

  1. Generate ephemeral ECDSA key
  2. Get SPIRE to issue an X.509 SVID for it, or get Fulcio to issue a short-lived cert
  3. Store the certificate chain alongside the JWT
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdeferredNot needed for current milestone; revisit latersecuritySecurity vulnerability or concern

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions