Breakglass emergency access
Multi-party approved emergency access on Enterprise: severities and approval thresholds, the recorded scope list, the session state machine, and the audit evidence it produces.
Overview
Breakglass is a recorded, multi-party path to emergency access. A person requests it with a written justification, other people approve it, and only then can it be activated. Every step lands in the audit log, and no session record is ever deleted.
Who can use it. Enterprise only: the function calls the Enterprise plan gate and answers 403 with code: "plan_required" for any other plan, and the dashboard tab shows an upgrade panel instead of the workflow.
Auth lane. Console-only. shield-breakglass is not in the worker allowlist, so there is no public URL; the dashboard calls it with supabase.functions.invoke("shield-breakglass") and your signed-in session. request, approve and activate additionally require a human identity: an API key cannot perform them, because an approval recorded against a null actor is not evidence of anything. revoke and list stay open to an API-key caller carrying the breakglass scope, so shutting access down works even when nobody can sign in. That scope is console-only and cannot be minted from Dashboard > API keys.
Every call is POST with action and tenant_id in the body. Anything else answers 405 Method not allowed, and a missing field answers 400 Missing tenant_id or 400 Missing action.
What it does not do. Activating a session does not hand out extra database privileges by itself. The record is the control: it proves who asked, who approved, when it started and when it ended.
Severity and approval thresholds
Severity picks the approval threshold and the maximum lifetime. It is chosen at request time and cannot be changed afterwards.
severity | Approvals required | Maximum TTL |
|---|---|---|
p1 | 3 | 4 hours |
p2 | 2 | 2 hours |
p3 | 1 | 1 hour |
p2 is the default when the field is omitted. An unknown value answers 400 Invalid severity. Must be one of: p1, p2, p3.
{
"action": "request",
"tenant_id": "<uuid>",
"severity": "p1",
"access_scope": ["read", "decrypt"],
"reason": "Investigating the 14:02 revocation storm on the payments tenant",
"ttl_hours": 2
}ttl_hours is optional and clamped down to the severity maximum; it can shorten a window, never extend it. reason must be at least 10 characters after trimming, or the answer is 400 Reason must be at least 10 characters.
{
"session": {
"id": "<uuid>",
"status": "pending",
"severity": "p1",
"required_approvals": 3,
"expires_at": "2026-09-16T16:02:00.000Z",
"created_at": "2026-09-16T14:02:00.000Z"
}
}The reply is 201. Note that expires_at is measured from the request rather than from activation: a session approved slowly has less time left when it starts.
Access scopes
A request names one or more scopes from a fixed list. An unknown value answers 400 naming the rejected scope and listing the valid ones.
| Scope | Intent |
|---|---|
read | read tenant data and configuration |
write | change tenant configuration such as playbooks and alert rules |
admin | administrative access including user management |
decrypt | decrypt vault entries |
rotate_keys | emergency key rotation |
These scopes are recorded, not enforced. The session row stores the list and the audit entries carry it, so a reviewer can see what was asked for and granted. No PasskeyBridge function reads the list to widen or narrow what the holder can do, and decrypt does not require a purpose code here. Treat the scope list as the declared purpose of the session, checked by the approvers rather than by the code.
The list is fixed at creation. There is no action that adds a scope to an existing session; a wider need means a new request.
Enforced controls
Four controls are enforced in code, three of them inside a single locked database transaction so concurrent approvers cannot race each other.
- No self-approval. The requester cannot approve their own session:
403 Requester cannot approve their own breakglass session. - One approval per person. A second approval from the same user answers
409 Already approved by this user. - State machine. Approving a session that is no longer pending answers
409naming its current state, and so does activating one that has not reachedapprovedor revoking one that is already terminal. - Human identity.
request,approveandactivaterefuse an API-key caller with403and a message saying so.
{ "approved": false, "approval_count": 2, "required": 3, "status": "pending" }That is the approve reply; status flips to approved and approved to true on the approval that meets the threshold.
Expiry is checked when someone acts rather than by a background job. Approving a session past expires_at answers 410 Session expired before approval; activating one answers 410 Session expired before activation and writes the row to expired. An already-active session that passes its expiry keeps the status active in the table until someone revokes it, while the dashboard countdown reads "Expired". Revoke an active session when the work is done rather than letting it lapse.
Every action writes an audit entry (breakglass.request, breakglass.approve, breakglass.activate, breakglass.revoke) with the actor, the hashed actor IP, the user agent and the action's metadata.
Session lifecycle
| Status | Meaning | Next |
|---|---|---|
pending | created, waiting for approvals | approved on the last approval, revoked on a deny |
approved | threshold met, awaiting activation | active on activation, expired if activation is attempted late, revoked |
active | started, access is live | revoked |
expired | terminal; activation was attempted after expires_at | none |
revoked | terminal; someone closed it | none |
activate answers {"status":"active","expires_at":"..."}. revoke answers {"status":"revoked"} and stores revoked_at, revoked_by and the optional revocation_reason. A deny is a revoke of a pending session; there is no separate action.
list reads the window you ask for.
{ "action": "list", "tenant_id": "<uuid>", "hours": 168 }hours defaults to 24 and is capped at 720, and the read returns at most 100 sessions, newest first, alongside per-status counts.
{
"sessions": [{ "id": "<uuid>", "status": "revoked", "severity": "p1", "approvals": [], "access_scope": ["read"], "expires_at": "...", "created_at": "..." }],
"stats": { "total": 7, "pending": 1, "active": 0, "approved": 0, "revoked": 5, "expired": 1 },
"window_hours": 168
}| Status | Body | Cause |
|---|---|---|
| 400 | {"error":"Missing session_id"} | an action that needs a session id did not get one |
| 403 | {"error":"...requires an authenticated user session..."} | API key attempting request, approve or activate |
| 403 | plan gate response with code: "plan_required" | the organization is not on Enterprise |
| 404 | {"error":"Session not found"} | unknown id, or an id belonging to another organization |
| 500 | {"error":"Failed to record approval"} | the approval transaction failed; nothing was recorded |
Breakglass tab
Dashboard > Breakglass, in the Security group, on Enterprise.
- Request access opens the form: severity buttons (P1 Critical, P2 High, P3 Moderate, each showing its approver count and maximum duration), access-scope toggles (Read, Write, Admin, Decrypt, Rotate keys), and a Justification box that requires at least 10 characters and accepts up to 1,000.
- Active session banner appears above everything while a session is active, with a live countdown per session and a Revoke button.
- Four tiles: Active, Pending, Revoked, Total for the window.
- Requests card lists open sessions. Expanding one shows the requester, the expiry, the scope list and an approval progress bar. Pending sessions offer Approve and Deny; approved sessions offer Activate and Revoke; active sessions offer Revoke. Denying or revoking asks for a reason.
- History section holds closed sessions. The tab requests a 168-hour window, so it covers the last 7 days, and the rows are read-only.
Confirm it worked. The session moves between the Requests card and History with the expected status, and Dashboard > Audit log shows the matching breakglass.* entry with your user as the actor.
SOC 2 readiness evidence
The workflow exists to produce evidence for the emergency-access controls an auditor asks about. PasskeyBridge's own SOC 2 position is readiness: evidence collected and under review, with no CPA engaged.
| Criterion | What the record supports |
|---|---|
| CC6.1 | logical access: emergency access needs approval from people other than the requester |
| CC6.3 | access removal: sessions carry a bounded lifetime and can be revoked at any moment |
| CC7.2 | monitoring: every request, approval, activation and revocation is logged with actor context |
| CC7.3 | incident response: the request text records why access was needed and when |
What you can export as evidence: the audit entries for a period (Dashboard > Audit log, filtered by action, CSV export), and the session list with its approval chains and revocation reasons. The compliance report counts the same audit rows under audit_trail; see CISO compliance report.
One caveat to state plainly in an evidence pack: the access scopes on a session are declarations reviewed by the approvers, and they are not enforced by the platform.