왜 민감한 작업에서 에이전트를 그냥 차단하지 않나요?
애플리케이션 안의 어떤 작업은 다른 작업보다 더 중요합니다. 비밀번호 변경, 팀원 추가, 데이터 내보내기, 프로젝트 삭제, 주문 승인처럼, 이런 일이 적절한 권한 없이 발생하면 실제 피해가 생깁니다.
동시에, 앱에서 정당하게 수행되는 작업의 상당 부분은 자동화가 맡고 있습니다. 사용자가 비서를 통해 설정을 업데이트하게 할 수 있고, 파트너 통합이 일정에 따라 레코드를 생성할 수 있으며, 내부 에이전트가 지원 티켓을 처리할 수도 있습니다. 모든 자동화 클라이언트를 민감한 경로에서 차단하면 이런 작업이 깨지고, 사람들을 수작업으로 되돌리거나 보안이 더 어려운 우회 방식으로 밀어 넣게 됩니다.
답은 "에이전트를 차단하자"도 아니고 "에이전트를 믿자"도 아닙니다. 작업의 위험도, 요청 뒤의 권한, 그리고 actor에 대한 증거가 각각 다른 역할을 하는 모델입니다. 이 가이드는 그 모델을 설명합니다. 그 배경이 되는 더 넓은 사고는 사람과 AI 에이전트를 위한 앱 수준 정책 설계하기를 참고하세요.
1단계: 경로를 위험도에 따라 나누기
먼저 클라이언트가 아니라 작업을 분류하세요. 대부분의 애플리케이션에는 다음의 3단계 구분이 잘 맞습니다.
| 단계 | 예시 | 기본 요구 사항 |
|---|---|---|
| 낮음 | 공개 데이터 읽기, 검색, 자신의 대시보드 보기 | 일반 인증, 있다면 |
| 중간 | 자신의 콘텐츠 생성 또는 편집, 뷰어 초대 | 인증됨, 리소스에 대한 권한 부여됨 |
| 높음 | 자격 증명 변경, 소유권 변경, 대량 내보내기, 삭제, 승인 | 권한 부여 plus 단계적 확인 또는 검토 |
코드 리뷰에서 보이도록 경로 설정에 단계를 기록하세요.
type Tier = "low" | "medium" | "high";
export const routeTier: Record<string, Tier> = {
"GET /projects": "low",
"POST /projects": "medium",
"POST /projects/:id/members": "medium",
"POST /account/password": "high",
"POST /exports": "high",
"DELETE /projects/:id": "high",
};단계는 천천히 바뀌고 이해하기 쉽습니다. 이 가이드의 나머지는 모두 이 단계에 걸려 있습니다.
2단계: 신원, 자격 증명, 권한 부여는 어떻게 다른가요?
아래의 세 질문은 자주 한데 섞입니다.
- 누가 또는 무엇이 행동하고 있나요? 이것은 actor 증거입니다. 상호작용하는 사람, AI의 도움을 받는 사람, 에이전트, 또는 Unknown이며, 근거는 verified 또는 declared 같은 형태일 수 있습니다.
- 어떤 자격 증명이 제시되었나요? 세션 쿠키, API 키, OAuth 액세스 토큰.
- 이 자격 증명이 이 리소스에서 이 작업을 수행해도 되나요? 이것이 권한 부여입니다.
검증된 에이전트 서명(RFC 9421)은 첫 번째 질문에 잘 답합니다. 하지만 세 번째 질문에는 아무 말도 하지 않습니다. 이 점은 서명된 AI 에이전트 요청이 증명하는 것과 증명하지 못하는 것에서 자세히 다룹니다. 올바른 범위를 가진 OAuth 토큰은 세 번째 질문에 답합니다. 하지만 첫 번째 질문에 대해서는 거의 말해 주지 않습니다. 높은 단계의 작업에 대한 결정은 항상 세 번째 질문에 대한 좋은 답을 필요로 하며, 첫 번째 질문은 추가로 어느 정도의 보장을 요청할지 정하는 데 사용합니다.
interface Context {
actor: { actor: "human" | "human_ai" | "agent" | "unknown"; basis: "verified" | "declared" | "inferred" | "unknown" };
credential: { type: "session" | "oauth" | "api_key"; scopes: string[]; subject: string };
}
function authorized(ctx: Context, action: string, resourceOwner: string): boolean {
return ctx.credential.subject === resourceOwner && ctx.credential.scopes.includes(action);
}유리한 actor 결과가 나왔다고 해서 권한 부여를 느슨하게 해서는 안 됩니다. actor 문맥은 요구 사항을 추가하거나, 동등하게 안전한 경로 사이에서 선택하는 데만 사용합니다.
3단계: 사람이 대신하는 에이전트에는 위임된 권한을 사용하기
에이전트가 사용자를 대신해 행동할 때는 사용자의 세션을 재사용하게 두지 말고, 제한된 권한을 가진 자체 자격 증명을 주세요. 표준 도구는 OAuth 2.0입니다. 사용자가 특정 클라이언트에 대해 특정 범위를 승인하고, 토큰에는 그 클라이언트가 기록됩니다. RFC 9700, 즉 OAuth 2.0 Security Best Current Practice는 이를 안전하게 배포하는 방법을 설명합니다. MCP server도 같은 패턴을 따릅니다. MCP authorization specification은 보호된 MCP server를 OAuth 2.1 resource server로 다룹니다. 우리의 글 누가 여러분의 MCP server를 호출하고 있나요?는 이를 도구 호출에 적용합니다.
이렇게 하면 민감한 작업에 꼭 필요한 것이 정확히 마련됩니다.
- 토큰은 어떤 클라이언트가 누구를 위해 행동하는지 알려 줍니다.
- 범위는 할 수 있는 일을 제한합니다.
calendar:write를 가진 일정 관리 에이전트는 비밀번호를 변경할 수 없습니다. - 사용자는 자신의 자격 증명을 바꾸지 않고도 허용을 철회할 수 있으며, OAuth 2.0 Token Revocation (RFC 7009)은 클라이언트가 토큰이 더 이상 유효하지 않아야 한다고 authorization server에 알리는 방법을 정의합니다.
- 감사 로그에는 에이전트가 아니라 사용자가 작업을 수행한 것으로 기록됩니다.
앱이 아직 위임된 자격 증명을 지원하지 않는다면, 에이전트가 가장 자주 필요로 하는 작업부터 우선순위를 두세요. 자동화된 접근에 대해 가장 효과적인 단일 제어입니다.
4단계: 언제 단계적 확인을 요청해야 하나요?
단계적 확인은 민감한 작업이 발생하는 순간에 추가적인 보장을 요청하는 것입니다. OAuth로 보호되는 API의 경우 RFC 9470은 resource server가 현재 토큰의 인증이 충분히 강하지 않거나 충분히 최근이 아니라고 client에게 알리는 방법을 정의합니다. 적절한 단계적 확인은 누가 행동하는지에 따라 달라집니다.
- 상호작용하는 사람(Human, Human + AI): 재인증(NIST SP 800-63B의 재인증 요구 사항 참고), WebAuthn을 기반으로 한 passkey 요청, 또는 일회성 코드.
- 위임된 자격 증명을 가진 에이전트: 사용자가 명시적으로 부여한, 높은 단계의 작업 전용 범위, 또는 별도 경로로 사용자에게 보내는 확인.
- Unknown: 경로에 이미 있는 강한 확인을 그대로 사용합니다.
type Requirement = "none" | "reauth" | "explicit_scope" | "user_confirmation" | "review";
function stepUp(tier: Tier, ctx: Context): Requirement {
if (tier !== "high") return "none";
const { actor } = ctx.actor;
if (actor === "human" || actor === "human_ai") return "reauth";
if (actor === "agent" && ctx.credential.type === "oauth") return "explicit_scope";
if (actor === "agent") return "user_confirmation";
return "reauth";
}Human 결과가 단계적 확인을 건너뛰는 것은 아니라는 점에 주목하세요. 상호작용하는 사람에게 맞는 단계적 확인을 고를 뿐입니다. 추론은 틀릴 수 있으므로, 확인의 형태를 바꿀 수는 있어도 제거해서는 안 됩니다.
5단계: 판단이 필요한 곳에서는 사람 검토로 라우팅하기
일부 작업은 드물고 영향이 크며 안전하게 자동화하기가 어렵습니다. 고객 데이터의 대량 내보내기, 자격 증명 분실 후 계정 복구, 조직 삭제 같은 경우입니다. 이런 작업은 자동 규칙보다 검토 큐가 더 나을 때가 많습니다.
actor 문맥은 검토자에게 증거를 제공함으로써 검토를 더 빠르고 공정하게 만듭니다. 클래스, 근거, 사용된 자격 증명, 유사 요청의 이력입니다. 검토자가 결정합니다. 분류는 정보를 제공합니다.
function shouldReview(route: string, ctx: Context): boolean {
const reviewRoutes = new Set(["POST /exports", "POST /account/recover"]);
if (!reviewRoutes.has(route)) return false;
return !(ctx.credential.type === "oauth" && ctx.credential.scopes.includes("export:bulk"));
}6단계: 단계별 실패 동작을 정하기
actor 평가는 시간 초과가 나거나 증거가 부족할 수 있습니다. 그때 무엇이 일어나는지 미리 정하세요.
- 낮은 단계: fail open. 요청은 정상적으로 진행됩니다.
- 중간 단계: fail open, 정상 권한 부여가 유지된 상태에서.
- 높은 단계: 기존의 강한 확인으로 fail closed. 부족한 actor 증거는 보호를 약화시키지 않으며, 원래 정상 검사에서 통과했을 요청을 막지도 않습니다.
핵심 속성은 대칭성입니다. actor 평가 실패는 작업을 actor 문맥을 추가하기 전과 정확히 같은 수준으로 안전하고, 정확히 같은 수준으로 이용 가능하게 남겨야 합니다.
7단계: 추론만으로 사람에 대한 중대한 결정을 내리지 않기
추론된 분류는 사람에 대한 사실이 아니라 상호작용에 대한 증거입니다. 세션이 자동화되었거나 AI의 도움을 받은 것처럼 보인다는 이유로 취업 지원, 신용 결정, 시험 결과, 주거, 필수 서비스 접근을 거부해서는 안 됩니다. 이런 흐름에서는 actor 문맥을 사용해 사람 검토나 대체 검증 경로 같은 프로세스를 선택하고, 그 사람이 끝까지 완료할 수 있게 해야 합니다.
이것은 공정성 원칙이면서 실무 원칙이기도 합니다. 추론에는 오탐이 있고, 이런 흐름에서의 오탐 비용은 잘못한 것이 없는 사람에게 돌아갑니다.
8단계: 먼저 관찰 모드로 롤아웃하기
이 중 어떤 것도 실제 요청에 영향을 주기 전에, 관찰 및 preview 모드에서 실행하세요.
- 단계, actor 클래스, 근거, 자격 증명 유형, 그리고 정책상 요구될 단계적 확인을 기록합니다.
- 높은 단계 요청의 샘플을 클래스별로 검토합니다. 정당한 에이전트가 위임된 자격 증명을 쓰고 있나요, 아니면 사용자 세션을 재사용하고 있나요?
- 확인한 격차를 수정합니다. 많은 경우 scoped 자격 증명을 제공하는 것이 방법입니다. 그다음에야 enforce합니다.
- 단일 경로의 작은 부분에만 enforce하고, 완료율을 측정한 뒤, 범위를 넓힙니다.
9단계: "누가, 누구를 대신해"에 답하는 감사 추적을 유지하기
모든 높은 단계 작업에 대해, 나중에 무슨 일이 있었는지 재구성할 수 있을 만큼 충분히 기록하세요.
- 요청 시점의 actor 클래스와 근거,
- 자격 증명 유형, 그 자격 증명이 발급된 클라이언트, 그리고 그것이 나타내는 subject(OAuth 2.0 Token Exchange, RFC 8693은 바로 이런 종류의 "대신 행동하는" 관계를 기록하기 위한
actclaim을 정의합니다), - 작업을 승인한 범위,
- 적용된 단계적 확인 또는 검토 요구 사항과 그 결과.
audit.write({
action: "project.export",
actor: ctx.actor,
client: ctx.credential.type === "oauth" ? clientIdFromToken(req) : undefined,
subject: ctx.credential.subject,
scopes: ctx.credential.scopes,
requirement,
outcome: "approved",
at: new Date().toISOString(),
});이 추적이 있으면 "사람이 이 프로젝트를 삭제했나요, 아니면 비서가 했나요, 그리고 누가 승인했나요" 같은 질문에 분명하게 답할 수 있습니다. 그러면 인시던트 대응이 빨라지고, 앱 안의 자동화된 도움도 책임 있게 다뤄진다는 신뢰를 사용자에게 줄 수 있습니다.
예시로 살펴보기
프로젝트 관리 앱에서 비서가 사용자의 작업 마감일을 업데이트하고, 가끔 프로젝트 전체를 내보내려고 시도합니다.
- 작업 업데이트는 중간 단계입니다. 비서는
tasks:write이 있는 OAuth 토큰을 사용합니다. 권한 부여됨, 단계적 확인 없음. 허용. - 프로젝트 내보내기는 높은 단계입니다. 비서의 토큰에는
export:bulk가 없습니다. 앱은 사용자에게 확인 요청을 보냅니다. 사용자가 승인하고, 앱은 그 클라이언트에 범위를 부여합니다. - Unknown 클라이언트가 재사용된 세션 쿠키로 같은 내보내기를 시도합니다. 기존의 재인증 프롬프트가 그대로 적용되며, 이전과 정확히 같습니다.
아무도 에이전트라는 이유로 차단되지 않았습니다. 영향이 큰 모든 작업에는 명시적 권한이 붙었습니다.
Proof of Actor의 역할
Proof of Actor는 이 모델의 actor 쪽을 제공합니다. 누가 또는 무엇이 행동하는지, 그리고 어떤 근거로 행동하는지를 보여 줍니다. 애플리케이션은 인증, 권한 부여, 최종 결정을 유지합니다. actor를 Detect하고, 각 클래스가 민감한 경로를 어떻게 사용하는지 Understand하며, 각 요청을 적절한 보장 수준으로 Route하세요.
우리 팀과 함께 여러분의 애플리케이션에 맞게 설계하려면, early access 신청하기를 이용하세요. SaaS 시나리오를 더 자세히 보려면 SaaS Apps & Websites를 참고하세요.
출처
- RFC 6749: The OAuth 2.0 Authorization Framework: 클라이언트를 위한 범위가 지정된 위임 자격 증명
- RFC 9700: Best Current Practice for OAuth 2.0 Security: OAuth 배포를 위한 현재 보안 지침
- RFC 7009: OAuth 2.0 Token Revocation: 사용자의 자격 증명을 바꾸지 않고 허용을 철회하기
- RFC 9470: OAuth 2.0 Step Up Authentication Challenge Protocol: 더 강한 또는 더 최근의 인증이 필요함을 알리는 방법
- RFC 8693: OAuth 2.0 Token Exchange: 위임을 위한
act(actor) claim - MCP specification 2026-07-28: Authorization: OAuth 2.1 resource server로서의 MCP server
- NIST SP 800-63B: Authentication and Authenticator Management: 재인증 지침
- W3C Web Authentication (WebAuthn) Level 3: passkey 요청의 표준
- RFC 9421: HTTP Message Signatures: 검증된 에이전트 서명이 무엇을 확립하는지