AI 에이전트 권한을 최소화하는 방법
AI 에이전트 권한을 최소화하는 방법
에이전트의 판단 정확도를 높이는 것만으로 안전한 실행을 보장할 수 없다. Prompt injection, 잘못된 추론, 오래된 상태와 재시도는 언제든 발생할 수 있다. 그래서 에이전트가 틀려도 읽을 수 있는 데이터, 변경할 수 있는 리소스, 호출할 수 있는 네트워크와 자격 증명의 수명이 작업에 필요한 최소 범위 안에 머물도록 설계해야 한다.
목차
- #에이전트가 틀리지 않을 것이라는 가정
- #최소 권한을 여러 축으로 나눈다
- #사람의 권한을 그대로 위임하지 않는다
- #도구별로 실행 주체와 자격 증명을 분리한다
- #리소스와 작업 범위를 Capability로 제한한다
- #짧은 수명의 자격 증명을 사용한다
- #네트워크 접근도 권한이다
- #파일 시스템 권한은 경로와 동작으로 나눈다
- #읽기 권한도 데이터 유출을 만들 수 있다
- #정책 검사는 모델 밖에서 수행한다
- #재구성한 권한 정책과 Executor 예제
- #Confused Deputy와 Token Passthrough를 막는다
- #권한 상승과 Break Glass
- #감사와 탐지 지표
- #최소 권한을 테스트하는 방법
- #마무리
- #참고 자료
- #관련 노트
에이전트가 틀리지 않을 것이라는 가정
코딩 에이전트에게 저장소 정리를 요청했다고 하자. 가장 편한 구현은 사용자 계정의 홈 디렉터리와 shell, network credential을 그대로 제공하는 것이다.
filesystem: full home directory read/write
shell: unrestricted
network: unrestricted egress
cloud: administrator credential
git: push to every repository
에이전트가 요청을 정확히 이해하면 빠르게 작업할 수 있다. 하지만 다음 중 하나만 발생해도 영향 범위가 커진다.
build폴더를 지우라는 요청을 workspace root 삭제로 해석한다.- 저장소 문서에 포함된 prompt injection을 지시로 받아들인다.
- test 계정이라고 생각하고 production API를 호출한다.
- 로그를 분석하다 secret 파일을 외부 서비스에 전송한다.
- timeout 뒤 같은 결제 또는 배포 작업을 다시 실행한다.
- dependency가 반환한 악의적인 텍스트를 다음 명령으로 사용한다.
flowchart LR
A[작은 판단 오류] --> B{에이전트 권한 범위}
B -- 넓음 --> C[여러 시스템과 데이터에 확산]
B -- 제한됨 --> D[작업 범위 안에서 실패]최소 권한은 모델이 안전하다는 가정이 깨졌을 때 피해 범위를 제한한다. NIST는 최소 권한을 사용자 또는 그 대신 동작하는 프로세스가 맡은 작업에 필요한 최소 자원과 권한만 갖게 하는 원칙으로 정의한다. 에이전트도 사용자를 대신하는 프로세스다.
“모델이 이 도구를 잘 선택하는가?”와 함께 “잘못 선택해도 시스템이 허용된 범위 밖에서는 실행하지 않는가?”를 검증한다.
최소 권한을 여러 축으로 나눈다
권한을 admin과 read-only 두 단계로만 나누면 실제 작업 범위를 충분히 표현하지 못한다.
누가 subject: 어느 사용자·service·agent run인가
무엇을 action: read, create, update, delete, send인가
어디에 resource: 어느 project·folder·record인가
언제 time: 얼마 동안 유효한가
어디서 environment: staging인가 production인가
어디로 network: 어떤 host와 port에 연결하는가
얼마나 limits: 횟수·금액·bytes·recipients 한도
왜 purpose: 어떤 승인된 task를 위한 것인가
이를 교차한 결과가 한 실행의 권한이다.
Effective Permission
= Actor
∩ Task
∩ Tool
∩ Resource
∩ Action
∩ Environment
∩ Time
∩ Limits
예를 들어 “문서 초안을 만든다”는 작업은 다음처럼 좁힐 수 있다.
task: draft-weekly-report
actor: user-example
expires_at: 2026-04-28T11:00:00Z
filesystem:
read:
- /workspace/source-notes
write:
- /workspace/output/report.md
delete: []
network:
allow:
- host: docs.example.test
port: 443
tools:
- read_document
- write_draft
limits:
max_output_bytes: 1048576
max_tool_calls: 50
write: /workspace처럼 넓은 prefix보다 구체적인 output 경로를 사용한다. 작업 성격상 여러 파일이 필요하면 새로 만든 task directory 안으로 제한한다.
사람의 권한을 그대로 위임하지 않는다
사용자가 production 관리자라고 해서 에이전트도 같은 권한을 가져야 하는 것은 아니다.
flowchart LR
U[Human User
broad organizational role] --> P[Task Proposal]
P --> D[Delegation Policy]
D --> T[Task-scoped Credential]
T --> E[Agent Executor]사람의 장기 권한은 다양한 업무를 위한 상한이다. 에이전트 실행에는 이번 작업에 필요한 일부만 위임한다.
Human:
- projects A, B, C 관리
- production 배포
- billing 확인
Agent task:
- project B의 staging release note 초안 읽기
- 지정 문서 한 곳에 draft 작성
사람의 browser cookie나 개인 access token을 agent runtime에 그대로 복사하면 권한 축소와 감사가 어렵다. Delegation service가 task-bound token을 발급하는 편이 낫다.
{
"subject": "agent-run-example",
"delegated_by": "user-example",
"audience": "document-service",
"scope": [
"document:read:project-b",
"document:create:drafts"
],
"task_id": "task-example-42",
"expires_at": "2026-04-28T11:00:00Z"
}
이 token은 cloud administrator API나 project A에 사용할 수 없어야 한다. 대상 서비스가 audience, scope, task와 resource-level authorization을 모두 검증한다.
Downstream이 agent service를 사용자와 같은 client로 오해하거나 token이 다른 audience에서 재사용되는 confused deputy 문제가 생길 수 있다.
도구별로 실행 주체와 자격 증명을 분리한다
하나의 agent process에 DB, email, cloud, Git credential을 모두 넣으면 어떤 tool이 compromise돼도 모든 권한에 접근할 수 있다.
Agent Orchestrator
├── read_document executor
├── send_email executor
├── deploy_staging executor
└── query_metrics executor
각 executor는 다른 service account와 scope를 가진다.
| Executor | 허용 | 금지 |
|---|---|---|
| Document reader | 지정 project 문서 읽기 | 쓰기·공유 변경 |
| Draft writer | drafts folder 파일 생성 | 기존 파일 삭제 |
| Email sender | 승인 template 단건 발송 | 임의 HTML·대량 발송 |
| Staging deployer | staging 특정 service 배포 | production·IAM 변경 |
| Metrics reader | allowlist query 실행 | raw customer log 읽기 |
Orchestrator는 실제 credential 대신 executor 호출 권한만 갖는다. Executor는 tool schema와 정책을 다시 검사한다.
flowchart LR
L[LLM] --> O[Orchestrator
no broad credential]
O --> P[Policy Gateway]
P --> D[Document Executor]
P --> M[Mail Executor]
P --> S[Staging Deploy Executor]도구 설명이 read_only: true라고 주장해도 그 metadata가 신뢰하는 registry에서 온 것인지 확인한다. 외부 tool server가 제공한 annotation만으로 credential을 선택하지 않는다.
리소스와 작업 범위를 Capability로 제한한다
전역 권한 대신 특정 객체와 동작을 가리키는 capability를 발급할 수 있다.
{
"capability_id": "cap-example-42",
"task_id": "task-example-42",
"resource": {
"type": "document",
"id": "document-example-7"
},
"actions": ["read", "append_comment"],
"constraints": {
"max_comments": 3,
"expires_at": "2026-04-28T11:00:00Z"
}
}
에이전트가 임의 document ID를 생성해도 capability가 가리키는 객체 밖에는 접근할 수 없다.
type Capability = {
taskId: string;
resourceType: "document";
resourceId: string;
actions: Array<"read" | "append_comment">;
expiresAt: Date;
maxComments: number;
};
function permits(input: {
capability: Capability;
taskId: string;
documentId: string;
action: "read" | "append_comment";
}): boolean {
return (
input.capability.taskId === input.taskId &&
input.capability.resourceId === input.documentId &&
input.capability.actions.includes(input.action) &&
input.capability.expiresAt > new Date()
);
}
Capability 문자열 자체가 탈취될 수 있으므로 서명, audience, 짧은 만료, 단일 사용 nonce와 안전한 저장이 필요하다.
리소스 prefix를 사용할 때 경계를 명확히 한다.
허용 의도: project/12/*
잘못된 문자열 prefix: project/12 also matches project/123
문자열 시작 여부가 아니라 구조화한 resource hierarchy와 canonical ID로 검사한다.
짧은 수명의 자격 증명을 사용한다
환경 변수에 장기 API key를 넣으면 agent run이 끝난 뒤에도 유효하고 로그나 child process로 퍼질 수 있다.
long-lived credential
= 넓은 노출 시간
+ 회수 어려움
+ task와 연결하기 어려운 audit
실행 직전에 짧은 token을 발급하고 작업이 끝나면 만료되게 한다.
sequenceDiagram
participant O as Orchestrator
participant B as Credential Broker
participant E as Executor
participant R as Resource Server
O->>B: task와 승인 증거 제출
B-->>E: audience-bound short token
E->>R: scoped operation
R-->>E: result
Note over E,R: token expires shortlyToken claim 예시다.
{
"sub": "agent-executor-example",
"aud": "document-api",
"scope": "document.append_comment",
"resource_id": "document-example-7",
"task_id": "task-example-42",
"jti": "token-example-92",
"iat": 1777373400,
"exp": 1777373700
}
단순히 만료를 5분으로 설정했다고 충분한 것은 아니다.
- Token audience를 resource server가 검증하는가
- task가 취소됐을 때 즉시 회수하거나 denylist 처리할 수 있는가
- 재발급 횟수와 조건이 제한되는가
- refresh token을 agent sandbox에 주지 않는가
- token이 로그, trace, error에 남지 않는가
- clock skew와 long-running operation을 어떻게 처리하는가
장기 작업은 긴 token 하나보다 단계별 lease와 재인가를 사용할 수 있다.
네트워크 접근도 권한이다
Filesystem이 read-only여도 자유로운 network egress가 있으면 읽은 데이터를 외부로 보낼 수 있다.
read sensitive file
+ unrestricted HTTPS
= potential exfiltration path
Domain allowlist만으로도 부족할 수 있다.
- DNS rebinding
- Redirect로 다른 host 이동
- 허용 domain의 사용자 업로드 endpoint
- 같은 host의 강한 쓰기 API
- proxy 환경 변수 우회
- IP literal과 IPv6
Network policy는 목적지, port, protocol, redirect, DNS resolution과 proxy를 함께 다룬다.
egress:
default: deny
allow:
- host: docs.example.test
port: 443
methods: [GET]
max_response_bytes: 5242880
- host: package-registry.example.test
port: 443
methods: [GET]
HTTP method 제한은 network layer만으로 완전한 authorization이 아니다. API credential과 resource server 정책이 함께 필요하다.
외부 URL fetch 도구는 SSRF를 막아야 한다.
function isBlockedAddress(hostname: string): boolean {
return (
hostname === "localhost" ||
hostname.endsWith(".internal") ||
hostname === "169.254.169.254"
);
}
이 간단한 함수는 개념 예시일 뿐 실제 방어로 충분하지 않다. DNS를 resolve한 모든 IP의 private·link-local 범위를 검사하고 redirect마다 재검증하며, 가능하면 전용 egress proxy를 사용한다.
모델이나 문서가 만든 URL을 허용된 목적지라고 가정하지 않는다. Network broker가 독립적으로 정책을 검사한다.
파일 시스템 권한은 경로와 동작으로 나눈다
Agent가 workspace 하나만 사용해도 모든 파일에 같은 권한이 필요한 것은 아니다.
/workspace
├── source/ read
├── generated/ read/write
├── secrets/ no access
├── .git/ read, write restricted
└── config/ read
경로 검사는 .., symlink, case sensitivity, mount를 고려해야 한다.
import { realpath } from "node:fs/promises";
import { relative, resolve } from "node:path";
async function isWithinAllowedRoot(
candidate: string,
allowedRoot: string,
): Promise<boolean> {
const root = await realpath(resolve(allowedRoot));
const target = await realpath(resolve(candidate));
const rel = relative(root, target);
return rel === "" || (!rel.startsWith("..") && !rel.startsWith("/"));
}
새 파일은 아직 realpath가 실패할 수 있다. 부모 directory의 canonical path를 확인하고 파일 생성 시 symlink race를 막는 OS 수준 sandbox가 필요하다. 애플리케이션 path check만으로 confinement를 구현하지 않는다.
작업별 임시 directory를 주고 완료 artifact만 밖으로 복사하는 방법이 단순하다.
/sandbox/tasks/task-example-42
├── input/ read-only mount
├── work/ read-write
└── output/ artifact collection
Git 권한도 분리한다.
read repository
write working tree
create local commit
push branch
merge pull request
modify branch protection
코드를 수정할 권한과 protected branch에 push할 권한은 다르다. 에이전트가 작업 branch를 만들 수 있어도 merge는 사람과 CI가 결정하도록 할 수 있다.
Sandbox 설계는 에이전트 작업을 샌드박스에서 실행하기에서 더 자세히 이어간다.
읽기 권한도 데이터 유출을 만들 수 있다
읽기 도구는 시스템 상태를 바꾸지 않지만 민감 정보를 노출할 수 있다.
read email
read private document
query production database
list cloud resources
read environment variables
한 번 읽은 정보는 모델 context, 로그, tool output, 외부 API 요청으로 퍼질 수 있다.
flowchart LR
D[Private Data] --> T[Read Tool]
T --> C[Model Context]
C --> L[Logs/Traces]
C --> O[Tool Output]
C --> X[External Model/API]따라서 읽기에도 다음 제한을 둔다.
- 사용자가 볼 수 있는 resource만 조회
- 필요한 field만 projection
- 행·문서·시간 범위 제한
- 결과 크기와 페이지 수 제한
- 개인정보 redaction
- 외부 전송은 기본적으로 거부
- 외부 model로 전달 가능한 데이터 분류
- cache와 log retention
-- 나쁜 예: 전체 customer row
SELECT *
FROM customers
WHERE organization_id = :organization_id;
-- 필요한 필드와 수량만
SELECT id, display_name, support_tier
FROM customers
WHERE organization_id = :organization_id
ORDER BY id
LIMIT 100;
SQL 자체를 모델에게 자유롭게 생성·실행하게 하기보다 승인된 query template과 parameter를 제공한다.
정책 검사는 모델 밖에서 수행한다
Prompt에 “production은 절대 수정하지 마”라고 쓰는 것은 방어층 하나일 뿐 강제 가능한 정책이 아니다.
모델 지시:
- production 수정 금지
실행 시스템:
- production credential 존재
- network 접근 허용
- tool이 environment 인자 수용
공격 입력이나 추론 오류로 prompt 규칙이 깨지면 실행된다. 실제 executor가 거부해야 한다.
type PolicyInput = {
actorId: string;
taskId: string;
tool: string;
action: string;
resource: {
type: string;
id: string;
environment: "development" | "staging" | "production";
};
approved: boolean;
now: Date;
};
type PolicyDecision =
| { allowed: true; constraints: Record<string, unknown> }
| { allowed: false; code: string };
정책은 명시적으로 deny한다.
function evaluatePolicy(input: PolicyInput): PolicyDecision {
if (input.resource.environment === "production") {
return {
allowed: false,
code: "production_access_not_available_to_this_agent",
};
}
if (input.action === "delete") {
return {
allowed: false,
code: "delete_not_allowed",
};
}
if (input.action === "write" && !input.approved) {
return {
allowed: false,
code: "approval_required",
};
}
return {
allowed: true,
constraints: {
maxOperations: 1,
},
};
}
모델은 정책 결정 이유를 조작할 수 없다. Executor가 인증된 actor, DB의 approval, resource metadata로 입력을 구성한다.
재구성한 권한 정책과 Executor 예제
문서 comment 추가 작업을 예로 든다. 실제 프로젝트 코드가 아닌 설명용 구현이다.
import { z } from "zod";
const appendCommentSchema = z.object({
documentId: z.string().min(1),
body: z.string().min(1).max(2_000),
expectedVersion: z.number().int().nonnegative(),
}).strict();
type AppendCommentCommand = z.infer<
typeof appendCommentSchema
>;
Task grant는 허용된 document와 횟수를 갖는다.
type TaskGrant = {
taskId: string;
delegatedBy: string;
audience: "document-executor";
allowedDocumentIds: string[];
allowedActions: Array<"read" | "append_comment">;
remainingWrites: number;
expiresAt: Date;
};
권한 검증은 사용자 membership과 task grant를 모두 확인한다.
async function authorizeAppendComment(input: {
actorId: string;
command: AppendCommentCommand;
grant: TaskGrant;
}) {
if (input.grant.expiresAt <= new Date()) {
return { allowed: false as const, reason: "grant_expired" };
}
if (input.grant.audience !== "document-executor") {
return { allowed: false as const, reason: "wrong_audience" };
}
if (!input.grant.allowedActions.includes("append_comment")) {
return { allowed: false as const, reason: "action_not_granted" };
}
if (
!input.grant.allowedDocumentIds.includes(
input.command.documentId,
)
) {
return { allowed: false as const, reason: "resource_not_granted" };
}
if (input.grant.remainingWrites < 1) {
return { allowed: false as const, reason: "write_limit_exceeded" };
}
const membership = await documentAcl.findMembership(
input.actorId,
input.command.documentId,
);
if (!membership?.canComment) {
return { allowed: false as const, reason: "actor_not_authorized" };
}
return { allowed: true as const };
}
실행 시 document version을 다시 확인하고 grant 사용량을 원자적으로 차감한다.
async function appendCommentWithGrant(input: {
operationId: string;
actorId: string;
rawCommand: unknown;
grantId: string;
}) {
const command = appendCommentSchema.parse(input.rawCommand);
return database.transaction(async (tx) => {
const grant = await tx.taskGrants.lockById(input.grantId);
const decision = await authorizeAppendComment({
actorId: input.actorId,
command,
grant,
});
if (!decision.allowed) {
return {
kind: "denied" as const,
reason: decision.reason,
};
}
const document = await tx.documents.lockById(
command.documentId,
);
if (document.version !== command.expectedVersion) {
return {
kind: "stale" as const,
reason: "document_changed_after_proposal",
};
}
const existing = await tx.comments.findByOperationId(
input.operationId,
);
if (existing) {
return {
kind: "already_applied" as const,
commentId: existing.id,
};
}
const comment = await tx.comments.create({
operationId: input.operationId,
documentId: command.documentId,
body: command.body,
createdBy: input.actorId,
});
await tx.taskGrants.decrementRemainingWrites(grant.id);
return {
kind: "succeeded" as const,
commentId: comment.id,
};
});
}
이 예제는 schema validation, 사용자 권한, task grant, 현재 version, write 횟수와 멱등 operation을 교차 검사한다.
Confused Deputy와 Token Passthrough를 막는다
Agent gateway가 사용자 token을 받아 downstream API에 그대로 전달하면 gateway가 원래 의도하지 않은 권한의 대리인이 될 수 있다.
sequenceDiagram
participant U as User
participant A as Agent Gateway
participant M as MCP/Tool Server
participant D as Downstream API
U->>A: token for Agent Gateway
A->>M: same token
M->>D: same token passthrough
Note over M,D: audience와 권한 경계가 흐려짐각 resource server용 token을 별도로 발급한다.
Token A:
audience = agent-gateway
Token B:
audience = document-tool
scope = document.comment
Token C:
audience = document-api
scope = comment.create
Tool server는 받은 token이 자신을 대상으로 발급됐는지 검사하고 downstream에는 자신의 별도 credential 또는 token exchange 결과를 사용한다.
function validateAudience(
claims: { aud: string | string[] },
expected: string,
): boolean {
const audiences = Array.isArray(claims.aud)
? claims.aud
: [claims.aud];
return audiences.includes(expected);
}
Audience 검사만으로 충분하지 않다. Issuer, signature, expiry, nonce, scope와 resource-level permission을 검증한다.
권한 상승과 Break Glass
에이전트가 더 많은 권한이 필요하다고 판단할 수 있다. 자동으로 scope를 확대하게 해서는 안 된다.
현재 grant:
- staging log read
모델 제안:
- production database read 필요
권한 상승은 새로운 요청이다.
stateDiagram-v2
[*] --> Limited
Limited --> EscalationRequested
EscalationRequested --> Denied
EscalationRequested --> TimeBoundGrant: approved
TimeBoundGrant --> Revoked: task complete
TimeBoundGrant --> Expired승인 화면에는 추가되는 범위와 이유, 만료, 데이터 노출을 보여 준다.
추가 요청 권한:
- production error log 읽기
범위:
- service=article-api
- time=최근 15분
- fields=timestamp, status, error_kind
- 최대 1,000건
제외:
- request body
- authorization header
- customer email
만료:
- 10분
Critical operation은 에이전트에 grant하지 않고 사람 운영자가 별도 Runbook으로 수행할 수 있다.
Break-glass credential은 평상시 agent runtime에 넣지 않는다. 별도 인증, 짧은 만료, 사유와 incident ID, 사후 review를 요구한다.
감사와 탐지 지표
최소 권한은 설정만으로 끝나지 않는다. 실제 거부와 사용을 관측한다.
agent_authorization_decisions_total{
tool,
action,
decision,
reason
}
agent_grants_issued_total{
scope,
risk_level
}
agent_grant_duration_seconds
agent_scope_escalations_total{decision}
agent_credential_refresh_total{reason}
agent_egress_denied_total{destination_class}
Resource ID와 user ID는 metric label에 넣지 않고 audit log에 둔다.
{
"event": "agent_authorization_denied",
"task_id": "task-example-42",
"actor_id": "user-example",
"agent_run_id": "run-example-8",
"tool": "append_document_comment",
"action": "append_comment",
"resource_type": "document",
"resource_id": "document-example-7",
"decision": "deny",
"reason": "grant_expired",
"policy_version": "agent-policy-4.2",
"occurred_at": "2026-04-28T11:02:00Z"
}
Audit에는 token 원문과 민감한 tool argument를 남기지 않는다.
다음 이상을 탐지한다.
- deny 비율이 prompt release 이후 급증
- 같은 task의 반복적인 scope escalation
- 사용하지 않은 broad grant가 자주 발급
- task 종료 후 credential 사용 시도
- 허용하지 않은 egress 목적지 반복
- 한 operation의 비정상적인 tool call 수
- 평소와 다른 production 접근 시간과 actor
거부가 많다고 정책이 나쁘다고 단정하지 않는다. 공격이나 모델 오류를 제대로 막고 있을 수 있다. 거부 이유와 사용자 결과를 함께 본다.
최소 권한을 테스트하는 방법
허용 경로만 테스트하면 정책이 지나치게 넓은지 알 수 없다. Negative test가 중요하다.
import { describe, expect, it } from "vitest";
describe("agent policy", () => {
it("production write를 거부한다", () => {
const decision = evaluatePolicy({
actorId: "user-example",
taskId: "task-example",
tool: "write_document",
action: "write",
resource: {
type: "document",
id: "document-example",
environment: "production",
},
approved: true,
now: new Date(),
});
expect(decision).toEqual({
allowed: false,
code: "production_access_not_available_to_this_agent",
});
});
});
테스트 행렬을 만든다.
| 조건 | 기대 |
|---|---|
| 허용 resource + 허용 action | 통과 |
| 다른 project ID | 거부 |
| 만료된 grant | 거부 |
| 잘못된 token audience | 거부 |
| task 취소 후 실행 | 거부 |
| 승인 뒤 resource version 변경 | stale |
| write 횟수 초과 | 거부 |
| redirect가 private IP로 이동 | network 거부 |
| symlink로 허용 root 탈출 | filesystem 거부 |
| external tool이 read-only 주장 | registry 미등록이면 거부 |
통합 테스트에서는 실제 sandbox와 credential broker를 사용한다.
1. 제한된 task grant 발급
2. 허용된 read 성공 확인
3. 같은 token으로 다른 resource 접근 실패 확인
4. network deny 확인
5. 만료 뒤 접근 실패 확인
6. audit event 생성 확인
7. task 종료 뒤 secret과 sandbox 제거 확인
권한 policy도 코드처럼 version control하고 변경 diff를 review한다.
policy diff:
- document.read: project-B
+ document.read: organization-wide
한 줄이지만 영향 범위가 크게 넓어진다. Policy test에서 새로 허용되는 subject-resource-action 조합을 보여 주면 review가 쉬워진다.
새 정책을 바로 강제하지 않고 기존 요청에 대해 새 policy의 allow/deny 차이를 기록해 예상하지 못한 권한 확대와 업무 차단을 찾을 수 있다.
마무리
AI 에이전트의 최소 권한은 tool 목록을 몇 개 숨기는 수준이 아니다. 사용자와 agent run, 작업 목적, resource, action, 환경, 시간, network와 횟수를 교차해 실제 실행 가능 범위를 만든다.
에이전트가 실수할 가능성을 0으로 만들려고 하기보다, 실수하더라도 이번 작업의 작은 capability 안에서만 영향을 만들도록 설계한다.
실무 적용 기준은 다음과 같다.
- 사람의 장기 권한을 agent runtime에 그대로 전달하지 않는다.
- Task별 subject와 만료되는 grant를 발급한다.
- Tool별 executor와 credential을 분리한다.
- Resource ID와 허용 action을 capability에 명시한다.
- Token audience, scope, expiry와 task binding을 검증한다.
- 파일은 canonical path와 OS sandbox로 제한한다.
- Network egress를 default deny하고 redirect와 DNS를 재검증한다.
- 읽기에도 field·row·bytes·시간 범위 제한을 둔다.
- 모델 prompt가 아니라 policy engine과 resource server가 권한을 강제한다.
- 권한 상승을 새 proposal과 승인으로 처리한다.
- Token passthrough를 피하고 resource별 credential을 사용한다.
- Negative test와 audit로 실제 피해 범위를 증명한다.
에이전트가 유용해질수록 더 많은 시스템에 연결하고 싶어진다. 이때 하나의 강력한 계정을 제공하는 대신 작업 단위의 작은 권한을 조합해야 기능을 늘리면서도 실패 범위를 통제할 수 있다.
참고 자료
- NIST CSRC - Least Privilege
- NIST SP 800-53 Rev. 5 - Security and Privacy Controls
- NIST SP 800-205 - Attribute Considerations for Access Control Systems
- Model Context Protocol - Authorization
- Model Context Protocol - Tools