Skip to content

Key rotation skill: auto-detect stale keys and rotate with JWKS update #3

@dickhardt

Description

@dickhardt

Summary

Add a key rotation skill that detects when signing keys are older than 30 days and walks the agent through rotating them — generating a new key, publishing it to the JWKS, and scheduling removal of the old key.

Motivation

Keys currently have no expiration or rotation guidance. The aauth.created field and date-stamped kid format (e.g. 2026-04-09_a3f) already encode the creation date, but nothing acts on it. Long-lived signing keys are a security risk; routine rotation should be a first-class operation.

Proposed Behavior (Skill Instructions)

The rotation skill should guide an agent (or be invoked by a user) through these steps:

1. Check key age and clean up retired keys

  • Read the agent's config (~/.aauth/config.json) and enumerate registered keys
  • Fetch the published JWKS and check for any keys with a removeAfter timestamp that has passed — remove those keys from the JWKS
  • Parse the creation date from the kid (format YYYY-MM-DD_HHH) or from aauth.created in the published JWK
  • Flag any key older than 30 days as due for rotation

2. Verify ability to update published keys

  • Resolve the agent's jwks_uri (from aauth-agent.json or cached in config)
  • Determine the hosting platform from config (hosting field)
  • Verify CLI access to the hosting platform (e.g. gh authenticated for GitHub Pages, wrangler for Cloudflare, etc.)
  • Confirm the JWKS file can be updated before proceeding — abort with guidance if not

3. Generate a new key

  • Use existing key generation (generateKey) on all available backends (hardware preferred, matching current setup)
  • Register the new key in the agent config

4. Update the JWKS file

  • Add the new public key to the jwks.json keys array
  • Annotate the old key with a retirement note in its aauth metadata:
    "aauth": {
      "device": "yubikey-otp+fido+ccid-0775",
      "created": "2026-03-01",
      "removeAfter": "2026-04-16T00:00:00Z"
    }
    The removeAfter timestamp should be 48 hours from the rotation time, giving relying parties time to see tokens signed with the old key
  • Push the updated JWKS to the hosting platform

5. Post-rotation

  • Log a summary: which key was rotated, what backend, old kid → new kid, and when the old key can be removed

Implementation Notes

  • Skill file: local-keys/skills/rotate.md
  • Key age threshold (30 days) could be a constant in the skill or configurable later
  • The 48-hour retirement window balances security with token lifetime (default 1h) plus clock skew
  • checkKeyAvailability() in resolve-key.ts already identifies which published keys are locally available — useful for finding keys that can no longer be rotated (device removed)
  • Platform-specific push logic already exists in platform skills (github-pages.md, cloudflare-pages.md, etc.) — rotation skill should reference those

Out of Scope (for now)

  • Automatic unattended rotation (cron/scheduled) — this is an interactive skill
  • Revoking keys before the 48-hour window (emergency rotation is a separate concern)
  • Multi-agent coordinated rotation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions