seekrit
Docs/Approval workflows

Approval workflows

Protect an environment and writes to it stop landing. Instead they queue as change requests: an admin reviews what is changing, and the change applies the moment it has the approvals your policy asks for.

The part worth understanding first is what a reviewer sees. A change request carries the ciphertext the proposer already encrypted — the same blob a direct write would have stored — so a review shows which secret is changing, who proposed it, which version it replaces, and why, and never the value. Approving a change and reading a credential stay two different permissions, which is exactly what you want from the person signing off on production.

note

Approval workflows are a plan feature. Your plan & billing page shows whether your plan includes them.

Protect an environment

In the dashboard, open the environment and use the Change approval card: pick how many approvals a change needs, and whether the proposer's own approval may count toward it (off by default — separation of duties is the point).

From the CLI:

seekrit changes protection on --app store --env production --approvals 2
seekrit changes protection show --app store --env production

Turning protection on and off is admin-only, and both directions are audited (env.protection_enabled, env.protection_disabled). Lifting the gate is the security-significant half: it is the row to look for if a protected environment turns out to have taken a direct write.

note

Protection is a product control, not a second key. It stops the API accepting a write and parks the ciphertext in a review queue — an admin can remove it. What it is not is a cryptographic seal: anyone holding a key grant for the environment could still decrypt what is already there.

Propose a change

Nothing about writing changes. Set a secret exactly as you always would — from the dashboard, the CLI, an SDK, or an MCP tool — and if the environment is protected the write is queued instead of applied:

seekrit secrets set STRIPE_KEY sk_live_… --app store --env production -m "rotating after the incident"
queued for review: set STRIPE_KEY — needs 2 approvals (chg_…)

The HTTP answer is 202, not 200, and the response body carries { "queued": true, "changeRequest": … }. Every seekrit client branches on that and says so — the one thing a client must never do is report a write that has not happened.

-m / --note is the explanation reviewers see. seekrit secrets rm and seekrit secrets restore queue the same way.

Review

seekrit changes list
seekrit changes show chg_…
seekrit changes approve chg_… -m "matches the rotation ticket"
seekrit changes reject chg_…

Or use Change requests in the dashboard sidebar.

The rules:

  • Reviewers are admins, and must be signed in as a person. A service token cannot approve: whatever holds it can already write, so letting it approve would make the gate decorative.
  • One rejection closes the change. There is no rejection quorum.
  • Approvals are counted per person. The same admin approving twice does not satisfy a threshold of two.
  • The threshold approval applies the change in the same request — there is no separate merge step to forget.
  • The proposer can always withdraw their own change, even when they may not approve it.

When the secret moves underneath

A change request records the secret version it was cut against. If that version is no longer current when the change is approved — someone else's change applied first, or the secret was deleted — seekrit marks it superseded and applies nothing:

not applied: STRIPE_KEY is now at v9; this change was cut against v7

That is deliberate. Applying it would overwrite a value none of its reviewers ever saw. Re-propose against the current value.

What lands in the audit trail

A completed change leaves the whole chain, each row naming its own actor:

ActionWritten when
change_request.openeda protected environment queued a write
change_request.approveda reviewer approved (with the running count)
change_request.rejecteda reviewer rejected
change_request.withdrawnthe proposer or an admin took it off the queue
change_request.appliedthe change was applied
change_request.supersededthe base moved, so it was not applied

The ordinary secret.created / secret.updated / secret.deleted / secret.restored row is written as well, carrying the change request's id — so a secret's own history stays complete whether or not it went through review, and the four-eyes evidence is a separate, attributable chain rather than a reinterpretation of the same rows.

Version history credits the proposer, not the approver: they wrote the ciphertext, and the approver never held a key.

Notifications

Admins are emailed when a change is queued; the proposer is emailed when it is applied, rejected, or superseded. Both are opt-out per user under email notifications.

Limits worth knowing

  • Protection is per environment. Protect production; leave development alone.
  • Reviewers are the org's admins and owners — there is no per-environment approver list yet.
  • Settings are snapshotted onto each change request when it is opened, so loosening the policy later never releases something already in the queue.
  • Taking protection off leaves queued changes queued. Withdraw them if they are no longer wanted.