기본 패턴
# --permission 플래그로 Permission Model 활성화
# 활성화하면 모든 리소스 접근이 기본 차단되며, 필요한 것만 명시적으로 허용한다
node \
--permission \
--allow-fs-read="/app/data" \
--allow-fs-write="/app/output" \
--allow-net="api.example.com:443" \
server.js// server.js — 런타임에 권한 여부를 확인하는 방어적 패턴
import { readFile, writeFile } from "node:fs/promises";
// 작업 전 권한 확인
if (!process.permission.has("fs.read", "/app/data/config.json")) {
throw new Error("설정 파일 읽기 권한 없음 — --allow-fs-read 확인 필요");
}
const config = await readFile("/app/data/config.json", "utf-8");
console.log("설정 로드 완료:", JSON.parse(config));설명
Permission Model은 런타임에 리소스 접근 범위를 선언적으로 제한한다
Node.js는 기본적으로 OS 사용자 권한 범위 안에서 파일, 네트워크, 자식 프로세스 등 모든 리소스에 자유롭게 접근할 수 있다. 애플리케이션에 취약점이 있거나 악의적인 의존성이 포함된 경우 이 접근 범위가 공격 벡터가 된다. --permission 플래그를 사용하면 허용 목록(allowlist) 방식으로 필요한 접근만 열어두고 나머지는 차단할 수 있다. 이는 최소 권한 원칙(principle of least privilege)을 런타임 수준에서 강제한다.
# 권한 없이 파일 접근 시 ERR_ACCESS_DENIED 발생
node --permission app.js
# → node:fs read → Error [ERR_ACCESS_DENIED]: Access to FileSystemRead was blocked
# 특정 경로만 허용
node --permission --allow-fs-read="/etc/ssl/certs" app.js
# 와일드카드로 디렉터리 전체 허용
node --permission --allow-fs-read="/app/*" app.js--allow-fs-read / --allow-fs-write로 파일 접근 범위를 경로 단위로 제한한다
파일 시스템 권한은 읽기(fs.read)와 쓰기(fs.write)를 독립적으로 제어한다. 경로는 절대 경로로 지정하며, 디렉터리를 지정하면 하위 경로 전체가 허용된다. 읽기 전용 설정 파일과 쓰기 가능한 출력 디렉터리를 분리해 명시하면 코드 버그나 탈취 시 피해 범위를 최소화할 수 있다.
# 실용적인 서버 구성 예시
node \
--permission \
--allow-fs-read="/app/src,/app/node_modules,/etc/ssl" \
--allow-fs-write="/app/logs,/tmp" \
--allow-fs-read="/proc/self" \ # Node.js 자체 동작에 필요
src/server.js// 권한 범위를 초과한 접근은 예외 발생 — try/catch로 처리
import { readFile } from "node:fs/promises";
async function safeRead(filePath) {
if (!process.permission.has("fs.read", filePath)) {
// 권한 없음을 미리 감지해 명확한 오류 메시지 제공
throw new Error(`읽기 권한 없음: ${filePath}`);
}
return readFile(filePath, "utf-8");
}
try {
const content = await safeRead("/app/data/report.json");
console.log(content);
} catch (err) {
console.error("파일 접근 실패:", err.message);
}--allow-net으로 네트워크 접근을 허용 도메인/포트로 제한한다 — supply chain 공격 방어
npm 패키지 의존성 중 악의적인 패키지가 외부 서버로 데이터를 유출하는 supply chain 공격이 현실화되고 있다. --allow-net으로 서비스에 실제 필요한 도메인과 포트만 허용하면, 의존성이 알 수 없는 외부 엔드포인트에 연결하려 할 때 차단된다. 포트를 생략하면 해당 호스트의 모든 포트가 허용되므로 가능하면 포트까지 명시한다.
# 결제 API와 내부 DB만 허용
node \
--permission \
--allow-net="payments.example.com:443,db.internal:5432" \
payment-service.js
# 허용되지 않은 호스트 연결 시도 → ERR_ACCESS_DENIED
# → Error [ERR_ACCESS_DENIED]: Access to Net was blocked// 네트워크 권한 확인 후 조건부 동작
import https from "node:https";
const TARGET = "payments.example.com:443";
if (!process.permission.has("net", TARGET)) {
console.error(`네트워크 접근 불가: ${TARGET}`);
process.exit(1);
}
// 허용된 도메인에만 요청 전송
https.get(`https://payments.example.com/health`, (res) => {
console.log("결제 서버 상태:", res.statusCode);
});process.permission.has()로 런타임에 권한 여부를 확인하는 방어적 코딩 패턴
Permission Model이 활성화된 상태에서 권한 없이 리소스에 접근하면 예외가 발생한다. 이를 미리 막으려면 process.permission.has(scope, resource) 로 권한 여부를 확인하고 명확한 오류 메시지를 제공한다. 이 방식은 Permission Model이 비활성화된 환경(--permission 없이 실행)에서도 안전하게 동작한다 — 비활성화 시 has()는 항상 true를 반환한다.
// process.permission.has(scope, resource?) API
// scope: 'fs.read' | 'fs.write' | 'net' | 'child' | 'worker' | 'inspector' | 'addons'
// resource: 경로 또는 호스트:포트 (생략 시 scope 전체 여부 확인)
function assertPermission(scope, resource) {
// --permission 없이 실행 시 process.permission 자체가 없을 수 있다
if (typeof process.permission === "undefined") return; // 비활성 환경 — 통과
if (!process.permission.has(scope, resource)) {
throw new Error(
`[권한 오류] ${scope} 접근 차단: ${resource ?? "(전체)"}\n` +
`실행 시 --allow-${scope.replace(".", "-")}="${resource}" 추가 필요`
);
}
}
// 사용 예
assertPermission("fs.read", "/app/config.json");
assertPermission("net", "api.example.com:443");
assertPermission("child"); // 자식 프로세스 생성 권한 전체 확인
// 파일 읽기 전 권한 체크를 래핑한 유틸리티
import { readFile } from "node:fs/promises";
async function permissionedReadFile(filePath, encoding = "utf-8") {
assertPermission("fs.read", filePath);
return readFile(filePath, encoding);
}빠른 정리
| 상황 | 적합한 선택 |
|---|---|
| 파일 읽기 경로 제한 | --allow-fs-read="/path" |
| 파일 쓰기 경로 제한 | --allow-fs-write="/path" |
| 특정 도메인·포트만 허용 | --allow-net="host:port" |
| 자식 프로세스 생성 허용 | --allow-child-process |
| worker_threads 생성 허용 | --allow-worker |
| 런타임에 권한 여부 확인 | process.permission.has(scope, resource) |
주의할 점
--permission 플래그 없이 Node.js를 시작하면 Permission Model이 완전히 비활성화된다. 기존 코드와의 하위 호환성은 유지되지만 보안 경계가 전혀 없다. Node.js 20 기준 실험적(Experimental) API이므로 마이너 버전 업데이트 시 동작 변경 여부를 확인해야 한다.
# ❌ Permission Model 비활성 — 모든 리소스에 자유롭게 접근 가능
node app.js
# ✅ Permission Model 활성 — 선언한 리소스만 접근 허용
node --permission --allow-fs-read="/app" --allow-net="api.example.com:443" app.js// ❌ 잘못된 예: Permission Model 활성 상태에서 권한 없이 접근
// --permission 플래그를 사용했지만 --allow-fs-read를 생략한 경우
import { readFileSync } from "node:fs";
const data = readFileSync("/app/config.json"); // ERR_ACCESS_DENIED 발생
// ✅ 올바른 예: 권한 확인 후 접근, 실행 명령에 --allow-fs-read 포함
if (process.permission?.has("fs.read", "/app/config.json")) {
const data = readFileSync("/app/config.json");
} else {
console.error("설정 파일 읽기 권한 없음 — 관리자에게 문의");
}