Skip to content

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.

🔬 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: 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) — and Rate Limit (the rack-attack throttle on /oauth/token and /oauth/userinfo). 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🔬 In design
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)

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)

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) 🔬 (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.

If you need an approval-decision workflow that works today, use Agent Approval Gate. It's already implemented: an Ed25519-signed agent identity (agt_) authenticates the request, and the user approves a high-risk action like a payment or a delete with their own WebAuthn (passkey) signature on their phone, and every decision is recorded in a tamper-proof, append-only (WORM) audit log. Note that the Gate produces and returns an approval decision — it is not an enforcement point (PEP) that physically blocks tool execution. Binding tool execution to the approved status is the RP/tool side's responsibility, and logi cannot stop an agent that bypasses the Gate and executes directly (see bypass limitations).

A specific tool runs only after the user grants push approval in their logi app (design spec — Panopticon itself does not yet enforce this. For now, have your RP bind tool execution to an Agent Approval Gate approval decision as described above).

Configuration

bash
logi panopticon policy <client_id> --hitl payment.charge,memory.delete,calendar.delete

Or console → Policies → add the tool name to the HITL tool list.

RP-side flow

python
# 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:

http
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

  1. When the RP calls the tool, a push notification arrives in the user's logi app.
  2. "MyAgent is requesting to run the payment tool — approve?"
  3. The user approves or denies in the logi app (or at /auth/approve/<uuid> via a Universal Link).
  4. 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

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).
  • Soft Kill / the Panopticon HITL endpoint: design stage (not applied — for HITL, Agent Approval Gate is the available substitute).

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

Next

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