AI Guard Policies
AI Guard applies policies to stop or limit the behavior Panopticon observes. We are designing four policies in the beta.
🔬 Some policies are at the design stage
Of the four policies this page covers, the HITL approval endpoint and the Soft Kill Switch CLI are not yet implemented in the codebase (audited 2026-05-15). Even if you define a policy in the start.1pass.dev/developer console, the logi server does not yet provide:
POST /panopticon/approval/request— not implemented (404)GET /panopticon/approval/<request_uuid>— not implemented (404)logi panopticon kill <client_id>CLI — not implemented- Kill-switch state columns such as
OauthApplication#killed_at— not implemented
Policies that work today: Scope Drift log_only (the default), and Rate Limit (the rack-attack throttle on /oauth/token and /oauth/userinfo). The rest can be defined in the console UI but are not wired to enforcement. For status inquiries, reach us at support@1pass.dev.
Policies at a glance
| Policy | Immediacy | RP-side burden | Status |
|---|---|---|---|
Rate Limit (rack-attack /oauth/*) | Immediate (server) | None | ✓ Implemented |
| Rate Limit (per-application × tool) | Immediate (server) | Receive webhook | 🔬 In design |
| Soft Kill Switch | ≤ 15 min | None | 🔬 In design |
| HITL approval | When the user responds | Extra endpoint call | 🔬 In design |
Scope Drift Policy log_only | Immediate | None | ✓ Implemented |
Scope Drift Policy alert / block | Immediate (/oauth/authorize) | None | 🔬 In design |
In the beta, the implemented policies are configured and enforced, but quota hard-blocking and billing are disabled.
1. Rate Limit
Defends against a flood of tool calls caused by LLM hallucination.
There are two kinds of Rate Limit, applied at different points.
A. logi endpoint throttle (existing rack-attack)
Protects the endpoints logi hosts directly, such as /oauth/token and /oauth/userinfo. Per application/IP.
B. AI Guard tool throttle (new in Panopticon)
For an application that reports traces, Panopticon counts (application × user) and (application × tool) usage on the logi side against these limits. When a limit is exceeded, logi fires a webhook (panopticon.anomaly_detected), and the RP decides whether to block or warn based on its own policy. logi does not block the tool call itself — the RP must receive the webhook and block on its own side.
Defaults (per application × user):
- 60 calls per minute
- 20 calls per minute for the same tool
Override (console or CLI):
logi panopticon policy <client_id> --rate-limit '{"per_minute":120,"per_tool_per_minute":40}'2. Soft Kill Switch 🔬 (design stage)
Not implemented — design spec
The trigger procedures below (CLI · console 1-click) have no route on the logi server yet. The OauthApplication#killed_at column, the logi panopticon kill CLI command, and the console Kill Switch tab are all in the backlog.
Blocking measures available today:
- Refresh token rotation + reuse detection: already implemented (
Oauth::TokensController#rotate_refresh_token+OauthAccessToken#revoke_chain!). On reuse detection, the entire chain is revoked automatically, so in a theft scenario this effectively blocks immediately. - Manual DB revoke: run
OauthAccessToken.where(oauth_application_id: APP_ID).find_each(&:revoke!)in the console. Once the RT is revoked, the next rotation attempt is rejected.
Immediate blocking of a specific application or (application × user).
Trigger (planned)
Console → application → Kill Switch tab → 1-click from the user matrix
Or the CLI:
logi panopticon kill <client_id> # the whole application
logi panopticon kill <client_id> --user user_xyz # a specific user only
logi agent revoke <client_id> # ALIAS for killBehavior (planned)
- Revoke all refresh tokens of the given
OauthAccessToken. - Existing access tokens expire naturally within at most 15 minutes (TTL 15 minutes).
- Fire the
panopticon.kill_switchwebhook.
If you want immediate blocking
The RP calls /oauth/introspect on every call (latency is on you, opt-in). The default RP accepts the 15-minute delay.
3. HITL approval (opt-in for sensitive tools) 🔬 (design stage)
Not implemented — no route
The POST /panopticon/approval/request and GET /panopticon/approval/<request_uuid> below do not exist in server/config/routes.rb (audited 2026-05-15). Calling the sample code as-is returns a 404.
Only PATCH /api/v1/me/panopticon/approvals/:id (user-session auth) partially exists — it is not an RP-facing PAK-auth API, so you cannot use it for RP integration.
A specific tool runs only after the user grants push approval in their logi app.
Configuration
logi panopticon policy <client_id> --hitl payment.charge,memory.delete,calendar.deleteOr console → Policies → add the tool name to the HITL tool list.
RP-side flow
# right before running the tool
async def call_payment_tool(amount, user_sub):
# 1. request approval from logi
resp = await httpx.post(
f"{LOGI_URL}/panopticon/approval/request",
headers={"Authorization": f"Bearer {PANOPTICON_PAK}"},
json={
"tool_name": "payment.charge",
"tool_args_summary": {"amount": amount, "currency": "KRW"},
"user_sub": user_sub,
}
)
request_uuid = resp.json()["request_uuid"]
# 2. wait for the user's response (polling or webhook)
while True:
status = await poll_approval(request_uuid)
if status == "approved":
return await execute_payment(amount)
elif status in ("denied", "expired"):
raise PermissionError(status)
await asyncio.sleep(2)The poll endpoint:
GET /panopticon/approval/<request_uuid>
Authorization: Bearer pano_pak_...Response: {"status":"pending|approved|denied|expired","approved_at":"..."}
TTL: 5 minutes. After it expires, it returns expired.
User experience
- When the RP calls the tool, a push notification arrives in the user's logi app.
- "MyAgent is requesting to run the payment tool — approve?"
- The user approves or denies in the logi app (or at
/auth/approve/<uuid>via a Universal Link). - The result is delivered to the RP by polling or webhook.
4. Scope Drift Policy
How to handle the case where the LLM requests a scope that is not registered.
The 3-level policy
| Policy | Behavior | Recommended for |
|---|---|---|
log_only (default) | The existing behavior: record the drift + scope.drift_detected webhook + proceed with the registered scope subset | General applications |
alert | log_only + show an inline alert card in the console | Applications under active monitoring |
block | When an unregistered scope is found at /oauth/authorize, return 400 invalid_scope (rejecting the authorization itself, RFC 6749 §4.1.2.1) | Security-sensitive applications |
Changing it
logi panopticon policy <client_id> --drift blockOr console → Policies → the Scope Drift Policy radio.
Every Scope Drift is recorded
Regardless of policy, ScopeDriftRecord ingests every drift event. On the first occurrence, a scope.drift_detected webhook fires; after escalation, a scope.drift_unresolved webhook fires (see the Webhook guide).
How usage relates to policy
During the beta, policy enforcement is separate from quota enforcement — policies enforce their own behavior, but no quota limit is applied. That is:
- Rate Limit: throttles when the per-minute limit is exceeded (applied).
- Soft Kill: only when the user runs it (no automatic trigger).
- HITL: user approval is required (applied).
- Scope Drift
block: rejects on an invalid scope (applied).
The quota policies to be added after GA (monthly trace limit, per-tier differentiation) are covered in a separate spec.
Next
- Trace API — the data source for policy
- Getting Started — from enabling Panopticon