AI Guard 정책
판옵티콘이 본 것을 정책으로 막는 것이 AI Guard입니다. 베타 기준으로 4종 정책 중 일부는 구현되어 있고, 일부는 설계 단계입니다.
AI 에이전트에게 얼마나 많은 권한을 줄지, 이탈했을 때 어떻게 멈출지를 정하는 것이 AI Guard가 존재하는 이유입니다. 이는 정부가 '에이전틱 AI 이니셔티브'에서 강조한 AI 에이전트 권한 관리·인간 감독 체계와 같은 방향입니다.
🔬 일부 정책은 설계 단계입니다
이 페이지가 다루는 4종 정책 중 Soft Kill Switch CLI 는 현재 코드베이스에 구현되지 않았습니다. logi 서버는 다음을 아직 제공하지 않습니다:
logi panopticon kill <client_id>CLI — 미구현OauthApplication#killed_at등 kill switch 상태 컬럼 — 미구현
HITL 승인의 사용자 결정 화면은 스토어 배포분에 아직 반영 전입니다. 서버 쪽(POST /panopticon/approvals — 복수형입니다. /panopticon/approval/request 는 존재한 적이 없습니다)은 요청 생성부터 폰 푸시 전달까지 동작하고, 앱 승인 화면과 approval_url(/approvals/<uuid>) 딥링크도 구현돼 있습니다. 앱 미설치·구버전 사용자는 그 URL 에서 정적 안내 페이지를 만납니다. RP 통합 시에는 승인이 오지 않는 경우(사용자가 결정 화면에 아직 닿지 못하는 구버전 앱)를 정상 경로로 다루세요 — TTL 5분 만료 후 expired 를 받는 흐름이 그 케이스입니다.
도구별 opt-in 목록(hitl_required_tools)은 개발자 콘솔의 앱 편집 화면에서 직접 설정할 수 있습니다(한 줄에 도구 하나). 운영자 문의는 더 이상 필요하지 않습니다.
프로덕션에서 사용자 승인이 실제로 필요하면 Agent Approval Gate를 쓰세요 — 그쪽은 앱 화면까지 완비돼 있습니다.
현재 동작 가능한 정책: Scope Drift Policy 3단계 전체 — 기본값은 block(2026-06-10부터)이라 미등록 scope는 /oauth/authorize에서 invalid_scope로 거절됩니다(log_only 완화는 운영자 admin API로만 설정). Rate Limit (rack-attack 단의 /oauth/token·/oauth/userinfo throttle). HITL 승인. 그 외는 enforcement 미연동입니다. 진행 상황 문의는 support@1pass.dev 로 보내주세요.
정책 한눈에
| 정책 | 즉시성 | RP 측 부담 | 상태 |
|---|---|---|---|
Rate Limit (rack-attack /oauth/*) | 즉시 (서버) | 없음 | ✓ 구현 완료 |
| Rate Limit (per-application × tool) | 즉시 (서버) | webhook 수신 | 🔬 설계 중 |
| Soft Kill Switch | ≤ 15분 | 없음 | 🔬 설계 중 |
| HITL 승인 | 사용자 응답 시 | endpoint 추가 호출 | 🔬 서버 브로커 + 푸시 전달 구현. 도구 목록은 개발자 콘솔에서 직접 설정. 앱 승인 화면·딥링크도 배선 완료지만 스토어 배포분에는 아직 반영 전 |
Scope Drift Policy (block 기본 / alert / log_only) | 즉시 (/oauth/authorize) | 없음 | ✓ 구현 완료 — 정책 변경은 운영자 admin API, 콘솔 토글·CLI는 로드맵 |
베타에서는 정책 설정·집행은 가능하지만 quota 강제와 과금은 비활성입니다 (구현된 정책 한정).
1. Rate Limit
LLM 환각으로 인한 도구 호출 폭주 방어.
두 종류의 Rate Limit이 존재하며 적용 지점이 다릅니다.
A. logi endpoint throttle (rack-attack 기존)
/oauth/token, /oauth/userinfo 등 logi가 직접 호스팅하는 endpoint에 대한 보호. application/IP 단위.
B. AI Guard tool throttle (Panopticon 신규) 🔬 (설계 단계)
미구현 — 설계 사양
아래 (application × user, application × tool) 카운팅·webhook 발사와 override CLI는 아직 구현되지 않았습니다. 현재 동작하는 Rate Limit은 위 A(rack-attack)뿐입니다.
RP가 trace를 보고하는 application에 대해 Panopticon이 logi 측에서 카운팅하는 (application × user, application × tool) 한도. 한도 초과 시 logi는 webhook(panopticon.anomaly_detected)을 발사하고, RP는 자기 정책에 따라 차단/경고를 결정합니다. logi가 도구 호출 자체를 막지는 않습니다 — RP는 webhook을 받아 자기 측에서 차단해야 합니다.
기본값 (per application × user):
- 분당 60회
- 동일 도구 분당 20회
override (콘솔 또는 CLI):
logi panopticon policy <client_id> --rate-limit '{"per_minute":120,"per_tool_per_minute":40}'2. Soft Kill Switch 🔬 (설계 단계)
미구현 — 설계 사양
아래 발동 절차 (CLI · 콘솔 1-click) 는 logi 서버에 아직 라우트가 없습니다. OauthApplication#killed_at 컬럼, logi panopticon kill CLI 명령, 콘솔 Kill Switch 탭 모두 backlog 입니다.
현재 가능한 차단 수단:
- Refresh token 회전+재사용 탐지: 이미 구현됨 (
Oauth::TokensController#rotate_refresh_token+OauthAccessToken#revoke_chain!). 재사용 감지 시 chain 전체가 자동 revoke 되므로, 도난 시나리오에서는 사실상 즉시 차단 효과. - 수동 DB revoke:
OauthAccessToken.where(oauth_application_id: APP_ID).find_each(&:revoke!)콘솔 실행. RT 가 revoke 되면 다음 회전 시도에서 거부.
특정 application 또는 (application × user) 단위 즉시 차단.
발동 (계획)
콘솔 → application → Kill Switch 탭 → 사용자 매트릭스에서 1-click
또는 CLI:
logi panopticon kill <client_id> # application 전체
logi panopticon kill <client_id> --user user_xyz # 특정 user만
logi agent revoke <client_id> # ALIAS for kill동작 (계획)
- 해당
OauthAccessToken의 모든 refresh token revoke - 기존 access token은 최대 15분 후 자연 만료 (TTL 15분)
panopticon.kill_switchwebhook 발사
즉시 차단을 원하면
RP가 매 호출마다 /oauth/introspect 호출 (latency 본인 부담, opt-in). 기본 RP는 15분 지연 수용.
3. HITL 승인 (민감 도구 opt-in) ✓
지정한 도구는 사용자가 logi 앱에서 승인해야만 실행할 수 있습니다. logi 는 승인 결정을 브로커할 뿐 도구 실행을 물리적으로 막는 집행 지점(PEP)은 아닙니다 — 실행을 approved 확인에 바인딩하는 것은 RP 몫입니다.
경로가 바뀌었습니다
이 문서의 2026-05 판은 POST /panopticon/approval/request 를 안내했지만 그 경로는 존재한 적이 없습니다. 실제 경로는 아래의 /panopticon/approvals(복수형) 입니다.
설정 (개발자 콘솔에서 직접)
도구별 opt-in 목록은 oauth_applications.hitl_required_tools 에 저장되며, 개발자 콘솔의 앱 편집 화면에서 한 줄에 하나씩 등록합니다. 이름은 MCP 서버가 보내는 tool_name 과 정확히 같아야 하고 대소문자도 구분합니다.
이름이 어긋나면 두 갈래로 갈립니다 — 어느 쪽도 "승인된 실행"이 아닙니다:
- 승인 요청을 보냈는데 그
tool_name이 목록에 없으면 422tool_not_hitl_required로 거절됩니다(fail-closed). 승인 레코드도, 푸시도 생기지 않으므로 이 응답을 받고 실행하면 안 됩니다. - 애초에 승인 요청을 보내지 않으면 logi 는 결정 브로커이지 집행 지점(PEP)이 아니라서 실행 자체는 막지 못합니다 — 실행을 승인에 바인딩하는 책임은 RP 쪽입니다. 다만 그 호출이 눈에 안 보이는 것은 아닙니다: 등록된 HITL 도구의 성공 trace 가 승인 근거 없이 들어오면 감사 원장에
required_but_missing정책 위반으로 기록됩니다.
logi panopticon policy --hitl CLI 는 아직 없습니다.
1. 승인 요청
POST /panopticon/approvals
Authorization: Bearer pano_pak_...
Content-Type: application/json
{
"tool_name": "payment.charge",
"idempotency_key": "<RP 고유 키>",
"args_digest": "<실행 인자의 SHA-256 hex 64자>",
"user_sub": "<이 앱의 pairwise sub>",
"display_title": "결제 승인",
"display_body": "12,000원을 결제합니다"
}idempotency_key·args_digest·tool_name·user_sub는 필수입니다.display_title(120자)·display_body(2000자)는 앱 상세 화면에만 표시됩니다. 잠금화면 알림에는 나오지 않습니다. Bearer 토큰·JWT·API 키 같은 시크릿 패턴이 들어 있으면 422 로 거절합니다.user_sub가 이 앱에 연결된 사용자로 해석되지 않으면 422user_unresolved(fail-closed — 레코드·푸시·webhook 모두 생성하지 않음).
응답 201(신규) 또는 200(같은 idempotency_key 재사용):
{
"request_uuid": "…",
"status": "pending",
"approval_url": "https://api.1pass.dev/approvals/…"
}같은 idempotency_key 를 다른 (user_sub, tool_name, args_digest) 로 재사용하면 409 idempotency_conflict.
2. 폴링
GET /panopticon/approvals/<request_uuid>
Authorization: Bearer pano_pak_...응답: {"request_uuid":"…","status":"pending|approved|denied|expired|consumed"}
TTL 5분. 만료된 pending 은 조회 시점에 expired 로 전이됩니다.
이미 소비된 승인은 consumed 로 내려옵니다 — 생성·폴링·소비 세 응답이 같은 규칙을 씁니다. 따라서 consume 응답을 못 받았을 때는 폴링으로 1차 판정하세요: consumed 면 앞선 호출이 이미 성공한 것입니다.
단 approved 는 "재시도 가능"과 동의어가 아닙니다. lazy expire 는 pending 행만 전이시키므로, 승인 후 소비 창(기본 300초)이 지난 행도 폴링에서는 계속 approved 로 보이고 consume 은 409 not_consumable 을 돌려줍니다.
재시도 대상은 consume 요청뿐입니다 — 같은 args_digest 로 consume 을 다시 호출하세요. 도구 실행은 consume 이 200 을 준 뒤에만 합니다. approved 를 보고 도구를 다시 실행하면 앞선 시도가 이미 실행됐을 경우 부작용이 두 번 일어납니다. 409 not_consumable 은 정상 종료 경로(창 만료 또는 이미 소비)로 처리하고 실행하지 마세요.
3. 소비 — 1승인 = 1실행
승인만으로 실행하지 말고, 실행 직전에 그 시점의 인자 해시를 다시 제출해 승인과 바인딩하세요.
POST /panopticon/approvals/<request_uuid>/consume
Authorization: Bearer pano_pak_...
{ "args_digest": "<실행 시점 인자의 SHA-256 hex>" }- 성공:
{"request_uuid":"…","status":"consumed"} - 승인 때와 인자가 다르면 409
args_mismatch— 사용자가 본 것과 다른 작업이 실행되는 것을 막습니다. - 미승인·이미 소비·소비 창 만료는 409
not_consumable.
사용자 경험
- RP 가 승인을 요청하면 사용자 logi 앱에 푸시가 도착합니다. 잠금화면에는 "logi · 도구 실행 승인 요청"과 도구 이름만 노출되고, RP 가 쓴 문구는 앱을 열어 인증한 뒤에만 보입니다.
- 사용자가 앱에서 승인/거부합니다.
- 결과가 폴링 또는 webhook 으로 RP 에 전달됩니다.
승인이 오지 않는 경로를 반드시 함께 다루세요
1·3단계(푸시 전달, 폴링·webhook)는 동작하고, 2단계의 앱 승인 화면과 approval_url(https://api.1pass.dev/approvals/<request_uuid>) 딥링크도 구현·라우팅돼 있습니다. 다만 다음 경우에는 알림이 사용자에게 닿지 않고 요청은 TTL 5분 뒤 expired 가 됩니다:
- 스토어에 올라간 앱 버전에 결정 화면이 아직 반영되기 전 — 구버전 앱 사용자는 링크에서 정적 안내 페이지를 만납니다.
- 푸시 받을 기기가 없음 — 푸시는 푸시 토큰이 있는 활성 기기에만 나갑니다. 대상 기기가 0대면 서버는 전달을 성공으로 처리하므로, 알림이 없었다는 사실은 응답에 드러나지 않습니다.
- 일시적 전달 실패(APNs/FCM 오류, 토큰 만료).
expired 를 "사용자가 거부함"으로 해석하지 마세요 — 결정이 없었던 것입니다.
에이전트(Claude·Codex·Cursor 등)가 자기 자격증명으로 승인을 요청하는 시나리오는 Agent Approval Gate가 담당합니다. 이 페이지의 HITL 은 RP 서버가 요청자인 경우입니다.
4. Scope Drift Policy
LLM이 등록되지 않은 scope를 요청하는 경우의 처리.
3단계 정책
| 정책 | 동작 | 추천 |
|---|---|---|
block (기본값, 2026-06-10부터) | /oauth/authorize에서 미등록 scope 발견 시 400 invalid_scope 반환 (인가 자체 거부, RFC 6749 §4.1.2.1) | 모든 application (기본) |
alert | drift 기록 + 미등록 scope는 버리고 등록된 subset으로 진행 + 관리자 푸시 알림 (30분 cooldown) | 완화가 필요하되 모니터링 중인 application |
log_only | drift 기록 + 등록된 subset으로 진행 | opt-in 완화 — 운영자 admin API로만 설정 가능 |
변경
현재 정책 변경은 운영자 admin API로만 가능합니다. 콘솔 셀프서브 라디오와 logi panopticon policy --drift CLI는 로드맵입니다. 완화가 필요하면 support@1pass.dev로 문의해 주세요.
Scope Drift는 모두 기록됨
정책에 관계없이 ScopeDriftRecord는 모든 drift 이벤트를 적재합니다. 첫 발생 시 scope.drift_detected webhook, escalation 후 scope.drift_unresolved webhook이 발사됩니다 (Webhook 가이드).
사용량과 정책의 관계
베타 동안 구현된 정책은 enforcement만 하고 quota를 차단하지 않습니다. 즉:
- Rate Limit (rack-attack): 분당 한도 초과 시 throttle (적용됨)
- Scope Drift
block: 미등록 scope 시invalid_scope거부 (적용됨, 기본값) - 판옵티콘 HITL 승인: 서버 브로커 + 푸시 전달 적용됨 (앱 결정 화면은 구현됐으나 스토어 배포분 반영 전 — 승인이 오지 않는 경로를 함께 다뤄야 함)
- Soft Kill Switch: 설계 단계 (미적용)
정식 출시 후 추가될 quota 정책 (월 trace 한도, tier별 차등)은 별도 spec에서 다룹니다.