AI 에이전트도 공급망을 가지고 있습니다: Docker 강화 이미지를 통한 Node.js 앱 보안 강화
요약
AI 에이전트 기반의 Node.js 애플리케이션은 코드베이스가 작더라도 MCP 서버, API 키, 브라우저 자동화 도구 등 확장된 의존성으로 인해 보안 표면이 매우 넓습니다. 따라서 단순한 모델 호출을 넘어 Docker 베이스 이미지, SBOM, 취약점 스캐닝 등 소프트웨어 공급망 보안을 아키텍처의 핵심 요소로 다루어야 합니다.
핵심 포인트
- AI 에이전트는 MCP 서버, 브라우저 자동화, 자격 증명 등 전통적인 앱보다 훨씬 넓은 의존성 그래프를 가짐
- AI 애플리케이션의 컨테이너는 단순 HTTP 처리를 넘어 데이터, 도구, 실행 경로에 직접 접근하므로 보안 위험이 높음
- Docker 이미지의 베이스 이미지, 의존성 설치, 런타임 사용자 설정 등은 AI 앱 아키텍처의 필수 보안 요소임
- 공급망 공격(Supply Chain Attack)을 통해 침해된 이미지나 패키지가 민감한 데이터로 향하는 경로가 될 수 있음
서론
AI 애플리케이션은 외부에서 보기에 종종 작아 보입니다. Node.js 서비스가 모델을 호출하고, 몇 가지 도구에 연결하며, 상태를 저장하고 응답을 반환하는 식입니다. 코드베이스는 전통적인 엔터프라이즈 애플리케이션보다 훨씬 작을 수도 있습니다. 하지만 보안 표면(Security surface)은 결코 작지 않습니다. 현대적인 Node.js AI 앱은 모델 제공자 API, MCP 서버, 브라우저 자동화, Redis 또는 Postgres, 프라이빗 npm 패키지, GitHub 토큰, 내부 API 및 로컬 파일을 사용할 수 있습니다. 에이전트는 저장소 코드를 읽거나, 브라우저를 열거나, 로그를 검사하거나, 고객 데이터를 요약하거나, 실제 동작을 수행하는 도구를 호출할 수 있습니다. 이는 앱을 실행하는 컨테이너가 단순히 HTTP 트래픽만 처리하는 것이 아님을 의미합니다. 컨테이너는 자격 증명(Credentials), 도구, 데이터 및 실행 경로 근처에 위치하게 됩니다. 이것이 바로 Docker 이미지가 중요한 이유입니다. 베이스 이미지(Base image), 의존성 설치 프로세스, 런타임 사용자, 파일 시스템 권한, SBOM(Software Bill of Materials), 취약점 스캐닝(Vulnerability scanning) 및 비밀 정보(Secret) 처리는 모두 AI 애플리케이션 아키텍처의 일부입니다. 많은 AI 튜토리얼은 이 계층을 건너뜁니다. 그들은 모델을 호출하거나, 에이전트 루프를 구축하거나, 도구를 연결하는 방법만을 보여줍니다. 프로덕션 환경에서의 질문은 다릅니다. 우리가 정확히 무엇을 배포하고 있는지, 그것이 어디에서 왔는지, 무엇에 접근할 수 있는지, 그리고 침해되었을 때 얼마나 많은 피해를 줄 수 있는지에 대한 질문입니다.
왜 AI 앱이 공급망 리스크를 증가시키는가
전통적인 Node.js 애플리케이션도 이미 공급망 리스크를 가지고 있습니다. 이들은 npm 패키지, 운영 체제 패키지, 베이스 이미지, CI 파이프라인 및 배포 설정에 의존합니다. AI 애플리케이션은 여기에 더 많은 가동 요소들을 추가합니다. AI 에이전트는 도구 어댑터로 MCP 서버를 사용할 수 있습니다. 각 MCP 서버는 자체적인 의존성, 권한 및 자격 증명을 가집니다. 로컬 LLM 워크플로우는 레지스트리에서 모델 아티팩트(Artifacts)를 가져올 수 있습니다. 브라우저 자동화 도구는 대규모 시스템 의존성을 가져올 수 있습니다. 코드 리뷰 에이전트는 GitHub 접근 권한이 필요할 수 있습니다. 지원 어시스턴트는 고객과 유사한 데이터에 접근할 수 있습니다. 테스트 생성 에이전트는 파일을 읽고 쓸 수 있습니다. 애플리케이션은 여전히 "단순한 Node.js 서비스"일 수 있지만, 의존성 그래프(Dependency graph)는 보이는 것보다 훨씬 더 넓습니다.
Trivy와 KICS에 관한 Docker의 2026년 공급망 보고서는 이러한 위험을 상기시켜 주는 유용한 자료입니다. Docker는 탈취된 게시자 자격 증명(Publisher credentials)이 정당한 게시 흐름을 통해 악성 이미지를 푸시하는 데 사용된 두 가지 사례를 설명했습니다. Docker는 자사의 인프라가 침해되지는 않았으나, 침해된 태그를 풀(pull)한 모든 사용자가 소프트웨어 공급망을 통해 일시적으로 노출되었다고 밝혔습니다. 이 이야기는 AI 앱에 있어 매우 중요한데, 에이전트(Agents)는 종종 자신이 직접 구축하지 않은 도구들에 의존하기 때문입니다. 침해된 이미지, 패키지, MCP 서버 또는 빌드 단계는 자격 증명, 소스 코드, 클라우드 시스템 또는 민감한 데이터로 향하는 경로가 될 수 있습니다.
위험한 Dockerfile
Node.js AI 앱을 위한 일반적인 Dockerfile은 다음과 같을 수 있습니다:
FROM node:latest
WORKDIR /app
COPY . .
RUN npm install
ENV OPENAI_API_KEY=sk-example
ENV GITHUB_TOKEN=ghp-example
EXPOSE 3000
CMD ["npm", "start"]
이 Dockerfile에는 여러 가지 문제가 있습니다. 우선 node:latest를 사용하는데, 이는 시간이 지남에 따라 변경될 수 있어 빌드 예측 가능성을 떨어뜨립니다. 로컬 디렉토리 전체를 이미지로 복사하는데, 이 과정에서 .env, .npmrc, 테스트 아티팩트(Test artifacts) 또는 로컬 파일이 실수로 포함될 수 있습니다. 또한 락파일(Lockfile) 기반 설치 대신 npm install을 사용합니다. 환경 변수를 통해 이미지 안에 비밀 정보(Secrets)를 구워 넣고(Bakes) 있습니다. 기본 사용자(Default user)로 실행되며, 빌드 의존성(Build dependencies)과 런타임 의존성(Runtime dependencies)을 분리하지 않습니다. 데모용으로는 작동할 수 있겠지만, 도구와 자격 증명에 접근 권한이 있는 AI 앱에게는 너무 허술합니다.
더 안전한 Dockerfile 패턴
더 나은 패턴은 특정 베이스 이미지 (Base Image), 멀티 스테이지 빌드 (Multi-stage build), 프로덕션 전용 의존성 (Production-only dependencies), 비루트 사용자 (Non-root user)를 사용하며, 내장된 비밀 정보 (Secrets)를 포함하지 않습니다:
syntax=docker/dockerfile:1.7
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM node:22-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit = dev && npm cache clean --force
COPY --from=build /app/dist ./dist
RUN groupadd --system appgroup
&& useradd --system --gid appgroup --home /app appuser
&& chown -R appuser:appgroup /app
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
이 방식은 이미 훨씬 더 안전합니다.
- latest 대신 Node 메이저 버전을 고정 (Pinning)합니다.
- 재현 가능한 의존성 설치를 위해 npm ci를 사용합니다.
- 빌드 도구 (Build tooling)를 런타임 스테이지 (Runtime stage)에서 제외합니다.
- 최종 이미지에는 프로덕션 의존성 (Production dependencies)만 설치합니다.
- 비루트 사용자 (Non-root user)로 실행합니다.
이것이 완벽한 보안은 아니지만, 더 나은 토대입니다.
Docker 강화 이미지 (Docker Hardened Images)의 역할
Docker 강화 이미지 (Docker Hardened Images)는 이 문제의 베이스 이미지 (Base-image) 부분을 한 단계 더 발전시킵니다. Docker는 Docker 강화 이미지를 안전하고, 최소화되었으며, 프로덕션 준비가 된 이미지로 설명하며, 2025년 12월 Docker는 이 이미지들이 Apache 2.0 라이선스 하에 무료 및 오픈 소스로 공개되었음을 발표했습니다. 또한 Docker는 카탈로그 내의 1,000개 이상의 이미지와 Helm 차트 (Helm charts)를 강화했다고 밝혔습니다. 핵심 아이디어는 베이스 이미지가 사후 고려 사항이 되어서는 안 된다는 것입니다. 강화된 이미지는 불필요한 패키지를 줄이고, 공격 표면 (Attack surface)을 좁히며, 팀들에게 범용 이미지 (General-purpose image)보다 더 강력한 시작점을 제공합니다. Dockerfile 패턴은 거의 동일하게 유지됩니다.
베이스 이미지는 레지스트리 및 조직에서 사용할 수 있는 보안 강화(hardened) 대응 이미지로 변경됩니다:
FROM <your-hardened-node-image>@sha256:<digest> AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
FROM <your-hardened-node-image>@sha256:<digest> AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit = dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER 10001
EXPOSE 3000
CMD ["node", "dist/index.js"]
정확한 이미지 이름은 팀이 Docker 강화 이미지 (Docker Hardened Images)에 접근하는 방식과 레지스트리 설정 방식에 따라 달라집니다. 중요한 습관은 신뢰할 수 있는 베이스 이미지 (base image)를 사용하고, latest 태그를 피하며, 더 강력한 재현성 (reproducibility)이 필요할 때는 다이제스트 (digest)를 통해 이미지를 고정 (pin)하는 것입니다. Docker의 강화 이미지 (Hardened Images) 제품 페이지에서도 즉시 교체 가능한 대체 (drop-in replacements), 지속적인 업데이트, 안전한 커스텀 (secure customization), 그리고 출처를 보존하는 워크플로우 (provenance-preserving workflows)를 강조합니다.
이미지에 비밀 정보 (Secrets)를 포함하지 마세요
AI 앱은 보통 비밀 정보 (secrets)가 필요하지만, 이미지 안에 이를 포함해서는 안 됩니다. 모델 제공자 키 (Model provider keys), GitHub 토큰 (GitHub tokens), MCP 자격 증명 (MCP credentials), 데이터베이스 비밀번호 (database passwords), 그리고 OAuth 토큰 (OAuth tokens)은 배포 환경이나 비밀 관리자 (secret manager)를 통해 런타임 (runtime) 시점에 제공되어야 합니다. 이들은 Dockerfile에 나타나서는 안 됩니다.
services :
agent :
image : node-ai-agent:secure
environment :
NODE_ENV : production
OPENAI_API_KEY : ${OPENAI_API_KEY}
GITHUB_TOKEN : ${GITHUB_TOKEN}
DATABASE_URL : ${DATABASE_URL}
이는 값이 커밋되지 않은 로컬 .env 파일에서 오는 경우 로컬 개발 환경에서는 허용될 수 있습니다. 프로덕션 (production) 환경에서는 Kubernetes Secrets, AWS Secrets Manager, Google Secret Manager, Azure Key Vault, HashiCorp Vault 또는 플랫폼의 내장 비밀 저장소 (built-in secret store)와 같은 관리형 비밀 시스템 (managed secret system)을 권장합니다.
빌드 시점의 비밀 정보 (Build-time secrets)는 다릅니다. 만약 이미지 빌드 과정에서 프라이빗 npm 패키지 (private npm package)나 프라이빗 Git 저장소 (private Git repository)에 대한 임시 접근이 필요하다면, ARG나 ENV 대신 Docker 빌드 비밀 (Docker Build Secrets) 또는 SSH 마운트 (SSH mounts)를 사용하세요.
Docker의 빌드 비밀 (build secrets) 문서를 살펴보면, 비밀 마운트 (secret mounts)와 SSH 마운트 (SSH mounts)는 빌드 중에 필요한 민감한 데이터를 위해 설계되었으며, 이 프로세스는 두 단계로 이루어집니다: docker build에 비밀을 전달한 다음, Dockerfile에서 이를 사용하는 것입니다.
RUN --mount=type=secret,id=npm_token npm config set //registry.npmjs.org/:_authToken = "$(cat /run/secrets/npm_token)" && npm ci && npm config delete //registry.npmjs.org/:_authToken
그다음 다음과 같이 빌드합니다:
docker build --secret id=npm_token,env=NPM_TOKEN -t node-ai-agent:secure .
Docker의 GitHub Actions 문서 또한 CI 환경에서의 비밀 마운트 및 SSH 마운트를 지원하며, 여기서 비밀 마운트는 기본적으로 /run/secrets 아래의 파일로 나타납니다.
런타임 가드레일 (Runtime Guardrails) 추가
보안 이미지는 하나의 계층일 뿐입니다. 컨테이너 또한 제한된 권한으로 실행되어야 합니다. Node.js AI 앱을 위한 실용적인 Compose 설정은 다음과 같을 수 있습니다:
services:
agent:
image: node-ai-agent:secure
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mem_limit: 1g
cpus: 1
environment:
NODE_ENV: production
OPENAI_API_KEY: ${OPENAI_API_KEY}
DATABASE_URL: ${DATABASE_URL}
읽기 전용 파일 시스템 (read-only filesystem)은 앱이 예상치 못한 곳에 파일을 쓰는 것을 방지합니다. 임시 파일 시스템 (tmpfs)은 임시 파일을 위한 안전한 장소를 제공합니다. 리눅스 기능 (Linux capabilities)을 드롭 (dropping)하는 것은 컨테이너가 할 수 있는 작업을 줄입니다. no-new-privileges는 권한 상승 (privilege escalation)을 방지합니다. CPU 및 메모리 제한은 잘못된 루프, 폭주하는 브라우저 프로세스 또는 예상치 못한 에이전트 동작의 영향 범위 (blast radius)를 줄여줍니다.
이러한 설정은 조정이 필요할 수 있습니다. 브라우저 자동화, 파일 처리 도구 및 일부 네이티브 종속성 (native dependencies)은 추가 권한이나 쓰기 가능한 디렉토리를 요구할 수 있습니다. 목표는 모든 제한 사항을 맹목적으로 복사하는 것이 아닙니다. 목표는 제한적인 상태에서 시작하여 애플리케이션이 실제로 필요로 하는 것만 여는 것입니다.
도구 및 데이터 경로 격리
AI 에이전트는 종종 도구 (tools)를 호출합니다. 이러한 도구들이 모두 동일한 권한으로 실행되어서는 안 됩니다.
GitHub MCP 서버는 GitHub에 대한 네트워크 액세스 (network access)가 필요할 수 있지만, 로컬 파일 시스템 (local filesystem)에 대한 쓰기 권한 (write access)은 필요하지 않아야 합니다. 파일 시스템 도구 (filesystem tool)는 특정 워크스페이스 (workspace)에 대한 읽기 권한 (read access)이 필요할 수 있지만, 호스트 머신 (host machine) 전체를 볼 수 있어서는 안 됩니다. 브라우저 자동화 도구 (browser automation tool)는 임시 쓰기 가능 공간 (writable space)이 필요할 수 있지만, 데이터베이스 자격 증명 (database credentials)은 필요하지 않아야 합니다. 간단한 아키텍처는 앱 (app), 도구 (tools), 그리고 데이터 (data)를 분리합니다. 이러한 분리가 중요한 이유는 침해 (compromise)가 곧 전체 액세스 (total access)를 의미해서는 안 되기 때문입니다. 만약 브라우저 도구가 침해되더라도, 자동으로 데이터베이스 자격 증명을 얻어서는 안 됩니다. 파일 시스템 도구가 침해되더라도, 자동으로 모델 제공자 키 (model provider key)를 얻어서는 안 됩니다. GitHub 도구가 침해되더라도, 가장 유용한 최소한의 토큰 범위 (token scope)만을 가져야 합니다.
SBOM 및 스캐닝 (Scanning) 사용하기
보이지 않는 것은 보안할 수 없습니다. 소프트웨어 자재 명세서 (Software Bill of Materials, SBOM)는 이미지 내부의 구성 요소 (components)를 나열합니다. Docker Scout는 SBOM을 사용하여 이미지 내의 구성 요소를 파악하고 이를 취약점 데이터 (vulnerability data)와 교차 참조합니다. Docker의 Scout 문서는 이를 이미지를 분석하고, 구성 요소의 인벤토리 (inventory)를 생성하며, 해당 인벤토리를 취약점 데이터베이스 (vulnerability databases)와 대조하는 공급망 보안 (supply chain security) 솔루션으로 설명합니다. 기본적인 스캔은 CI 워크플로 (CI workflow)의 일부가 될 수 있습니다:
name : Docker Security Scan
on :
pull_request :
push :
branches :
- main
jobs :
scan :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- name : Build image
run : docker build -t node-ai-agent:ci .
- name : Scan image with Docker Scout
uses : docker/scout-action@v1
with :
image : node-ai-agent:ci
command : cves
only-severities : critical,high
Docker Scout의 이미지 분석 (image analysis) 문서는 이미지 분석이 SBOM과 이미지 메타데이터 (image metadata)를 추출한 다음, 이를 보안 권고 (security advisories)의 취약점 데이터와 대조하여 평가한다고 설명합니다. 스캐닝이 이미지의 안전을 보장하는 것은 아닙니다. 그것은 가시성 (visibility)을 제공합니다.
그러한 가시성 (visibility)은 AI 앱에서 특히 중요합니다. 왜냐하면 의존성 표면 (dependency surface)에 npm 패키지, 시스템 패키지, 브라우저 의존성, 도구 서버 (tool servers), 그리고 때로는 모델 아티팩트 (model artifacts)가 포함되기 때문입니다. '중요한 항목 고정 (Pin What Matters)' 태그는 편리하지만, 변경될 수 있습니다. 프로덕션 (production) 이미지를 위해서는 다이제스트 (digest)를 통해 중요한 베이스 이미지 (base images)를 고정하십시오: FROM node:22-slim@sha256:<digest>. 다이제스트는 특정 이미지를 식별하므로 빌드를 더 예측 가능하게 만듭니다. 여전히 정기적으로 업데이트할 수 있지만, 업데이트가 우발적인 것이 아니라 의도적인 것이 됩니다. 동일한 사고방식이 MCP 서버 이미지 및 기타 도구 컨테이너 (tool containers)에도 적용됩니다. 프로덕션 워크플로에서 무작위의 latest 이미지를 가져오는 것을 피하십시오. 알려진 버전이나 다이제스트를 사용하십시오. 소스를 검토하십시오. 업데이트 프로세스를 유지하십시오. 이는 도구가 작업을 수행할 수 있기 때문에 에이전트 시스템 (agent systems)에서 특히 중요합니다. 침해되었거나 예기치 않게 변경된 도구 이미지는 깨진 정적 자산 (static asset) 빌드보다 더 위험합니다.
실용적인 보안 체크리스트 (Security Checklist)
Docker에서 Node.js AI 앱을 배포하기 전에 다음의 기본 사항을 확인하십시오:
- 신뢰할 수 있는 베이스 이미지를 사용하십시오. 가능하다면 강화된 이미지 (hardened image)를 권장합니다.
- 프로덕션에서
latest사용을 피하고 중요한 이미지는 다이제스트로 고정하십시오. - 빌드 도구가 런타임 이미지 (runtime image)에 포함되지 않도록 멀티 스테이지 Dockerfile (multi-stage Dockerfile)을 사용하십시오.
npm ci로 의존성을 설치하고 프로덕션 의존성만 배포하십시오.- 컨테이너를 비루트 사용자 (non-root user)로 실행하십시오.
.env,.npmrc, 로컬 로그, 테스트 보고서 또는 불필요한 파일을 이미지에 복사하지 마십시오.- 프라이빗 패키지 설치를 위해 Docker Build Secrets를 사용하십시오.
- 모델 제공자 키, GitHub 토큰, 데이터베이스 자격 증명, MCP 자격 증명을 런타임에 시크릿 매니저 (secret manager)를 통해 전달하십시오.
- CI에서 이미지 스캐닝 (image scanning)을 실행하고 높음(high) 및 심각(critical) 탐지 결과를 검토하십시오.
- 가능한 경우 읽기 전용 파일 시스템 (read-only filesystems), 드롭된 기능 (dropped capabilities), 리소스 제한 (resource limits) 및 좁은 네트워크 액세스를 사용하십시오.
- 모든 MCP 서버와 도구에 최소한의
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기