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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions