Skip to content

AI Guard Policy Configuration

Source: en/panopticon/policies.md · Live: https://docs.1pass.dev/en/panopticon/policies LLM-sanitized: internal links absolutized, VitePress containers → admonitions, line numbers in the Jump-to Index reference this rendered file (1-indexed).

📍 Jump-to Index

  • L51-L64: ## Policies at a glance
  • L65-L91: ## 1. Rate Limit
    • L71-L73: ### A. logi endpoint throttle (existing rack-attack)
    • L74-L91: ### B. AI Guard tool throttle (new in Panopticon) 🔬 (design stage)
  • L92-L122: ## 2. Soft Kill Switch 🔬 (design stage)
    • L103-L112: ### Trigger (planned)
    • L113-L117: ### Behavior (planned)
    • L118-L122: ### If you want immediate blocking
  • L123-L220: ## 3. HITL approval (opt-in for sensitive tools) ✓
    • L130-L140: ### Configuration (directly in the developer console)
    • L141-L171: ### 1. Request approval
    • L172-L187: ### 2. Poll
    • L188-L202: ### 3. Consume — one approval, one execution
    • L203-L220: ### User experience
  • L221-L240: ## 4. Scope Drift Policy
    • L225-L232: ### The 3-level policy
    • L233-L235: ### Changing it
    • L236-L240: ### Every Scope Drift is recorded
  • L241-L250: ## How usage relates to policy
  • L251-L255: ## Next

AI Guard Policies

AI Guard applies policies to stop or limit the behavior Panopticon observes. As of the beta, some of the four policies are implemented and some are still at the design stage.

Deciding how much permission to grant an AI agent, and how to stop it when it drifts, is why AI Guard exists. That's the same direction as the "governing AI agent permissions" and "human oversight" principles in Korea's Agentic AI Initiative.

⚠️ Warning: 🔬 Some policies are at the design stage Of the four policies this page covers, the Soft Kill Switch CLI is not yet implemented in the codebase. The logi server does not yet provide:

  • The logi panopticon kill <client_id> CLI — not implemented
  • Kill-switch state columns such as OauthApplication#killed_at — not implemented

The HITL user-decision screen is not in the store release yet. The server side (POST /panopticon/approvals — plural; /panopticon/approval/request never existed) works end to end, from creating the request to delivering the push to the phone, and the app approval screen plus the approval_url (/approvals/<uuid>) deep link are implemented. Users without the app — or on an older build — get a static landing page at that URL. When integrating, treat "no approval arrives" as a normal path: an older app that can't reach the decision screen yields the TTL-expiry flow, where you receive expired after 5 minutes.

The per-tool opt-in list (hitl_required_tools) can now be set directly in the developer console, on the app edit screen, one tool per line. An operator request is no longer needed.

If you need user approval to actually work in production, use Agent Approval Gate instead — that flow has the app screens fully built.

Policies that work today: all three Scope Drift Policy levels — the default is block (since 2026-06-10), so unregistered scopes are rejected with invalid_scope at /oauth/authorize (relaxing to log_only is operator-only via the admin API) — Rate Limit (the rack-attack throttle on /oauth/token and /oauth/userinfo) — and HITL approval. The rest are not wired to enforcement. For status inquiries, reach us at support@1pass.dev.

Policies at a glance

PolicyImmediacyRP-side burdenStatus
Rate Limit (rack-attack /oauth/*)Immediate (server)None✓ Implemented
Rate Limit (per-application × tool)Immediate (server)Receive webhook🔬 In design
Soft Kill Switch≤ 15 minNone🔬 In design
HITL approvalWhen the user respondsExtra endpoint call🔬 Server broker + push delivery implemented. Tool list is set directly in the developer console. The app approval screen and deep link are wired too, but not in the store release yet
Scope Drift Policy (block default / alert / log_only)Immediate (/oauth/authorize)None✓ Implemented — policy changes are operator-only via the admin API; console toggle and CLI are on the roadmap

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) 🔬 (design stage)

⚠️ Warning: Not implemented — design spec The (application × user, application × tool) counting, the webhook firing, and the override CLI below are not implemented yet. The only Rate Limit that works today is A (rack-attack) above.

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):

bash
logi panopticon policy <client_id> --rate-limit '{"per_minute":120,"per_tool_per_minute":40}'

2. Soft Kill Switch 🔬 (design stage)

⚠️ Warning: 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:

bash
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 kill

Behavior (planned)

  1. Revoke all refresh tokens of the given OauthAccessToken.
  2. Existing access tokens expire naturally within at most 15 minutes (TTL 15 minutes).
  3. Fire the panopticon.kill_switch webhook.

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) ✓

A specified tool can only run once the user approves it in the logi app. logi only brokers the approval decision — it is not an enforcement point (PEP) that physically blocks tool execution. Binding execution to an approved check is the RP's responsibility.

💡 Tip: The route has changed The 2026-05 edition of this document pointed to POST /panopticon/approval/request, but that route never existed. The actual route is /panopticon/approvals (plural), described below.

Configuration (directly in the developer console)

The per-tool opt-in list is stored in oauth_applications.hitl_required_tools, and you register entries in the developer console's app edit screen, one per line. Names must match the tool_name your MCP server sends exactly, including case.

A name mismatch splits into two cases — neither of which is an approved execution:

  • If you send an approval request whose tool_name isn't on the list, it is rejected with 422 tool_not_hitl_required (fail-closed). No approval record and no push are created, so you must not execute after this response.
  • If you never send an approval request, logi brokers decisions and is not a policy enforcement point (PEP), so it cannot stop the execution itself — binding execution to an approval is the RP's responsibility. The call is not invisible, though: when a successful trace for a registered HITL tool arrives with no approval evidence, it is recorded in the audit ledger as a required_but_missing policy violation.

The logi panopticon policy --hitl CLI does not exist yet.

1. Request approval

http
POST /panopticon/approvals
Authorization: Bearer pano_pak_...
Content-Type: application/json

{
  "tool_name": "payment.charge",
  "idempotency_key": "<an RP-unique key>",
  "args_digest": "<SHA-256 hex, 64 chars, of the execution args>",
  "user_sub": "<this app's pairwise sub>",
  "display_title": "Confirm payment",
  "display_body": "Charge ₩12,000"
}
  • idempotency_key, args_digest, tool_name, and user_sub are required.
  • display_title (120 chars) and display_body (2000 chars) are shown only on the in-app detail screen. They never appear in the lock-screen notification. A request whose display fields contain a secret pattern (bearer token, JWT, API key, etc.) is rejected with 422.
  • If user_sub does not resolve to a user connected to this app, the request is rejected with 422 user_unresolved (fail-closed — no record, push, or webhook is created).

Response 201 (new) or 200 (reusing the same idempotency_key):

json
{
  "request_uuid": "…",
  "status": "pending",
  "approval_url": "https://api.1pass.dev/approvals/…"
}

Reusing the same idempotency_key with a different (user_sub, tool_name, args_digest) returns 409 idempotency_conflict.

2. Poll

http
GET /panopticon/approvals/<request_uuid>
Authorization: Bearer pano_pak_...

Response: {"request_uuid":"…","status":"pending|approved|denied|expired|consumed"}

TTL is 5 minutes. A pending request that has expired transitions to expired the next time it's read.

An approval that has already been consumed comes back as consumed — create, poll, and consume all follow the same rule. So when you never got a response from consume, poll first: consumed means the earlier call already succeeded.

But approved is not a synonym for "safe to retry." Lazy expiry only transitions pending rows, so a row whose consume window (300 seconds by default) has elapsed still reads as approved while consume returns 409 not_consumable.

What you retry is the consume request only — call consume again with the same args_digest. Execute the tool only after consume returns 200. Re-running the tool just because you saw approved duplicates the side effects when an earlier attempt already executed. Treat 409 not_consumable as a normal terminal outcome (window elapsed, or already consumed) and do not execute.

3. Consume — one approval, one execution

Don't execute on the approval alone. Right before executing, resubmit the args digest at that moment to bind the execution to the approval.

http
POST /panopticon/approvals/<request_uuid>/consume
Authorization: Bearer pano_pak_...

{ "args_digest": "<SHA-256 hex of the args at execution time>" }
  • Success: {"request_uuid":"…","status":"consumed"}
  • If the args differ from what was approved, 409 args_mismatch — this prevents an action other than the one the user saw from being executed.
  • Not approved, already consumed, or the consume window has expired: 409 not_consumable.

User experience

  1. When the RP requests an approval, a push notification arrives in the user's logi app. The lock screen shows only "logi · Tool execution approval requested" and the tool name — the text the RP wrote is visible only after the user opens and authenticates into the app.
  2. The user approves or denies in the app.
  3. The result is delivered to the RP by polling or webhook.

⚠️ Warning: Always handle the "no approval arrives" path Steps 1 and 3 (push delivery, polling/webhook) work, and the step-2 app approval screen plus the approval_url (https://api.1pass.dev/approvals/<request_uuid>) deep link are implemented and routed. Still, in these cases the notification never reaches the user and the request becomes expired after its 5-minute TTL:

  • Before the decision screen ships in the store build — users on an older app land on a static page.
  • No device to push to — pushes only go to active devices that have a push token. If the target set is empty the server treats delivery as successful, so a missing notification does not show up in the response.
  • Transient delivery failures (APNs/FCM errors, expired tokens).

Don't read expired as "the user denied it" — it means no decision was made.

The scenario where an agent (Claude, Codex, Cursor, etc.) requests approval under its own credentials is handled by Agent Approval Gate instead. The HITL flow on this page is for when the RP server itself is the requester.


4. Scope Drift Policy

How to handle the case where the LLM requests a scope that is not registered.

The 3-level policy

PolicyBehaviorRecommended for
block (default, since 2026-06-10)When an unregistered scope is found at /oauth/authorize, return 400 invalid_scope (rejecting the authorization itself, RFC 6749 §4.1.2.1)All applications (default)
alertRecord the drift + drop the unregistered scopes and proceed with the registered subset + push an admin notification (30-minute cooldown)Applications that need relaxation but are actively monitored
log_onlyRecord the drift + proceed with the registered subsetOpt-in relaxation — operator-only via the admin API

Changing it

Policy changes are currently operator-only via the admin API. The console self-serve radio and the logi panopticon policy --drift CLI are on the roadmap. If you need relaxation, contact support@1pass.dev.

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, the implemented policies enforce their own behavior, but no quota limit is applied. That is:

  • Rate Limit (rack-attack): throttles when the per-minute limit is exceeded (applied).
  • Scope Drift block: rejects unregistered scopes with invalid_scope (applied, the default).
  • Panopticon HITL approval: server broker + push delivery applied (the app decision screen is implemented but not in the store release yet — handle the "no approval arrives" path too).
  • Soft Kill Switch: design stage (not applied).

The quota policies to be added after GA (monthly trace limit, per-tier differentiation) are covered in a separate spec.

Next

최종 수정:

Identity가 제품의 신뢰를 만듭니다.