0Pricing
AI Agents · 강의

에이전트 작업에 대한 정책 적용

작업 전 정책 점검, 허용 목록과 차단 목록, 동적 정책 규칙을 다룹니다.

에이전트 작업에 대한 정책 적용은(는) CoddyKit의 무료 AI Agents 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Agents 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.

에이전트 정책 적용이란 무엇인가요

정책 적용은 모든 에이전트 작업 전에 실행되어 해당 작업이 허용되는지 결정하는 런타임 관문입니다. 이것이 없으면 에이전트의 유일한 제약은 LLM의 지시 준수 능력뿐이며, 이는 우회되거나 잘못 해석될 수 있습니다.

정책 적용은 LLM 외부의 인프라에서 이루어져야 합니다.

작업 전 확인 패턴

도구를 실행하기 전에 can_agent_do(action, context)를 호출합니다. 이 함수는 유일한 정책 적용 지점이며, 작업 실행으로 이어지는 모든 경로가 이 함수를 통과합니다.

def can_agent_do(action: str, context: dict) -> tuple[bool, str]:
    '''
    Returns (allowed: bool, reason: str).
    Context includes: user_id, agent_id, session_id, parameters, timestamp.
    '''
    # 1. Check denylist first (fast path for obvious violations)
    if action in DENIED_ACTIONS:
        return False, f'Action "{action}" is on the global denylist'

    # 2. Check allowlist
    if action not in ALLOWED_ACTIONS:
        return False, f'Action "{action}" is not on the allowlist'

    # 3. Context-specific checks
    return check_context_policy(action, context)

허용 목록과 거부 목록 정의하기

허용 목록은 에이전트가 수행할 수 있는 모든 작업을 열거합니다. 목록에 없는 것은 기본적으로 차단됩니다. 거부 목록은 상황과 관계없이 절대 허용해서는 안 되는 작업에 추가적인 안전망을 제공합니다.

# Allowlist: tools the agent can use
ALLOWED_ACTIONS = {
    'web_search',
    'read_file',
    'write_file',
    'send_email',
    'create_calendar_event',
    'query_database',
    'execute_python_sandbox',
    'fetch_url',
    'create_ticket'
}

# Denylist: actions that are always blocked, regardless of context
DENIED_ACTIONS = {
    'delete_all_records',
    'export_entire_database',
    'send_mass_email',
    'modify_system_config',
    'create_admin_user',
    'disable_audit_logging'
}

if __name__ == '__main__':
    for action in ('web_search', 'send_mass_email'):
        print(f"{action}: allowed={action in ALLOWED_ACTIONS} denied={action in DENIED_ACTIONS}")

상황별 정책 확인

단순한 허용 및 거부 목록을 넘어 정책은 상황에 따라 달라지는 경우가 많습니다. 사용자가 누구인지, 어떤 역할인지, 현재 시간이 언제인지, 대상 리소스가 무엇인지 등을 확인합니다. 이러한 것이 상황별 확인입니다.

from datetime import datetime, timezone

def check_context_policy(action: str, context: dict) -> tuple[bool, str]:
    user_id   = context.get('user_id', '')
    params    = context.get('parameters', {})
    user_role = context.get('user_role', 'user')

    # send_email: only agents with email_sender role
    if action == 'send_email':
        if user_role not in ('email_agent', 'admin'):
            return False, f'Role "{user_role}" cannot send emails'
        recipient = params.get('to', '')
        if not recipient.endswith('@trusted-domain.com'):
            return False, 'Email recipient must be in @trusted-domain.com'

    # write_file: path restrictions
    if action == 'write_file':
        path = params.get('path', '')
        if not path.startswith('/tmp/') and not path.startswith('/workspace/'):
            return False, f'File writes outside /tmp/ and /workspace/ are not allowed'

    return True, 'Permitted'

if __name__ == '__main__':
    ctx = {'user_id': 'u1', 'user_role': 'user',
           'parameters': {'to': 'someone@gmail.com'}}
    print('send_email as user:', check_context_policy('send_email', ctx))

    ctx2 = {'user_id': 'u1', 'user_role': 'user',
            'parameters': {'path': '/etc/passwd'}}
    print('write_file outside sandbox:', check_context_policy('write_file', ctx2))

정책 엔진을 통한 동적 정책

하드코딩된 정책은 실제 운영 환경에서 업데이트하기 어렵습니다. 코드가 아닌 데이터로 정의된 정책을 평가하려면 정책 엔진(OPA — 개방형 정책 에이전트)을 사용합니다. 에이전트를 다시 배포하지 않고도 정책을 업데이트할 수 있습니다.

import requests

OPA_URL = 'http://localhost:8181/v1/data/agent/allow'

def opa_policy_check(action: str, context: dict) -> tuple[bool, str]:
    payload = {
        'input': {
            'action':   action,
            'user_id':  context.get('user_id'),
            'role':     context.get('user_role', 'user'),
            'params':   context.get('parameters', {}),
            'time_utc': datetime.now(timezone.utc).isoformat()
        }
    }
    try:
        resp = requests.post(OPA_URL, json=payload, timeout=0.5)
        result = resp.json().get('result', {})
        allowed = result.get('allow', False)
        reason  = result.get('reason', 'Policy decision')
        return allowed, reason
    except Exception as e:
        # Fail closed: deny if policy engine is unreachable
        return False, f'Policy engine unavailable: {e}'

폐쇄형 실패와 개방형 실패

정책 엔진을 사용할 수 없을 때는 두 가지 선택지가 있습니다.

  • 폐쇄형 실패: 모든 작업을 거부합니다. 안전하지만 에이전트가 작동을 멈춥니다.
  • 개방형 실패: 모든 작업을 허용합니다. 에이전트는 계속 작동하지만 정책이 적용되지 않습니다.

보안에 민감한 에이전트에서는 항상 폐쇄형 실패를 사용해야 합니다. 위험도가 낮은 작업을 수행하는 생산성 에이전트에서는 개방형 실패가 허용될 수 있습니다.

FAIL_CLOSED = True  # Configure per agent

def safe_policy_check(action: str, context: dict) -> tuple[bool, str]:
    try:
        return can_agent_do(action, context)
    except Exception as e:
        if FAIL_CLOSED:
            return False, f'Policy check failed (fail-closed): {e}'
        else:
            # Log the failure but allow the action
            import logging
            logging.warning('Policy check error (fail-open): %s', e)
            return True, 'Policy check bypassed due to error (fail-open)'

작업 속도 제한

정책 적용에는 속도 제한이 포함될 수 있습니다. 예를 들어 에이전트가 이메일을 보낼 수는 있지만 세션당 5통으로 제한할 수 있습니다. 제한을 초과하면 작업이 거부됩니다.

from collections import defaultdict
import time

action_counts: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
action_window_start: dict[str, float] = defaultdict(float)

ACTION_RATE_LIMITS = {
    'send_email':   {'limit': 5,  'window_secs': 3600},  # 5/hour
    'write_file':   {'limit': 50, 'window_secs': 300},
    'web_search':   {'limit': 20, 'window_secs': 60}
}

def check_rate_limit(action: str, session_id: str) -> tuple[bool, str]:
    limit_config = ACTION_RATE_LIMITS.get(action)
    if not limit_config:
        return True, 'No rate limit defined'

    window = limit_config['window_secs']
    now = time.time()
    key = f'{session_id}:{action}'

    if now - action_window_start[key] > window:
        action_counts[key] = defaultdict(int)
        action_window_start[key] = now

    action_counts[key]['count'] += 1
    if action_counts[key]['count'] > limit_config['limit']:
        return False, f'Rate limit exceeded: {action} ({limit_config["limit"]}/{window}s)'
    return True, 'Within rate limit'

if __name__ == '__main__':
    for i in range(6):
        allowed, reason = check_rate_limit('send_email', 'session-1')
    print(f'After 6 send_email calls: allowed={allowed}, reason={reason}')

정책 위반 로깅

모든 정책 거부는 전체 상황 정보와 함께 기록해야 합니다. 예상치 못한 에이전트 동작을 디버깅하거나 보안 사고를 조사할 때 가장 먼저 확인해야 하는 곳이 이러한 로그입니다.

import logging, json, time

policy_logger = logging.getLogger('agent.policy')

def enforced_action(agent_id: str, user_id: str, action: str,
                    context: dict, audit_log) -> tuple[bool, str]:
    allowed, reason = safe_policy_check(action, context)

    log_entry = {
        'ts':       time.time(),
        'agent_id': agent_id,
        'user_id':  user_id,
        'action':   action,
        'allowed':  allowed,
        'reason':   reason,
        'params':   context.get('parameters', {})
    }

    if allowed:
        policy_logger.info('ALLOWED %s', json.dumps(log_entry))
    else:
        policy_logger.warning('DENIED %s', json.dumps(log_entry))

    audit_log.append(
        agent_id, user_id,
        f'POLICY_{"ALLOW" if allowed else "DENY"}',
        {'action': action},
        {'allowed': allowed, 'reason': reason},
        context.get('session_id', '')
    )
    return allowed, reason

리소스 범위 제한

허용된 작업이라도 에이전트가 액세스할 수 있는 리소스의 범위를 제한해야 합니다. 사용자 A의 문서를 처리하는 에이전트는 read_file이 허용 목록에 있더라도 사용자 B의 파일을 읽을 수 없어야 합니다.

def check_resource_scope(action: str, context: dict) -> tuple[bool, str]:
    user_id  = context.get('user_id', '')
    params   = context.get('parameters', {})

    if action == 'read_file':
        path = params.get('path', '')
        # Each user's files must be under their own namespace
        if not path.startswith(f'/workspace/{user_id}/'):
            return False, (
                f'User {user_id} cannot read files outside '
                f'/workspace/{user_id}/'
            )

    if action == 'query_database':
        table = params.get('table', '')
        allowed_tables = {'products', 'public_docs', f'user_{user_id}_data'}
        if table not in allowed_tables:
            return False, f'Table "{table}" not in scope for user {user_id}'

    return True, 'Resource scope check passed'

if __name__ == '__main__':
    ctx = {'user_id': 'u1', 'parameters': {'path': '/workspace/u2/secret.txt'}}
    print('Cross-user file read:', check_resource_scope('read_file', ctx))

    ctx2 = {'user_id': 'u1', 'parameters': {'path': '/workspace/u1/notes.txt'}}
    print('Own file read:      ', check_resource_scope('read_file', ctx2))

정책 규칙 테스트하기

정책 규칙은 코드이므로 테스트해야 합니다. 각 규칙에 대한 단위 테스트를 작성하여 거부와 허용이 올바르게 작동하고, 경계 사례가 실수로 정책을 우회하게 만들지 않는지 확인합니다.

def test_policy_rules():
    # Denylist blocks unconditionally
    ok, msg = can_agent_do('delete_all_records', {'user_role': 'admin'})
    assert not ok, 'Denylist should block even for admin'

    # Email requires trusted domain
    ok, msg = can_agent_do('send_email', {
        'user_role': 'email_agent',
        'parameters': {'to': 'attacker@evil.com'}
    })
    assert not ok, 'Should block untrusted email recipient'

    # File write outside allowed paths
    ok, msg = can_agent_do('write_file', {
        'user_role': 'user',
        'parameters': {'path': '/etc/crontab'}
    })
    assert not ok, 'Should block write to /etc/'

    print('All policy tests passed')

test_policy_rules()

정책 결정 캐싱하기

모든 작업마다 정책 엔진을 호출하면 지연 시간이 늘어납니다. 특히 외부 OPA 서비스를 사용할 때 그렇습니다. 왕복 통신을 줄이려면 (action, context_hash) 쌍에 대한 최근 결정을 짧은 TTL 동안 캐시합니다.

import hashlib, time

policy_cache: dict[str, dict] = {}
POLICY_CACHE_TTL = 10  # seconds — short TTL so policy updates take effect quickly

def cached_policy_check(action: str, context: dict) -> tuple[bool, str]:
    ctx_hash = hashlib.md5(
        f'{action}:{context.get("user_id")}:{context.get("user_role")}'
        .encode()
    ).hexdigest()
    key = f'{action}:{ctx_hash}'
    entry = policy_cache.get(key)
    if entry and time.time() - entry['ts'] < POLICY_CACHE_TTL:
        return entry['result']
    result = can_agent_do(action, context)
    policy_cache[key] = {'result': result, 'ts': time.time()}
    return result

정책 적용에서 ‘폐쇄형 실패’ 접근 방식이란 무엇인가요

폐쇄형 실패와 개방형 실패 중 무엇을 선택할지는 모든 정책 적용 시스템에서 근본적인 보안 절충 문제입니다. 각 접근 방식을 언제 사용해야 하는지 아는 것은 핵심 거버넌스 개념입니다.

정책 적용 요약

에이전트 정책 적용에는 단일 관문인 작업 전 확인, 기본 결정을 위한 허용 목록 + 거부 목록, 리소스 범위와 역할 확인을 위한 상황별 규칙, 업데이트 가능한 규칙을 위한 동적 정책 엔진(OPA), 작업 및 세션별 속도 제한, 보안이 중요한 에이전트를 위한 폐쇄형 실패 기본값이 사용됩니다.

자주 묻는 질문

“에이전트 작업에 대한 정책 적용” 강의는 무료인가요?

네 — “에이전트 작업에 대한 정책 적용” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Agents 강의 전체를 잠금 해제할 수 있습니다. AI Agents 강의에는 총 4개의 강의가 포함되어 있습니다.

“에이전트 작업에 대한 정책 적용”에서 뭘 배우나요?

작업 전 정책 점검, 허용 목록과 차단 목록, 동적 정책 규칙을 다룹니다. 브라우저에서 직접 실행하는 실습 코드로 AI Agents을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Agents을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Agents은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“에이전트 작업에 대한 정책 적용” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Agents 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Agents 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 에이전트를 위한 변경 불가 작업 기록
  2. 에이전트 작업에 대한 정책 적용
  3. 규제 준수: GDPR 및 SOC2
  4. 사람 참여형 승인 관문
← AI Agents(으)로 돌아가기