@tumen-security/guard
v0.2.0
Published
Tumen Security 실시간 방어(RASP) — Express/Next.js/Fastify용 런타임 보안 미들웨어. 요청 검사(perimeter) + sink 계측(SQLi·NoSQLi·명령어 인젝션·경로탐색·SSRF·프롬프트 인젝션을 실행 지점에서 문맥/데이터흐름으로 탐지·차단) + 레이트리밋 + 스캐너 차단
Maintainers
Readme
@tumen-security/guard — 실시간 방어 미들웨어
Tumen Security의 ② 실시간 방어(RASP). AI로 앱 만든 비개발자를 위한 런타임 보안 미들웨어입니다. Aikido Zen과 같은 기법(sink 계측)을 자체 clean-room으로 구현했습니다(코드는 MIT).
내 Express / Next.js / Fastify 앱에 넣으면, 요청을 검사할 뿐 아니라 **앱 안의 실제 실행 지점(sink)**에서 공격을 잡습니다.
- ① 요청 검사(perimeter): 들어오는 요청 페이로드에서 SQL/NoSQL/명령어 인젝션, 경로 탐색, 반사형 XSS 등 42종 패턴 탐지
- ② sink 계측(RASP): 앱 내부 실행 지점에 사용자 입력이 위험하게 도달하는지 문맥/데이터흐름(taint)으로 판정 → 시그니처 없이 제로데이도 탐지. 파라미터라이즈드 쿼리는 자동으로 오탐 0. 커버 sink:
- SQL 인젝션 —
pg,mysql2 - NoSQL 인젝션 —
mongodb(연산자 오염$ne·JS eval$where) - 명령어 인젝션 —
child_processexec/execSync/spawn(shell) - 경로 탐색 —
fs.readFile/readFileSync/createReadStream - SSRF —
http/httpsrequest, 전역fetch - 프롬프트 인젝션(LLM) —
detectPromptInjection(vibeguard 정신 계승, 차별점)
- SQL 인젝션 —
- 레이트리밋: IP별 슬라이딩 윈도우(인메모리)
- 스캐너 차단: sqlmap, nikto, nmap 등 악성 스캐너 User-Agent(옵션)
- 세 가지 모드:
monitor(로깅만 — 기본값) /auto(치명·높음만 차단) /block(탐지된 건 전부 차단 + 429/403) - 절대 앱을 안 깨뜨림: 내부 오류는 전부 fail-open(요청 통과)
★ sink 계측(RASP) — 제로데이 런타임 방어
요청 검사(perimeter)에 더해, 앱 안에서 실제로 위험한 지점(sink)에 사용자 입력이 도달하는 순간을 잡습니다.
원리 (Aikido Zen과 같은 '기법', 코드는 자체 clean-room 구현 — MIT):
- 모듈 훅 —
require-in-the-middle(CJS) /import-in-the-middle(ESM)로 위험 모듈(pg/mysql2/mongodb) 로드를 가로채 실행 함수를 감쌉니다. 내장(child_process/fs/http)·전역fetch는 초기화 시 이미 로드돼 있어 즉시 감쌉니다. - 요청 문맥 추적 —
AsyncLocalStorage로 매 요청의 사용자 입력을 담고, sink에 도달한 값이 그 입력에서 온(오염된) 것인지 런타임 taint로 판정합니다("이 입력이 코드로 해석되나").
- 파라미터라이즈드 쿼리 = 오탐 0: 값이 바인딩(
$1/?)으로 넘어가면 raw 값이 최종 쿼리 문자열에 없어 애초에 판정 대상이 아닙니다. 안전한 코드는 절대 안 걸립니다. - 문자열 이어붙이기(취약)로 인젝션 페이로드가 sink에 도달하면 → 실증(confirmed)으로 탐지/차단.
한계(정직하게): Prisma 등 자체 쿼리엔진은 아직 미계측(하위 pg 드라이버 계측으로 일부 커버). LLM 프롬프트 인젝션은 자동 sink 훅이 아니라 detectPromptInjection(prompt, userInputs)를 LLM 호출 직전에 직접 호출하는 방식입니다. block 모드는 위험 sink 호출을 중단(콜백=에러, 프로미스=reject, 동기=throw)하므로 스테이징에서 monitor로 오탐을 먼저 확인한 뒤 켜세요.
설치
npm i @tumen-security/guard의존성: 모듈 훅용 require-in-the-middle/import-in-the-middle(둘 다 nodejs 공식, 경량). 없어도 이미 로드된 모듈은 계측됩니다(폴백). Express/Next/Fastify는 peer(강제 설치 안 함).
한 줄 설치(register) — 미들웨어 없이도 RASP
// 진입 파일 맨 위 한 줄 → RITM/IITM 자동 훅 + http 서버 요청 자동 컨텍스트(URL/쿼리 taint)
import '@tumen-security/guard/register';
// 또는: node --import @tumen-security/guard/register app.js
// env: TUMEN_GUARD_MODE=monitor|auto|block, TUMEN_GUARD_API_KEY, TUMEN_GUARD_REPORT_URL, TUMEN_GUARD_POLICY_URLregister는 URL/쿼리 taint까지 자동입니다. POST body taint와 요청 검사(perimeter)·레이트리밋까지 원하면 아래 guard() 미들웨어를 함께 쓰세요(body 파싱 후 컨텍스트를 보강).
원격 정책 제어(감시↔차단)
policyUrl(미들웨어 옵션) 또는 TUMEN_GUARD_POLICY_URL(register)을 주면, guard가 그 URL을 주기적으로(기본 60초) 폴링해 대시보드에서 바꾼 mode(monitor↔auto↔block)를 코드 재배포 없이 반영합니다. 폴링 실패 시 기존 모드를 유지합니다(fail-open).
⚠️ 정책이 코드보다 우선합니다. 정책을 받는 동안에는 mode 옵션에 뭘 적었든 정책 값이 이깁니다. 정책 없이 쓰실 거면 mode를 직접 지정하세요.
app.use(guard({ apiKey, reportUrl,
policyUrl: 'https://tumensecurity.com/api/guard/policy' })); // 대시보드 토글을 폴링Express
import express from 'express';
import { guard } from '@tumen-security/guard';
const app = express();
app.use(express.json()); // body 검사를 원하면 body 파서를 guard보다 먼저
app.use(guard({
mode: 'monitor', // 기본값. 오탐 확인 후 'auto'(치명·높음만 차단) → 'block'
apiKey: process.env.TUMEN_GUARD_API_KEY, // 대시보드에서 발급
reportUrl: 'https://tumensecurity.com/api/guard/events',
rateLimit: { windowMs: 60_000, max: 100 }, // 1분에 100요청 초과 시 429
blockScanners: true, // sqlmap 등 스캐너 UA 차단
onDetect: (event) => { // 내 로깅에 꽂기(선택)
console.warn('[tumen-guard]', event.decision, event.findings.map(f => f.kind));
},
}));CommonJS도 됩니다: const { guard } = require('@tumen-security/guard');
Next.js (App Router)
(A) middleware.js
import { NextResponse } from 'next/server';
import { nextGuard } from '@tumen-security/guard';
const runGuard = nextGuard({
mode: 'block',
apiKey: process.env.TUMEN_GUARD_API_KEY,
reportUrl: 'https://tumensecurity.com/api/guard/events',
// NextResponse는 여러분 앱 것을 주입(패키지가 next를 직접 import하지 않음)
denyResponse: (code, body) => NextResponse.json(body, { status: code }),
});
export function middleware(request) {
return runGuard(request) || NextResponse.next();
}
export const config = { matcher: ['/api/:path*'] };(B) route handler 감싸기
import { NextResponse } from 'next/server';
import { wrapGuard } from '@tumen-security/guard';
async function handler(request) {
return NextResponse.json({ ok: true });
}
export const POST = wrapGuard(handler, {
mode: 'block',
denyResponse: (code, body) => NextResponse.json(body, { status: code }),
});Next의 Edge 미들웨어/Web Request는 body가 스트림이라 body는 기본 검사 안 함(url·query·headers만). body 검사가 필요하면 route handler에서 파싱 후
inspectRequest()를 직접 부르세요(아래).
옵션
| 옵션 | 기본값 | 설명 |
|---|---|---|
| mode | 'monitor' | 'monitor'=로깅만·통과, 'auto'=치명·높음만 차단, 'block'=탐지된 건 전부 차단 |
| apiKey | null | 고객별 발급 키(보고에 사용) |
| reportUrl | null | 이벤트 수신 엔드포인트 |
| rateLimit | {windowMs:60000, max:100} | IP별 창/최대 |
| rules | 전부 true | { payload, rateLimit, scanner } 개별 토글 |
| blockScanners | false | 스캐너 UA를 실제 차단(false면 탐지만) |
| trustProxy | false | x-forwarded-for에서 IP 추출. 신뢰된 프록시(Vercel·Nginx·Cloudflare) 뒤에서만 켜세요 — 직접 노출된 앱에서 XFF는 위조 가능(레이트리밋 우회) |
| statusCode | 403 | 공격 차단 시 코드(레이트리밋은 항상 429) |
| onDetect | null | (event) => void 탐지 콜백 |
| report | true | 우리 서비스로 이벤트 보고 |
순수 함수 직접 쓰기
미들웨어 없이 판정 로직만 쓸 수도 있습니다(테스트/커스텀 통합).
import { detectAttacks, inspectRequest, RateLimiter } from '@tumen-security/guard/lib';
detectAttacks({ query: { id: "1' OR '1'='1" } });
// → [{ tool:'guard', kind:'sqli', severity:'high', rule_id:'guard-sqli', ... }]안전 원칙
- 기본 monitor: 설치만으로는 아무것도 막지 않습니다. 우리 오탐 하나로 여러분 앱 손님이 튕기는 일은 없어야 하니까요. 로그로 먼저 확인한 뒤
auto또는block을 켜세요. (모드를 오타로 적으면 경고를 찍고monitor로 동작합니다 — 조용히 차단이 켜지지 않습니다.) - fail-open: 미들웨어 내부에서 뭐가 터져도 요청은 통과하고
next()가 호출됩니다. - 보고는 fire-and-forget: 우리 서버로의 이벤트 전송은 응답을 기다리지 않습니다(지연 0, 실패해도 무시).
- 개인정보 최소화: 보고 시 IP 마스킹, 쿼리스트링 제거, 시크릿 패턴 별표 처리.
테스트
node --test packages/guard/lib.test.mjs