Skip to content

90-day key rotation policy: due keys wait for approval, auto-approve is the paid add-on (CLI 0.16.0) - #270

Merged
sh1pt-actions-fleet[bot] merged 1 commit into
masterfrom
feat/rotation-90-day-policy
Oct 29, 2026
Merged

sh1pt-actions-fleet[bot] merged 1 commit into
masterfrom
feat/rotation-90-day-policy

Conversation

@sh1pt-actions-fleet

Copy link
Copy Markdown
Contributor

Anthony (2026-10-29): logicsrc should rotate keys every 90 days by default, with our approval, fast (about 10 minutes), and automatic rotation as a paid add-on for premium users.

Builds on #265 (rotators, propagate) and #269 (run queue).

What changes

  • Policy: each automatic secret has a max age, 90 days by default (credshare_teams.rotation_max_age_days overrides it). The plan rows now carry ageDays and due. never and manual keys are never due.
  • Due run: POST /api/credshare/teams/:slug/rotation/due (owner/admin or a machine key), logicsrc teams rotate-keys <team> --queue-due, or the "Queue the due keys now" button. Every due key, oldest first, at most 200, goes into ONE run in awaiting-approval. While it is open the call is idempotent.
  • Approval: POST .../rotation/runs/:id/approve, logicsrc teams rotation-approve <team> <run-id>, or the Approve button on the Rotation page. Owner/admin only, a person (machine keys get 403). The approver is recorded (approved_by/approved_at) and audited. Decline = cancel.
  • Runner: logicsrc-rotate --queue (meant for a */5 cron) runs approved runs with --from-queue --propagate --approve (3 sites at a time, health checks, rollback) and mails the outcome. The weekly run queues the due run first and the mail links to the approve page. Low memory skips a tick.
  • Paid add-on: credshare_teams.rotation_auto_approve = 1 queues due runs already approved. Off for every team. No self-serve toggle or billing yet.
  • Migration 011_rotation_policy.sql (sqlite + pg), additive only.
  • Runbook: new section "The 90-day policy" in docs/rotation.md.

Tests

  • PWA: 108 passed. 5 new policy tests: due flags, awaiting-approval, idempotency, no claim before approval, member cannot approve, approver email, decline, team max age, auto-approve add-on, page.
  • CLI: 200 passed. The build includes the new commands.

No live rotation was run.

🤖 Generated with Claude Code

…0.16.0)

Every automatic secret older than the team's max age (90 days default) is
queued as one run in awaiting-approval. An owner/admin approves it on the
Rotation page or with `teams rotation-approve`; machine keys can queue but
never approve. logicsrc-rotate --queue (*/5 cron) runs approved runs with
propagate + health checks + rollback. rotation_auto_approve per team is the
paid automatic rotation add-on (off by default).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sh1pt-actions-fleet
sh1pt-actions-fleet Bot merged commit 0f237cb into master Oct 29, 2026
4 checks passed
const maxAge = maxAgeDays(team);
const due = (await rotationPlan(team.id, { maxAge, now, vaultFilter })).filter((p) => p.due);
const open = (await all(
`SELECT * FROM credshare_rotation_runs WHERE team_id = ? AND status IN (${OPEN_RUN_STATUSES.map(() => "?").join(",")}) ORDER BY created_at DESC`,
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

67 finding(s)

HIGH/CRITICAL: 6 | MEDIUM: 29 | LOW: 32

Severity Rule Location
HIGH secret-private-key apps/pwa/src/routes/keyring.mjs:32
HIGH secret-private-key packages/cli/src/keyring.ts:83
HIGH secret-private-key packages/logicsrc-mcp/src/keyring.ts:20
HIGH secret-private-key plugins/credential-sharing/src/providers/ssh.ts:129
HIGH js-host-header-trust apps/commandboard-web/server.js:27
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
MEDIUM js-unescaped-html-sink apps/commandboard-web/src/main.ts:19
MEDIUM sh-remote-script-execution apps/logicsrc-web/public/install.sh:71
MEDIUM js-unescaped-html-sink apps/logicsrc-web/src/app/[[...slug]]/page.tsx:59
MEDIUM js-unescaped-html-sink apps/logicsrc-web/src/app/layout.tsx:80
MEDIUM js-unescaped-html-sink apps/logicsrc-web/src/app/pricing/page.tsx:42
MEDIUM js-unescaped-html-sink apps/logicsrc-web/src/components/breadcrumbs.tsx:43
MEDIUM js-unescaped-html-sink apps/logicsrc-web/src/components/pit-footer.tsx:16
MEDIUM js-unescaped-html-sink apps/logicsrc-web/src/components/site-shell.tsx:36
MEDIUM js-unescaped-html-sink apps/pwa/public/keyring.js:51
MEDIUM sql-template-interpolation apps/pwa/src/routes/rotation.mjs:78
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:253
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:297
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:299
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:303
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:307
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:326
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:327
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:329
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:330
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:331
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:334
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:395
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:402
MEDIUM redos-nested-quantifier packages/cli/src/rotation-catalog.ts:408
MEDIUM redos-nested-quantifier packages/opencontext/src/ids.ts:10
MEDIUM redos-nested-quantifier packages/opencontext/src/ids.ts:11
MEDIUM js-timing-unsafe-mac-compare packages/opencontext/src/validate.ts:208
MEDIUM redos-nested-quantifier packages/openerrand/src/browser.ts:34
MEDIUM redos-nested-quantifier packages/openontology/src/ids.ts:20
LOW secret-generic-credential apps/pwa/test/appbar.test.mjs:10
LOW secret-private-key apps/pwa/test/keyring.test.mjs:137
LOW secret-generic-credential apps/pwa/test/signup-profile.test.mjs:61
LOW secret-generic-credential apps/pwa/test/signup-profile.test.mjs:72
LOW secret-openai-key packages/cli/src/desktop-flow.test.ts:315
LOW secret-generic-api-key packages/cli/src/desktop-flow.test.ts:315
LOW secret-private-key packages/cli/src/keyring.test.ts:86
LOW secret-private-key packages/cli/src/keyring.test.ts:88
LOW secret-private-key packages/cli/src/rotate-flow.test.ts:125
LOW secret-generic-api-key packages/cli/src/rotate-flow.test.ts:177
LOW secret-generic-credential packages/cli/src/rotate-flow.test.ts:184
LOW secret-generic-api-key packages/cli/src/rotate-flow.test.ts:198
LOW secret-generic-credential packages/cli/src/rotate-flow.test.ts:204
LOW secret-generic-credential packages/cli/src/rotators.test.ts:52
LOW secret-generic-credential packages/cli/src/rotators.test.ts:75

…and 17 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants