
에이전트 워크플로 주입(CVE-2026-44246)에 대한 재현 가능한 취약 및 수정된 GitHub Actions 픽스처로, 측정된 탐지기 커버리지와 완화 지침을 제공합니다.
CVE-2026-44246 (nnU-Net, 에이전틱 워크플로 주입, CVSS 7.2, CWE-1427)으로 공개된 버그 클래스에 대한 재현 가능한 취약/수정 픽스처와, 이에 대한 세 가지 탐지기의 측정된 출력.
이 저장소가 존재하는 이유는 탐지기를 테스트할 대상이 아무것도 없었기 때문이다. 이 클래스에 대한 규칙을 작성한다는 것은 픽스처를 손으로 작성하고 그것이 충실하기를 바라는 것을 의미하며, 공개된 리비전들은 어떤 커밋을 봐야 하는지 알아야만 이용할 수 있었는데, 바로 그 부분이 흥미로운 부분으로 드러났다.
GitHub Actions 워크플로가 저장소의 자격 증명을 가진 AI 에이전트에게 신뢰할 수 없는 텍스트를 넘긴다.
네 가지 조건, 모두 필요하다:
issues, issue_comment, pull_request_review —
GitHub 계정을 가진 누구나 텍스트를 작성할 수 있는 이벤트.issues: write, contents: write 등.claude-code-action과
codex-action은 입력(, )이 명시적으로
옵트인하지 않는 한 쓰기 권한이 없는 실행 액터를 거부한다. 그 입력이 없으면
신뢰할 수 없는 작성자는 에이전트에 도달하지 못하며, 워크플로는 이 구성이 아니라
안전한 구성이 된다.allowed_non_write_usersallow-usersprompt: 내부의
${{ github.event.issue.body }}) 작업이 넘겨주는 토큰으로 런타임에 가져와지거나
(gh issue view).이것은 템플릿 주입이 아니다. 어떤 것도 셸로 평가되지 않는다. 페이로드는 산문이며,
인터프리터는 모델이다. 그래서 일반적인 조언 — 변수를 인용하고 eval하지 말라 — 이
적용되지 않으며, 아래의 수정이 입력 이스케이프가 아니라 에이전트가 무엇에 도달할 수
있는지에 관한 것인 이유이다.
nnU-Net은 이 워크플로를 두 개의 커밋으로 강화했으며, "이전"과 "이후"를 이진으로 취급하는 탐지기는 중간 것을 잘못 판단한다.
| 리비전 | 커밋 | 날짜 | 에이전트의 --allowedTools | 판정 |
|---|---|---|---|---|
| 취약 | 94300b49e716 | 2026-04-13 | gh issue comment, gh issue edit | 도달 가능한 쓰기 |
| "수정" | 4e4770b0b0e6 | 2026-04-24 | gh issue comment만; 라벨링은 래퍼 스크립트로 이동 | 여전히 도달 가능한 쓰기 |
| 이후 | 11bd8746fc06 | 2026-04-27 | 둘 다 아님; 이후 단계가 에이전트가 작성한 파일에서 게시 | 도달 불가 |
누구나 수정이라고 부를 커밋 — 그 메시지는 "hardened issue and PR agents" — 은
gh issue edit를 제거하고 라벨을 .github/scripts/safe-label.sh를 통해 라우팅했지만,
Bash(gh issue comment:*)를 에이전트에게 남겨두었다. 에이전트는 여전히 댓글을 달도록
조종될 수 있었고, 허용 목록 패턴 gh issue comment:*는 트리거한 이슈로 범위가
한정되지 않으므로 대상은 모델이 선택하는 것이었다. 세 번째 커밋만이 그것을
닫았다: 이제 에이전트는 /tmp/issue-comment.md를 작성하고, 이후의 비에이전트 단계가
이벤트에서 가져온 ISSUE: ${{ github.event.issue.number }}로 그것을 게시한다.
두 가지가 따라오며, 이것이 이 저장소가 단지 두 개의 파일이 아닌 이유이다:
"수정됨"은 버전이 아니라 리비전에 대한 주장이다. v2.4.1이 수정된 릴리스로
인용되지만, 그 .github/workflows/에는 codespell.yml만 들어 있다 — 에이전트
워크플로는 그 태그에 전혀 없다. 태그로는 수정을 검증할 수 없다; 커밋을 고정해야 한다.
권한 블록은 절대 변하지 않는다. issues: write는 세 리비전 모두에, 마지막 것을
포함하여 존재하고 올바르다, 왜냐하면 이후 단계가 그것을 필요로 하기 때문이다.
permissions만을 기준으로 삼는 탐지기는 리비전 1과 리비전 3을 구분할 수 없다.
그것들을 구분하는 것은 에이전트가 무엇을 호출할 수 있는가이다.
세 가지 탐지기, 두 픽스처와 세 개의 실제 리비전 모두에 대해 실행. 전체 원시 출력과 도구 버전은 results.md에 있다.
| 리비전 | agentbound 0.1.3 | sisakulint v0.3.7 | zizmor 1.30.1 |
|---|---|---|---|
1-vulnerable | HIGH write-scope, HIGH untrusted-content | ai-action-prompt-injection | — |
2-fix-commit | HIGH write-scope, CRITICAL author-association | — (일반만) | — |
3-later | LOW write-scope, CRITICAL author-association | ai-action-excessive-tools, ai-action-execution-order | — |
여기서 어떤 탐지기도 단순히 "틀린" 것이 아니다 — 그것들은 서로 다른 질문에 답한다:
ai-action-prompt-injection은 이슈 본문이
prompt:에 보간되는 취약 리비전에서 발동하고, 보간이 사라지면 멈춘다. 정확하다.
이후 리비전에 대한 그 발견들은 그것에 따라 행동하기 전에 읽어볼 가치가 있다 —
ai-action-excessive-tools는 Write를 지적하는데, 이 워크플로는 이후 단계가
게시하는 파일을 작성하기 위해 의도적으로 그것을 사용한다; 그리고
ai-action-execution-order는 에이전트를 마지막에 두기를 원하는데, 이는 이
설계가 정확히 피하는 것이다, 왜냐하면 권한 있는 단계들이 모델의 차례가 끝난 뒤에
오기 때문이다.permissions가 아니라 에이전트의 도구 허용 목록을
읽음으로써 리비전 2와 리비전 3을 구분하는 유일한 도구이다. 그
ci-agent-missing-author-association 발견은 이후 리비전에서 critical로
보고되며, 자체 README는 그것을 거짓이 아니라 과도하게 심각한 것으로 설명한다:
auto-triage는 누구나 도달할 수 있도록 의도된 것이다.여기 모든 도구는 작성자가 이미 그 정확한 조건에 대해 숙고한 리비전에 대해 보고하는 발견을 가지고 있다. 그것이 휴리스틱의 정상 상태이며, 커버리지 표가 통과/실패 열보다 더 유용한 이유이다.
git clone https://github.com/sushant-me/agentic-workflow-injection
cd agentic-workflow-injection
bash scripts/fetch-revisions.sh # 세 개의 실제 리비전, SHA로 고정
bash scripts/benchmark.sh # 설치된 모든 탐지기를 실행
benchmark.sh는 누락된 탐지기를 조용히 건너뛰는 대신 누락으로 보고한다, 왜냐하면
도구를 조용히 생략하는 커버리지 표는 그것에 대해 아무것도 증명하지 못하기 때문이다.
픽스처는 축소된 형태이며, 이 저장소를 위해 작성되었고 나머지와 마찬가지로 MIT
라이선스이다. 그것들은 복사본이 아니라 메커니즘에 충실하다:
fixtures/vulnerable.yml은 네 가지 조건과 그 외 아무것도 담고 있지 않으며,
fixtures/fixed.yml은 중요한 여섯 가지 변경이 각각 주석 처리된 같은 파일이다.
규칙을 추가한다면, 그 두 파일이 그것이 올바르게 처리해야 하는 가장 작은 입력이다.
두 가지 인터페이스 특이점, 스크립트에서 처리되지만 알아둘 가치가 있다:
not found를
출력하고 3으로 종료한다. 주시하지 않으면, 그것은 깨끗한 스캔과 동일하게 보인다.path는 basename이다, 따라서 같은 이름의 파일들에 대한
디렉터리 스캔은 모호하다.탐지는 쉬운 절반이다. **docs/mitigations.md**는 나머지 절반을 다룬다: 에이전트의 권한을 어떻게 제한할 것인가, 계층을 한 가지 질문으로 순위 매기며 — 이 통제는 모델이 따르기로 선택하는 것에 의존하는가?
그 순위 매김이 핵심이다. 프롬프트 내의 지시는 입력이지 가드 절이 아니다; 대상을 명명하는 도구 패턴, 또는 에이전트가 결코 보유하지 않는 토큰은 경계이다. 이 가이드는 "모델이 잘못된 일을 할 수 없다"에서 "모델에게 하지 말라고 요청했다"로 내려가며, 체크리스트와 nnU-Net의 진화를 실제 예시로 제시한다.
fixtures/fixed.yml은 여섯 가지 변경 각각에 그것이 구현하는 계층을 그리고 그것이
하중을 지탱하는지 여부를 태그한다 — 그중 둘은 그렇지 않으며, 독자가 그 파일을
과대평가하지 않도록 그렇게 표시되어 있다.
런타임에 커밋 SHA로 가져오며, 절대 벤더링하지 않는다:
| 파일 | 저장소 | 커밋 | 경로 |
|---|---|---|---|
real/1-vulnerable.yml | MIC-DKFZ/nnUNet | 94300b49e716 | .github/workflows/issue-triage.yml |
real/2-fix-commit.yml | MIC-DKFZ/nnUNet | 4e4770b0b0e6 | .github/workflows/issue-agent.yml |
real/3-later.yml | MIC-DKFZ/nnUNet | 11bd8746fc06 | .github/workflows/issue-agent.yml |
nnU-Net 구성의 어떤 사본도 여기에서 재배포되지 않는다; 가져오기를 다시 실행하고 위의 blob들과 바이트를 직접 비교하라.
이것은 탐지 엔지니어링 산출물이다. 익스플로잇을 포함하지 않으며, 여기의 어떤 것도
실제 워크플로에 대해 테스트되지 않았다 — 취약 리비전은 정적 파일이며, 이미
nnU-Net의 이력에 공개되어 있고 공개된 권고에 의해 참조된다. 픽스처는 do not deploy로
표시되어 있는데, 그것들은 복사되기 위해서가 아니라 탐지되기 위해 존재하기 때문이다.
이 클래스에 대한 탐지기를 유지 관리하며 표에 행을 추가하고 싶다면, 벤치마크
스크립트가 인터페이스이다: targets에 대해 도구를 실행하고 그 발견을 출력하는
섹션을 추가하면, 픽스처와 고정된 리비전이 나머지를 해준다.
MIT.