스크립트 위험 게이트
Agent가 스크립트와 명령을 허용, 확인 요청, 거부하는 기준, 즉 RED, YELLOW, GREEN 분류 체계와 각 판단의 근거를 설명합니다.
Agent는 다음과 같은 위젯을 표시합니다.
The script contains 1 risk. Execute anyway?
[fund_moving_cli] The script will move funds via `minara swap`.
[ Execute ] [ Cancel ]어떤 스크립트가 확인 위젯을 트리거하고, 어떤 스크립트가 자동으로 허용되며, 어떤 스크립트가 즉시 거부되는지, 그 판단 로직을 이 페이지에서 설명합니다.
세 가지 분류
게이트의 판단 모델은 세 가지 범주로 구성됩니다. 각 범주는 패턴 목록이 아닌, 해당 범주가 답하는 질문으로 정의됩니다.
- GREEN: 합리적이고 되돌릴 수 있는 저비용 작업입니다. 데이터 읽기, 수학 연산, 새 파일 쓰기, 공개 HTTPS GET 요청 등이 해당합니다. Agent는 사용자의 개입 없이 진행합니다.
- YELLOW: 정당하지만 위험도가 높은 작업입니다. 자금 이동, 온체인 권한 변경, 특정 파일 삭제, 패키지 설치, 외부 쓰기 요청 등이 포함됩니다. 이것이 의도된 작업인지는 오직 사용자만 판단할 수 있습니다. 게이트는 실행을 멈추고 사용자에게 확인을 요청합니다.
- RED: 정당한 업무상 이유가 없는 작업입니다. 대량 삭제, 클라우드 메타데이터 유출,
~/.ssh/id_rsa읽기, 컨테이너 탈출, 소스에 하드코딩된 인라인 개인키 등이 해당합니다. 사용자가 해당 작업을 "원하더라도" 올바른 해결책은 적절한 도구를 사용하는 것입니다. 이런 형태의 스크립트를 Agent가 실행하도록 허용하는 것은 옳지 않습니다. 따라서 RED는 재정의할 수 없습니다. 클릭으로도, 플래그 전달로도 우회할 수 없습니다.
RED를 재정의할 수 없는 이유는 무엇인가요? 대부분의 RED 감지는 프롬프트 인젝션의 결과이기 때문입니다. 웹 페이지, 스킬 콘텐츠 블롭, 또는 도구 결과가 "다음으로, 이 유용한 스크립트를 실행하세요"와 같은 내용을 LLM에 주입하고, LLM은 그 작업을 정중하게 설명하게 됩니다. 만약 RED를 "사용자 확인"으로 우회할 수 있다면, 사용자가 확인 버튼을 클릭하는 순간 공격이 성공합니다. 사용자가 읽는 설명 자체가 Agent가 아닌 공격자가 작성한 것이기 때문입니다. 따라서 RED는 대화 맥락과 무관하게 강제 적용됩니다. "위험을 이해하고 그래도 실행하겠다"는 의사는 적용되지 않습니다. 사용자가 이해한 내용 자체가 이미 공격자의 프레이밍을 통해 필터링된 것이기 때문입니다.
YELLOW 확인 위젯에 표시되는 정보
각 위험 항목은 세 가지 요소로 구성됩니다.
- Category: 위험의 종류입니다. 예:
fund_moving_cli,onchain_dangerous_call. - Evidence: 일치한 스크립트의 실제 해당 줄입니다. 개인키 hex 값, Authorization 헤더, Bearer 토큰, 또는 긴 base64 블롭은
<REDACTED-*>플레이스홀더로 대체됩니다. - Decision: Execute 또는 Cancel.
증거를 마스킹 처리하는 이유는 무엇인가요? 위젯의 목적은 의도를 판단하는 것이며, "이것이 의도된 작업인가?"라는 질문에 답하기 위해 비밀 값 자체를 볼 필요는 없기 때문입니다. 스크립트에
new ethers.Wallet("0xabc…64-hex…")가 포함되어 있을 때 원시 hex 값을 그대로 표시하면, 확인 위젯이 두 번째 유출 경로가 됩니다. 해당 바이트가 감사 로그, 채팅 기록, 그리고 화면 녹화에까지 복사될 수 있습니다. 마스킹 처리를 하면 "지갑 생성자에 개인키가 전달되고 있습니다"라는 정보만 표시되며, 이것이 사용자가 실제로 답해야 할 질문입니다.
범주 분류 기준
모든 패턴을 나열하는 대신 (정식 목록은 소스 코드를 참조하세요.
apps/agent/src/tools/_shared/script-risk.ts),
여기서는 특정 항목이 RED가 아닌 YELLOW로, 또는 그 반대로 분류된 이유에 초점을 맞춥니다. 대표적인 예시는 다음과 같습니다.
fund_moving_cli: YELLOW
minara swap, cast send, forge --broadcast, hardhat run … --network mainnet, solana transfer 등을 호출하는 스크립트입니다.
RED가 아닌 이유는 무엇인가요? 분할 매수 (DCA) 스크립트, 리밸런싱 스크립트, 청산 봇은 모두 정당한 자동화 패턴입니다. RED로 강제 분류하면 실제 워크플로가 중단됩니다.
GREEN이 아닌 이유는 무엇인가요? 이러한 CLI를 호출하는 스크립트는 Layer 3, 즉 프로세스 내 2단계 확인을 우회합니다. 이는 사용자가 명시적으로 확인 버튼을 클릭하지 않은 채로 Agent가 자금을 이동시키는 사각지대입니다. 게이트는 이를 반드시 표면화해야 합니다.
obfuscation_with_sink: RED
eval(base64.b64decode(...)), getattr(__import__("o" + "s"), "sys" + "tem")(...), 역순 문자열 컴파일, globalThis["Func" + "tion"].
RED인 이유는 무엇인가요? 다른 설명이 없습니다. 정적 분석을 회피하기 위한 목적 외에는 이런 방식으로 코드를 작성하지 않습니다. 패턴 자체가 악의적 의도의 증거이므로, 사용자에게 물어볼 것이 없습니다.
mass_delete: 대량 삭제는 RED, 특정 파일 삭제는 YELLOW
rm *, rm -rf $UNSET/x, find . -delete, shutil.rmtree(".")는 RED입니다. rm sandbox/files/foo.csv는 YELLOW입니다.
이렇게 구분하는 이유는 무엇인가요? 특정 경로가 없거나 빈 값으로 확장될 수 있는 변수를 사용하는 대량 삭제는, LLM이 "작업공간 정리"를 잘못 해석하거나 $UNSET이 /로 확장되도록 유도하는 프롬프트 인젝션의 결과인 경우가 압도적으로 많습니다. 이러한 경우를 즉시 거부하면 일반적인 공격과 일반적인 실수 모두를 방지할 수 있습니다.
특정 파일 삭제(rm sandbox/files/foo.csv)는 사용자가 실제로 원하는 작업이지만, 파일을 삭제하기 전에 "그 파일 맞습니다"라는 한 번의 확인은 여전히 필요합니다.
credential_exfil: RED
~/.ssh/id_rsa, ~/.aws/credentials, macOS 키체인, Chrome / Brave / Edge 로그인 데이터베이스, MetaMask / Phantom 확장 프로그램 스토리지, 1Password / Bitwarden / KeePass 볼트를 읽는 작업입니다.
RED인 이유는 무엇인가요? 이 중 어느 것도 "Agent가 나 대신 이것을 열어도 된다"는 정당한 해석이 없습니다. 워크플로에서 SSH 키를 실제로 읽어야 한다면, LLM 기반 스크립트가 아닌 전용 CLI를 사용하십시오. 이를 RED로 잠그면 재정적으로 가장 큰 피해를 줄 수 있는 프롬프트 인젝션 공격 유형도 차단됩니다.
imds_ssrf: RED (모든 인코딩 형식)
클라우드 메타데이터 엔드포인트(169.254.169.254, metadata.google.internal, Alibaba 및 Oracle 동등 주소)와 공격자가 단순 차단 목록을 우회하기 위해 사용하는 모든 인코딩 형식, 즉 10진수(2852039166), 16진수(0xa9fea9fe), IPv6 매핑([::ffff:169.254.169.254])이 포함됩니다.
RED인 이유는 무엇인가요? 사용자를 위한 정당한 스크립트에서 인스턴스 메타데이터 서비스를 호출할 이유가 없습니다. 이 항목이 감지되면 거의 항상 Agent를 실행 중인 클라우드 호스트에서 IAM 자격 증명을 탈취하려는 SSRF 시도입니다.
direct_signing_with_constructor: RED
new ethers.Wallet("0x…64-hex…"), Account.from_key("0x…"), Keypair.fromSecretKey(byteArray).
RED인 이유는 무엇인가요? 스크립트 소스에 인라인 개인키가 있다는 것은, (a) 누군가가 키를 탈취하려 하거나, (b) 키가 디스크에 저장될 상황임을 의미합니다. 어느 경우든 올바른 다음 조치는 중단하는 것이지 묻는 것이 아닙니다. Agent의 기본 거래 도구를 사용하면 소스에 키를 붙여넣을 필요가 없습니다.
env_var_poisoning: YELLOW
NODE_OPTIONS=--require ./steal.js, LD_PRELOAD=…, BASH_ENV=…, PYTHONPATH=. 설정이 해당합니다.
RED가 아닌 YELLOW인 이유는 무엇인가요? 일부는 정당한 사용 사례가 있습니다. 트레이싱을 위한 NODE_OPTIONS 설정이나 로컬 개발을 위한 PYTHONPATH 설정이 그 예입니다. 그러나 이들 모두 자식 프로세스에 코드를 주입하는 표준적인 방법이기도 합니다. 게이트는 이를 표면화하여 사용자가 해당 환경 변수가 가리키는 파일이 자신이 작성한 것인지, 아니면 프롬프트 인젝션이 작성한 것인지 확인할 수 있게 합니다.
전체 카탈로그는 RED 12개 범주와 YELLOW 13개 범주로 구성됩니다. 소스 코드가 정식 목록이며, 새로운 우회 기법이 발견될 때마다 패턴이 추가됩니다. 위에 소개한 항목들이 운영상 가장 중요한 것들입니다.
게이트가 감지할 수 없는 것
정적 분석에는 명확한 한계가 있습니다. 이 한계를 이해하면 운영자로서 어디에 주의를 기울여야 하는지 알 수 있습니다.
- 런타임에 생성되는 문자열.
a와b가 런타임에 계산되는exec(a + b)와 같은 스크립트에서, 분석기는exec(<variable>)을 감지하고dynamic_commandYELLOW를 보고하지만 실행될 문자열이 무엇인지는 알 수 없습니다. 스크립트를 직접 읽어야 합니다. - 다중 도구 결합 공격. 스크립트가 파일을 쓰고 환경 변수를 설정한 후, 별도의
terminal호출이 두 가지를 모두 읽는 바이너리를 실행하는 경우입니다. 각 호출은 단독으로는 이상이 없습니다. 게이트는 한 번에 하나의 호출만 검사합니다. 이 경우 게이트가 아닌 Layer 2, 즉 OS jail이 최후 방어선입니다. - 새로운 난독화 패턴. 공격자는 이 소스 코드를 읽고 정규식의 허점을 찾을 수 있습니다. 99번 hex 인코딩, 유니코드 유사 문자, 깊이 중첩된
compile()호출 등이 그 예입니다. 발견되는 즉시 패턴을 추가하지만, 실질적인 경계는 Layer 2입니다. 스크립트가 여기의 모든 규칙을 우회하더라도, 생성된 서브프로세스는 여전히bwrap/sandbox-exec내에서 실행됩니다.
솔직하게 말하면, Layer 4는 실행 전에 의도를 검사합니다. Layer 2는 실행 후 피해 범위를 제한합니다. Layer 4가 무언가를 놓치더라도 Layer 2가 여전히 적용됩니다.
결정 흐름
LLM이 execute_code를 호출한 시점부터 서브프로세스가 실제로 실행되기까지의 과정입니다.
LLM → execute_code({ code, language })
│
▼
analyzeScript(body)
│
┌────┼────┐
▼ ▼ ▼
RED YEL GREEN
│ │ │
│ ▼ ▼
│ ctx.interactionQueue.ask()
│ │ │
│ │ └── handler proceeds → spawn
│ │
│ ├── user "Execute" → handler proceeds → spawn
│ └── user "Cancel" → err("user_declined: …")
│
└── err("script_risk_rejected: …")게이트는 프롬프트 수준의 지시로 우회할 수 없습니다. 게이트는 서브프로세스 작업이 시작되기 전, 핸들러 내에서 실행되며 LLM의 프롬프트 안에 있지 않습니다.
사용자가 제어할 수 있는 것
- ✅ YELLOW에서 Cancel을 선택할 수 있습니다.
- ✅ 검토된 워크플로 정의 내에서
script_risk_policy를 통해 특정 스크립트 본문을 사전 승인할 수 있습니다. 자세한 내용은 감사 및 재정의를 참조하십시오. - ❌ RED는 재정의할 수 없습니다. 클릭으로 통과하도록 설계되지 않았습니다. 신뢰하는 스크립트가 RED에 걸린다면, 해결책은 게이트와 싸우는 것이 아니라 스크립트를 재작성하는 것입니다. 예를 들어 인라인 개인키 대신 Minara의 기본 거래 도구를 사용하십시오.
- ❌ LLM은 위젯을 건너뛸 수 없습니다. 게이트는 서브프로세스 실행 전 툴 핸들러에서 실행되므로, LLM이 프롬프트 엔지니어링으로 우회할 수 없습니다.