Revocation and scoped responses
Scoped security responses, ordered receipts, the policy matrix, hosted-RP freezes and step-up, and legacy caller boundaries.
Overview
A security response classifies a signal, selects an ordered policy and records what each step changed. The classifier remains deterministic: the same signal type always has the same hard, soft or unknown classification. It is described in a filed application; no patent has been granted.
Rollout boundary. The scoped response API requires both the server SCOPED_RESPONSE_ENABLED=true switch and the organization's scoped_response_enabled opt-in. This build enables neither. The behavior below is conditional on those gates and the corresponding runtime/schema being deployed. A source change or documentation page is not evidence of hosted activation.
With either gate off, the three legacy callers retain their existing scope and behavior. With both gates on, they use the common coordinator while retaining their original target sets. This does not turn an ingest signal or inbound CAEP mapping into a tenant-wide credential or session action.
Dashboard > Revocation keeps the existing classifier and cascade controls and adds Response runs, the read-only Response policy matrix and administrator Hosted RP credentials lookup. Disabled or failed reads are shown explicitly.
Caller scope and compatibility
| Caller | Legacy hard-signal targets | With both response gates on |
|---|---|---|
Live shield-ingest, with an identifier | Tenant delegates and open A2A negotiations; only the subject's matching shadows | Two independent explicit plans: tenant revoke_delegates and a separate subject freeze_shadows plan. They may execute concurrently. Exact existing shadow candidates are retained; no session, credential or tunnel action is added |
Admin cascade_execute | Tenant delegates, open A2A negotiations, shadows and BLAST tunnels | Explicit ordered revoke_delegates, freeze_shadows, teardown_tunnels. The first step includes A2A, retaining the same four target sets |
| Verified inbound hard CAEP mapping | Subject-matching shadows only | Explicit subject freeze_shadows, with the same matching candidates. No phone-to-RP, delegate or account relationship is invented |
Sandbox and test-mode ingest still skip hard-response writes and metering. The admin path refuses opted-in sandbox/test execution. A disabled gate keeps the legacy path; an error after selecting the new path never falls back to legacy execution.
New admin and CAEP runs retain ordered response receipts. Ingest stores bounded run/receipt references in its existing event action and audit evidence; its public response body is unchanged. Completed optional ingest-idempotency replay and an event-insert retry resume without repeating the two plans. Requests without a key and an isolate lost before the dispatched checkpoint retain the existing crash/retry limits.
Legacy ingest counts use returned row arrays and can undercount large updates. The coordinator records exact database counts with separately capped row-ID evidence. Soft admin/CAEP mappings retain their earlier routing behavior; ingest still dispatches its existing graduated trust evaluation. These compatibility callers do not silently select the new default policy matrix.
Signal taxonomy
Hard signals revoke immediately.
| Signal type | Usual source |
|---|---|
sim_swap | Carrier feed or your own backend |
sim_swap_detected | A carrier lookup that flagged a swap |
port_out | Carrier feed |
number_port | Carrier feed |
number_porting | Carrier feed |
device_compromise | Device attestation failure |
ss7_intercept | Network monitoring |
account_takeover | Your own detection |
scope_poisoning | Agent scope tampering |
Soft signals route to graduated trust evaluation: velocity_anomaly, behavioral_anomaly, geo_anomaly, credential_leak, credential_stuffing_attempt, suspicious_login, failed_verification, device_change, unusual_device, number_recycle.
When a soft type reaches ingest with an identifier, the handler dispatches one trust re-evaluation of the organization's active delegates, subject to its debounce. The engine decides from each delegate's own recent activity; the type does not select a per-type step-up or re-attestation action. The admin cascade endpoint and inbound CAEP soft mappings record routing without dispatching that evaluation.
Unknown types are refused by this endpoint with 400 Unknown signal type: <value>. The classifier is not extensible at run time; a new type is a platform release. The ingest pipeline is more permissive: it stores an unrecognised type and runs any playbook that matches it.
Policy selection and order
An explicit steps array takes precedence. Otherwise the server selects the latest tenant policy for the signal/risk pair, before the latest default. The selected version, source and ordered operations are snapshotted on the run.
| Classification and risk | Default ordered steps |
|---|---|
| Hard + high | revoke_sessions, freeze_credentials, revoke_delegates, freeze_shadows, teardown_tunnels |
| Soft + medium | narrow_agent_scopes, step_up_required |
| Soft + low | notify |
| Any other pair | Unspecified; provide explicit steps or an administrator-managed policy |
Every selected step must support the supplied scope and selector. The whole plan is preflighted before a run is created. A subject request cannot combine unrelated delegate and RP selectors to satisfy the medium-risk default: select a narrower explicit plan for the actual linked subject, or use authorized tenant scope. A missing policy returns response_policy_missing; unsupported linkage returns step_not_subject_scoped. An rp_credential_id that matches no credential in your organization returns subject_not_found (404) at preflight, so no run is created.
Execution follows the array order. Each step commits its target changes and recorded result in one database transaction. A failed step rolls back its own effects, records step_failed, and permits later independent steps to run. The entire plan is not one transaction. Inspect ok, every step outcome and receipt availability, including in a transport-success response.
Subject linkage and authority
Subject scope accepts exactly one closed selector. Tenant scope accepts no subject. Missing or foreign references never widen a request.
| Selector | Supported subject steps and boundaries |
|---|---|
phone_hash | freeze_shadows; the existing keyed digest selects matching shadow identities |
rp_credential_id | freeze_credentials, step_up_required; the public canonical WebAuthn credential ID resolves its tenant-bound RP owner/config. Freeze covers that owner's RP credentials; step-up marks the RP user |
event_id | revoke_sessions; an existing non-test event supplies the tenant-owned opaque handle for CAEP and callback work |
dashboard_user_id | revoke_sessions; the explicit tenant owner or active member's stored first-party auth sessions and refresh tokens are deleted across organizations |
delegate_id | narrow_agent_scopes; narrows the named tenant delegate without inferring a phone relationship |
notify is also available for each valid selector: it notifies the configured tenant callback about that run without including the selector. revoke_delegates and teardown_tunnels require tenant scope. A phone digest never implies an RP identity, dashboard account, delegate or receiver subject.
respond with subject scope accepts a tenant-admin JWT or a server-side API key with cascade:write, available in the key wizard. Tenant scope, dashboard-account session deletion, unfreeze, history, receipts and policy reads require administrator authority. The endpoint remains Enterprise-only. Keep API keys on your backend; never put them in a browser.
Step effects and limits
| Step | Actual effect |
|---|---|
revoke_sessions | Queues CAEP session-revoked requests and configured callback notifications for known opaque handles. Only an explicit linked dashboard account selector deletes stored first-party sessions/refresh tokens |
freeze_credentials | Marks eligible hosted-RP credentials frozen, preserving prior freezes and revocations, with the response correlation ID |
narrow_agent_scopes | Intersects current delegate permissions with the read-only trust ladder, retains the original snapshot and caps trust below the full rung without raising an existing lower score. A conflicting revocation floor fails the step |
revoke_delegates | Deactivates tenant delegates and revokes open tenant A2A negotiations |
freeze_shadows | Deactivates matching shadow identities |
teardown_tunnels | Marks BLAST sessions torn down; it does not destroy key material |
step_up_required | Marks affected hosted-RP users pending a fresh, verified user-verification assertion |
notify | Queues one response.notification callback when a callback URL exists; otherwise reports zero queued |
CAEP/webhook counts prove enqueue, not delivery or external IdP termination. Delivery workers retain their URL guards, signatures, retries and receiver deduplication keys. A message already in flight cannot be recalled. Already-issued stateless access tokens, customer sessions and externally retained artifacts retain their existing lifetime.
A2A reads, renewals and new signed commits check live parent authority after narrowing. Historical signed proofs remain historical evidence. Fresh successful delegate activity may earn recovery under the existing trust engine.
Hosted RP freeze, step-up and unfreeze
A frozen or revoked RP credential fails auth_verify with verification_failed, reason credential_frozen or credential_revoked, and cannot mint a new session attestation. Freeze enforcement also protects counter/touch writes against lifecycle races.
Step-up belongs to the RP user, so registering another credential does not evade it. A challenge issued before the marker, including an ambiguous same-millisecond timestamp, cannot satisfy it. Known-user options require UV while marked. Discoverable options cannot identify the user yet; an as_requested RP may need a fresh required-UV ceremony after a presence-only assertion is refused. Only cryptographically verified presence and UV on a fresh challenge can provide the current marker proof. A newer response racing verification refuses the final write.
The credential update, marker clear and response.step_up_satisfied audit commit together. Suppressed credential writes, failed clears or failed audits cannot report satisfaction. attest_session refuses step_up_required while a marker is pending, including a marker observed at its final issuance check. Existing customer sessions and previously issued attestations are not recalled.
An administrator can look up the configured RP domain and opaque handle in Hosted RP credentials, then use Unfreeze after reviewing the incident. The API accepts the public credential_id and the observed frozen_correlation_id, including an observed null for a manual freeze. A changed correlation returns freeze_changed; revocation is never cleared. A successful unfreeze and its audit commit together; a repeat returns already_unfrozen:true without another write. Unfreeze does not clear a pending step-up requirement.
Calling the scoped response API
Subject preview. Obtain the actual stored keyed phone digest from your authorized server context. This explicitly previews only matching shadows. Replace the example UUID with a fresh correlation ID for new work; reuse it only for retries of the same intent.
curl -sS -X POST https://api.passkeybridge.io/v1/shield-cascade \
-H "content-type: application/json" \
-H "x-pb-api-key: $PB_SCOPED_RESPONSE_KEY" \
-d '{"action":"respond","tenant_id":"YOUR_TENANT_ID","signal_type":"sim_swap","risk":"high","scope":"subject","subject":{"phone_hash":"YOUR_STORED_64_HEX_KEYED_DIGEST"},"steps":["freeze_shadows"],"correlation_id":"f6600000-0000-4000-8000-000000000010","dry_run":true}'Tenant preview. This requests the full hard/high default policy and requires administrator authority. It can cover more targets than the four-subsystem compatibility action.
curl -sS -X POST https://api.passkeybridge.io/v1/shield-cascade \
-H "content-type: application/json" \
-H "authorization: Bearer $PB_SESSION_JWT" \
-d '{"action":"respond","tenant_id":"YOUR_TENANT_ID","signal_type":"sim_swap","risk":"high","scope":"tenant","correlation_id":"f6600000-0000-4000-8000-000000000011","dry_run":true}'A respond dry run reads counts without creating a run, audit or receipt. Set dry_run:false only when execution is intended. After execution, retry the same correlation ID with the same intent to resume or retrieve its result. Reusing an ID for different intent returns response_request_mismatch. A recorded failed effect requires a new correlation for a new attempt; receipt-only retry does not repeat effects.
Unlike respond preview, the legacy cascade_dry_run action records its existing audit event. Complete request/response shapes and bounded error codes are in OpenAPI.
History, receipts and policy reads
response_history accepts tenant_id, optional limit (default 50, maximum 100) and an optional unchanged before cursor from next. It returns {ok,runs,next} in descending (started_at,id) order. There is no inferred total. Refresh the first page to see concurrent new runs.
Each run shows its full correlation ID, signal, scope, ordered steps/results, measured duration, receipt reference and bounded failure/erasure state. Private selector context, subject indexes, intent bindings and leases are withheld. Copy receipt reads the retained complete ordered artifact; it never reconstructs a receipt from the displayed table.
response_receipt reads one UUID correlation ID and returns {ok,run,receipt} without re-signing. An erased context returns response_subject_erased (409), including erasure observed during retrieval. A missing run returns 404; an unavailable stored artifact returns receipt_unavailable (502). Ordered receipts use pb-response-receipt+jwt; historical pb-cascade-receipt+jwt artifacts remain distinct. With both gates on, cascade_receipt can also route a UUID to the new reader; historical pb-… references keep their old reader.
response_policy accepts only action and tenant_id. It returns all classifier/risk pairs, the effective version/source/ordered steps or null for an unspecified pair, and the supported selector capabilities. The dashboard displays this matrix read-only. There is no policy editing action.
Queued CAEP evidence
revoke_sessions queues one SET per eligible enabled transmitter push stream and selected opaque handle. Requested event subscriptions and endpoint configuration still apply. The worker resolves the current source event before signing and withholds an erased source. A callback, when configured, uses response.session_revocation_requested with run/source references and a stable idempotency key.
Illustrative decoded SET shape, not an issued token:
{
"iss": "https://api.passkeybridge.io/v1/tenants/example",
"aud": "https://receiver.example.invalid/events",
"iat": 1793354400,
"jti": "pb-response:RUN_UUID:EVENT_UUID",
"sub_id": { "format": "opaque", "id": "receiver-owned-opaque-handle" },
"events": {
"https://schemas.openid.net/secevent/caep/event-type/session-revoked": {
"event_timestamp": 1793354400,
"initiating_entity": "policy",
"reason_admin": { "en": "Security response requested session revocation" }
}
}
}The signed SET uses the stream's actual issuer/audience and tenant signing keys. Its transport acknowledgement is separate from the response run's queue count and from the receiver's decision to terminate a session. See SSF/CAEP shared signals.
Response context and erasure
Response selectors are purpose-bound encrypted retry context indexed by server-produced tenant/type HMACs. Public evidence excludes those values. Erasure tombstones a run, removes its retry context, receipt and changed-row IDs, revokes its lease, and scrubs/cancels linked queue and delivery payloads. Historical counts, timing and outcomes remain; erased runs cannot be replayed or signed again.
The administrator-only shield-dsar dataset response_context supports each closed response selector. It exports public run evidence or erases that context; it does not erase source accounts, credentials, RP step-up state or independently held messages/receipts. The ordinary digest DSAR also handles matching phone/event response contexts before unlinking source events. See DSAR workflows.
Legacy API actions
The following seven compatibility actions describe behavior with scoped responses off. The gated actions above are additional.
Seven actions, all POST to the same path with a tenant-admin session JWT.
curl -sS -X POST https://api.passkeybridge.io/v1/shield-cascade \
-H "content-type: application/json" \
-H "authorization: Bearer $PB_SESSION_JWT" \
-d '{"action":"cascade_execute","tenant_id":"YOUR_TENANT_ID","signal_type":"sim_swap"}'| Action | Body fields | Effect |
|---|---|---|
cascade_execute | tenant_id, signal_type | Classifies, then runs the four-subsystem cascade for a hard type or records the routing for a soft one |
cascade_dry_run | tenant_id, signal_type | Counts matching targets without changing them; writes an audit event |
cascade_history | tenant_id, optional limit | Returns cascade audit rows, newest first. Default 50, maximum 200 |
classify | tenant_id, signal_type or signal_types (up to 50) | Classifies each type as cascade_execute would and acts on nothing |
get_model | tenant_id | Returns the hard and soft lists, a version derived from them, and the organization's trust ladder or the defaults |
update_threshold | tenant_id, full_at and read_only_at, or reset: true | Sets or resets the organization's soft-signal trust ladder |
cascade_receipt | tenant_id, correlation_id | Returns the stored signed receipt of one executed cascade and the row ids its digests bind, whether it was run here or started by an inbound CAEP event |
Preview and receipt retrieval. A preview leaves the target rows unchanged and records an audit event. Receipt retrieval reads a prior executed cascade; a dry run has no receipt.
curl -sS -X POST https://api.passkeybridge.io/v1/shield-cascade \
-H "content-type: application/json" \
-H "authorization: Bearer $PB_SESSION_JWT" \
-d '{"action":"cascade_dry_run","tenant_id":"YOUR_TENANT_ID","signal_type":"sim_swap"}'
curl -sS -X POST https://api.passkeybridge.io/v1/shield-cascade \
-H "content-type: application/json" \
-H "authorization: Bearer $PB_SESSION_JWT" \
-d '{"action":"cascade_receipt","tenant_id":"YOUR_TENANT_ID","correlation_id":"pb-19a2f3c4d5e-7b1c9f04"}'Replace the receipt request's correlation ID with one from an executed cascade. An absent receipt returns 404. A returned receipt includes its signed JWT, detached post-quantum proof, claims, verification instructions and the affected row IDs with completeness flags.
Illustrative hard-signal response fields. These values show the response shape and are not benchmark results:
{
"ok": true,
"signal_type": "sim_swap",
"signal_class": "hard",
"delegates_revoked": 4,
"negotiations_revoked": 2,
"shadows_frozen": 1,
"tunnels_torn_down": 3,
"cascade_latency_ms": 118,
"correlation_id": "pb-19a2f3c4d5e-7b1c9f04",
"dry_run": false
}An executed cascade also returns executed_at, revoked_ids, a signed receipt and receipt_stored, described in the API reference. A dry run returns the same shape as above with dry_run: true and the counts as "would be affected". A soft type returns signal_class: "soft", action_taken: "routed_to_trust_engine" and a message saying the evaluation is deferred to the trust engine through the ingest pipeline: the endpoint itself does not dispatch one, it records that the signal was routed.
Errors: 400 Unknown action. Supported: cascade_execute, cascade_dry_run, cascade_history, classify, get_model, update_threshold, cascade_receipt, 400 tenant_id required, 400 signal_type required (must be a string), 400 Unknown signal type: <value>, 400 limit must be a positive number, 400 when a classify request names no type or more than 50, 400 when a ladder is out of order (0 < read_only_at < full_at < 1) or past two decimal places, 400 when a cascade_receipt correlation_id is missing or not of the form pb-<hex>-<8 hex>, 403 plan_required or a missing-scope refusal, 404 when no receipt is stored for that correlation id, 405 Method not allowed, 413 Payload too large, 502 Cascade execution failed, 502 Dry run failed, 502 Failed to fetch cascade history, 502 when the organization's trust ladder cannot be read, saved or reset, 502 when a stored receipt cannot be read.
Audit actions are cascade_execute, cascade_dry_run, cascade_soft_routed and cascade_threshold_updated, all with resource_type: "cascade", which is exactly what cascade_history reads back. A cascade an inbound CAEP event started is a cascade_execute row with triggered_by: "caep". classify, get_model and cascade_receipt read only and write no audit row.
From the dashboard. Dashboard > Revocation offers five hard types (SIM swap, Port-out, SS7 intercept, Device compromise, Account takeover), a Dry run button, and a Run cascade button behind a confirmation dialog. The result card shows the four counts, the correlation id and a Copy receipt button for the signed receipt; History lists recent runs (including those an inbound CAEP event started), dry runs, soft routings and trust ladder changes, and copies the stored receipt of a run. The other four hard types are reachable through the API.
Confirming it ran. Compare the four counts in the response with the Agents, Legacy identifiers and Session tunnels tabs, and find the run in History or in the audit log under resource_type: "cascade".