feat(secrets): one expiry classifier and a manifest-only expiry report (P8, 1/3) #98

Merged
jfraeys merged 1 commit from jfraeys/feat/secrets-expiry-report into main 2026-10-13 13:03:05 -04:00
Owner

P8 needs a monthly report of expired / approaching / unknown secrets. manage secret list-expiring already bucketed overdue and due-soon inline; this extracts it into one shared definition and adds the report.

  • scripts/lib/secret_expiry.py: pure classification over the manifest (never reads a value): expired (ISO expiry in the past), overdue (rotation interval passed), approaching (expiry within 30d or rotation due within 14d), unknown, documented (free-text expiry), ok.
  • scripts/secrets-expiry-report.py: text / json / markdown output; exit 2 only for expired or overdue, so approaching and unknown never fail a run.
  • manage secret list-expiring now calls the report (output reorganised into the same buckets plus UNKNOWN; counts and audit log kept).
  • Behaviour change, on purpose: rotated_at: null with a schedule is now UNKNOWN, not OVERDUE (the P3 rule: null means unknown, not overdue). Before, 19 secrets with a schedule but no recorded date showed as 'never rotated' alarms. They are still listed, under UNKNOWN.
  • 11 unit tests (scripts/tests/test_secret_expiry.py), added to the lint workflow: hard expiry incl. the 30-day edge, rotation boundaries (due today, 1 day past, 14 and 15 days left), null/garbage dates, free text, precedence, exit codes, formats, a sentinel proving no value reaches a result, and the real manifest parses. Ran manage secret list-expiring live on the real manifest: expired=0 overdue=0 approaching=0 unknown=49 documented=1 ok=12; shellcheck -S error clean.

49 UNKNOWN is the honest state of the manifest: most entries carry no real expiry or rotation date. PR 3 records what the repo actually knows (and does not invent the rest). PR 2 is the rotation lock; PR 3 the monthly workflow.

🤖 Generated with Claude Code

P8 needs a monthly report of expired / approaching / unknown secrets. `manage secret list-expiring` already bucketed overdue and due-soon inline; this extracts it into one shared definition and adds the report. - `scripts/lib/secret_expiry.py`: pure classification over the manifest (never reads a value): expired (ISO `expiry` in the past), overdue (rotation interval passed), approaching (expiry within 30d or rotation due within 14d), unknown, documented (free-text expiry), ok. - `scripts/secrets-expiry-report.py`: text / json / markdown output; exit 2 only for expired or overdue, so approaching and unknown never fail a run. - `manage secret list-expiring` now calls the report (output reorganised into the same buckets plus UNKNOWN; counts and audit log kept). - Behaviour change, on purpose: `rotated_at: null` with a schedule is now UNKNOWN, not OVERDUE (the P3 rule: null means unknown, not overdue). Before, 19 secrets with a schedule but no recorded date showed as 'never rotated' alarms. They are still listed, under UNKNOWN. - 11 unit tests (scripts/tests/test_secret_expiry.py), added to the lint workflow: hard expiry incl. the 30-day edge, rotation boundaries (due today, 1 day past, 14 and 15 days left), null/garbage dates, free text, precedence, exit codes, formats, a sentinel proving no value reaches a result, and the real manifest parses. Ran `manage secret list-expiring` live on the real manifest: expired=0 overdue=0 approaching=0 unknown=49 documented=1 ok=12; shellcheck -S error clean. 49 UNKNOWN is the honest state of the manifest: most entries carry no real expiry or rotation date. PR 3 records what the repo actually knows (and does not invent the rest). PR 2 is the rotation lock; PR 3 the monthly workflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(secrets): one expiry classifier and a manifest-only expiry report
All checks were successful
Lint / lint (pull_request) Successful in 1m13s
Secret scan / scan (pull_request) Successful in 14s
44ba182f43
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jfraeys/infra!98
No description provided.