모델 출력의 Node.js 셸 실행

모델 출력의 Node.js 셸 실행

설명

LLM 출력은 모델 정렬이나 프롬프트 지침과 관계없이 신뢰할 수 없는 하위 시스템 입력으로 취급해야 합니다. 프롬프트 인젝션뿐 아니라 일반적인 사용자 요청, 검색된 콘텐츠, 모델 오류도 예상하지 못한 명령 문자열을 만들 수 있습니다. 이 출력을 시스템 셸에 전달하면 애플리케이션이 모델에 간접적인 명령 실행 권한을 부여하게 됩니다.

실제 위험도는 입력 출처, 프로세스 권한, 실행 환경, 별도의 권한 부여와 확인 절차에 따라 달라집니다. 모델 출력을 데이터로 검증하는 것과 그 데이터가 지시하는 작업을 허가하는 것은 별개입니다.

잠재적 영향

  • 모델이 생성한 문자열이 exec 또는 execSync에 전달되면 셸 메타문자와 추가 인자가 해석되어 명령 인젝션으로 이어질 수 있습니다.
  • 공격자는 애플리케이션 프로세스 권한으로 임의 명령을 실행하거나 다른 실행 파일과 스크립트를 호출할 수 있습니다.
  • 실행 프로세스가 파일, 자격 증명, 데이터베이스 또는 배포 권한을 보유하면 정보 유출, 데이터 변경, 서비스 중단 등으로 영향이 확대될 수 있습니다.

해결 방법

  1. 모델이 반환한 자유 형식 명령을 실행하지 마세요. 운영체제 명령보다 프로세스 내부 라이브러리나 애플리케이션이 정의한 고정된 작업 매핑을 우선하세요.
  2. 구조화된 출력도 신뢰하지 마세요. JSON 스키마나 형식 검증은 데이터 형태만 확인하므로, 허용된 작업·인자·대상과 사용자의 권한을 결정론적인 애플리케이션 코드에서 별도로 검증하세요.
  3. 운영체제 기능이 필요하면 우선 동일한 기능의 라이브러리 API를 사용하세요. 외부 프로세스가 불가피한 경우 실행 파일을 코드에 고정하고, 인자를 개별 검증한 뒤 execFile 또는 spawn에 배열로 전달하며 셸을 비활성화하세요. 명령 문자열 전체를 이스케이프하거나 이름만 sanitize인 도우미에 의존하지 마세요.
  4. 권한이 필요하거나 되돌릴 수 없거나 외부에 영향을 주는 작업에는 정확한 작업 내용을 보여 주는 확인 절차를 적용하고, 모델 연동 프로세스에는 최소 권한만 부여하세요.
  5. 모델, 프롬프트, 도구 또는 출력 스키마를 변경한 뒤에는 직접·간접 프롬프트 인젝션과 잘못된 형식의 출력 경로를 다시 테스트하세요.

예시

변경 전

javascript
import OpenAI from "openai";
import { exec } from "node:child_process";

const client = new OpenAI();

async function runGeneratedCommand(userRequest) {
  const response = await client.responses.create({
    model: "gpt-5.5",
    input: `Write a shell command for this request: ${userRequest}`
  });

  exec(response.output_text);
}

변경 후

javascript
import OpenAI from "openai";

const client = new OpenAI();
const ALLOWED_TARGETS = new Set(["api-staging", "worker-staging"]);
const OPERATIONS = Object.freeze({
  status: target => getDeploymentStatus(target)
});

function parseAction(text) {
  const value = JSON.parse(text);
  if (
    !value ||
    typeof value !== "object" ||
    typeof value.operation !== "string" ||
    typeof value.target !== "string" ||
    !Object.hasOwn(OPERATIONS, value.operation) ||
    !ALLOWED_TARGETS.has(value.target)
  ) {
    throw new Error("Unsupported model action");
  }
  return value;
}

async function runAllowedAction(userRequest) {
  const response = await client.responses.create({
    model: "gpt-5.5",
    input: `Choose a read-only operation for: ${userRequest}`
  });

  const action = parseAction(response.output_text);
  return OPERATIONS[action.operation](action.target);
}

설명:

  • 변경 전: 모델이 반환한 문자열을 exec(...)에 직접 전달하므로 셸이 메타문자와 추가 명령을 해석합니다.
  • 변경 후: 애플리케이션이 정의한 읽기 전용 작업과 대상만 선택할 수 있으며, 모델 출력 자체는 실행하지 않습니다. getDeploymentStatus와 오류 처리는 생략했습니다. 실제 서비스에서는 이 검증과 별도로 현재 사용자가 해당 대상에 접근할 권한이 있는지도 확인해야 합니다.

다른 출력 문맥

모델 출력이 HTML·Markdown, SQL, 파일 경로나 터미널로 전달되는 경우에도 그 문맥에 맞는 검증이 필요합니다. HTML은 텍스트 출력과 문맥별 인코딩을 우선하고, 서식이 꼭 필요할 때는 DOMPurify 같은 HTML 전용 정화 도구를 사용하세요. 네이티브 Element.setHTML()은 지원 런타임을 확인한 경우에만 사용하고, setHTMLUnsafe()를 일반적인 해결책으로 삼지 마세요.

참조