ssrf-guard-js v0.1.1

사용자가 준 URL을 서버에서 fetch하기 전에 막아야 할 것을 막습니다.

`@devslab/ssrf-guard-js`는 TypeScript/JavaScript용 SSRF guard입니다. URL allowlist, private IP 차단, redirect 재검증, LLM tool input 검사를 제공합니다. 아래 예제는 그대로 복사해서 실행할 수 있습니다.

Node 22+ TypeScript ready Apache-2.0 LLM tool guard

5분 시작하기

빈 폴더를 만들고 패키지를 설치합니다.

mkdir ssrf-guard-demo
cd ssrf-guard-demo
npm init -y
npm pkg set type=module
npm install @devslab/ssrf-guard-js

`demo.mjs` 파일을 만들고 아래 코드를 붙여넣습니다.

import { validateUrl } from '@devslab/ssrf-guard-js';

const policy = {
  exactHosts: ['api.example.com'],
  allowedSchemes: ['https'],
  allowedPorts: [-1, 443],
};

validateUrl('https://api.example.com/v1/users', policy);
console.log('allowed');

try {
  validateUrl('http://169.254.169.254/latest/meta-data/', policy);
} catch (error) {
  console.log('blocked:', error.reason);
}

실행합니다.

node demo.mjs

정상 출력:

allowed
blocked: blocked_ip_literal

가장 중요한 규칙

1. 기본은 fail-closed

`exactHosts`나 `suffixes`를 설정하지 않으면 어떤 host도 허용하지 않습니다.

2. IP literal은 기본 차단

`127.0.0.1`, `2130706433`, `[::1]` 같은 우회형 IP를 URL 단계에서 막습니다.

3. redirect도 다시 검사

`safeFetch`는 302 Location이 private IP나 허용되지 않은 host로 향하면 차단합니다.

API 사용법

`validateUrl(input, policy)`

URL을 fetch하기 전에 먼저 검사합니다. 통과하면 `URL` 객체를 반환하고, 실패하면 `SsrfGuardError`를 던집니다.

import { validateUrl } from '@devslab/ssrf-guard-js';

const url = validateUrl('https://api.example.com/data', {
  exactHosts: ['api.example.com'],
  allowedSchemes: ['https'],
  allowedPorts: [-1, 443],
});

console.log(url.href);

`safeFetch(input, policy, init?)`

URL 검증, DNS private IP 검사, redirect 재검증을 포함한 fetch helper입니다.

import { safeFetch } from '@devslab/ssrf-guard-js';

const response = await safeFetch('https://api.example.com/data', {
  exactHosts: ['api.example.com'],
  allowedSchemes: ['https'],
  allowedPorts: [-1, 443],
});

const text = await response.text();
console.log(text);
Node의 built-in `fetch`는 Java Apache HttpClient처럼 검증한 IP로 socket을 고정하는 API를 제공하지 않습니다. 위험도가 높은 임의 URL 크롤링은 strict allowlist 또는 별도 guarded egress service를 사용하세요.

Express / Vite / LangChain 연동

Express: middleware 한 줄 추가

사용자가 보낸 body/query 안의 URL을 검사하고, 위험한 URL이면 `400` JSON 응답을 반환합니다.

import express from 'express';
import { createExpressUrlGuard } from '@devslab/ssrf-guard-js';

const app = express();
app.use(express.json());

app.post(
  '/crawl',
  createExpressUrlGuard({
    exactHosts: ['example.com'],
    suffixes: ['example.com'],
    allowedSchemes: ['https'],
  }),
  async (req, res) => {
    res.json({ ok: true });
  },
);

Vite: `vite.config.ts`에 plugin 추가

Vite dev server의 SSR/proxy endpoint가 URL을 받아 server-side fetch하는 경우에 사용합니다. 브라우저가 직접 외부로 보내는 모든 요청을 막는 도구는 아닙니다.

import { defineConfig } from 'vite';
import { ssrfGuardVitePlugin } from '@devslab/ssrf-guard-js/vite';

export default defineConfig({
  plugins: [
    ssrfGuardVitePlugin({
      routes: ['/api/crawl'],
      policy: {
        suffixes: ['example.com'],
        allowedSchemes: ['https'],
      },
    }),
  ],
});

아래 요청은 dev server middleware에서 차단됩니다.

/api/crawl?url=http://169.254.169.254/latest/meta-data/

LangChain / Agent Tool: tool 함수를 감싸기

모델이 tool에 넘긴 object 전체를 검사한 뒤, 안전할 때만 실제 tool 함수를 실행합니다.

import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod';
import { createGuardedToolHandler, safeFetch } from '@devslab/ssrf-guard-js';

const policy = {
  suffixes: ['example.com'],
  allowedSchemes: ['https'],
};

export const fetchUrlTool = new DynamicStructuredTool({
  name: 'fetch_url',
  description: 'Fetch an allowed URL',
  schema: z.object({ url: z.string().url() }),
  func: createGuardedToolHandler(policy, async ({ url }) => {
    const response = await safeFetch(url, policy);
    return await response.text();
  }),
});

LLM Tool URL 검사

LLM tool input은 top-level `url` 필드만 보면 부족합니다. 공격 URL이 nested object, array, 설명 문장 안에 숨을 수 있습니다. `guardToolInputJson`은 JSON 전체를 검사합니다.

import { guardToolInputJson } from '@devslab/ssrf-guard-js';

const toolInput = JSON.stringify({
  request: {
    target: 'http://169.254.169.254/latest/meta-data/',
  },
});

const violation = guardToolInputJson(toolInput, {
  exactHosts: ['api.example.com'],
});

if (violation) {
  console.log(violation);
  // 이 문자열을 tool 결과로 LLM에게 돌려주면 됩니다.
}

Policy 옵션

옵션 기본값 설명
exactHosts [] 정확히 일치해야 하는 host 목록입니다. 예: api.example.com
suffixes [] example.com과 모든 하위 도메인을 허용합니다. badexample.com은 허용하지 않습니다.
allowedSchemes ['http', 'https'] 보통 production에서는 ['https']로 좁히는 것을 권장합니다.
allowedPorts [-1, 80, 443] -1은 URL에 명시 포트가 없는 경우입니다. 예: https://api.example.com/
rejectIpLiteralHosts true IP 주소를 host로 직접 쓰는 URL을 차단합니다.
rejectUserInfo true https://user:pass@example.com 형태를 차단합니다.
blockPrivateNetworks true DNS 결과가 loopback, private, link-local, metadata IP면 차단합니다.

차단 이유

`SsrfGuardError.reason`은 아래 문자열 중 하나입니다.

blocked_scheme
blocked_host
blocked_port
blocked_ip_literal
blocked_userinfo
blocked_private_ip
blocked_redirect
blocked_other