@devslab/ssrf-guard-js
v0.1.1
Published
SSRF protection for JavaScript and TypeScript: URL policy validation, private-network blocking, redirect checks, and LLM tool-input guards.
Maintainers
Readme
ssrf-guard-js
SSRF protection for JavaScript and TypeScript.
This is the JS/TS sibling of devslab-kr/ssrf-guard.
It ports the same core security model:
- URL-time validation: scheme, host allowlist, port, userinfo, IP-literal checks
- private-network IP classification
- LLM/tool-call JSON scanning for hidden URLs
- guarded fetch with URL, DNS, and redirect checks
Install
pnpm add @devslab/ssrf-guard-jsFor a copy-paste tutorial, see the documentation site: https://devslab-kr.github.io/ssrf-guard-js/
URL Policy
import { validateUrl } from '@devslab/ssrf-guard-js';
validateUrl('https://api.example.com/v1', {
exactHosts: ['api.example.com'],
allowedSchemes: ['https'],
allowedPorts: [-1, 443],
});Empty exactHosts and suffixes are fail-closed: no host is allowed until
you configure one.
Defaults:
allowedSchemes:['http', 'https']allowedPorts:[-1, 80, 443]rejectIpLiteralHosts:truerejectUserInfo:trueblockPrivateNetworks:true
LLM Tool Input Guard
import { guardToolInputJson } from '@devslab/ssrf-guard-js';
const violation = guardToolInputJson(
JSON.stringify({ request: { target: 'http://169.254.169.254/latest/meta-data/' } }),
{ exactHosts: ['api.example.com'] },
);
if (violation) {
return violation; // structured JSON error for the model/tool caller
}The guard walks the entire JSON tree. A bad URL hidden inside a nested object, array, or explanation field is still blocked.
Guarded Fetch
import { safeFetch } from '@devslab/ssrf-guard-js';
const response = await safeFetch('https://api.example.com/data', {
exactHosts: ['api.example.com'],
allowedSchemes: ['https'],
});safeFetch validates the URL, checks DNS results for private/local IPs, and
revalidates every redirect hop.
Node's built-in fetch does not expose the same socket-level IP pinning API
that the Java Apache HttpClient adapter uses. Treat safeFetch as a strong
guard rail, but use strict allowlists or a dedicated egress service for
high-risk arbitrary URL crawling.
Express
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 });
},
);The middleware scans req.body and req.query by default. It returns a
structured 400 response when it finds a blocked URL.
Vite
Use this when your Vite dev server has SSR/proxy endpoints that receive a URL and then fetch it server-side.
// vite.config.ts
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'],
},
}),
],
});The plugin scans query params named url, target, uri, and href by
default. Example blocked request:
/api/crawl?url=http://169.254.169.254/latest/meta-data/LangChain / Agent Tools
createGuardedToolHandler wraps any object-input tool function without taking
a hard dependency on LangChain.
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();
}),
});If the model tries to pass a private IP, metadata URL, or non-allowed host, the
tool returns a structured ssrf_blocked JSON string instead of fetching it.
Block Reasons
Thrown SsrfGuardError instances expose stable reason values:
blocked_schemeblocked_hostblocked_portblocked_ip_literalblocked_userinfoblocked_private_ipblocked_redirectblocked_other
License
Apache-2.0
Maintainer Release
Publishing is handled by GitHub Actions.
- Add an npm automation token as the repository secret
NPM_TOKEN. - Bump
versioninpackage.json. - Commit the change.
- Create and push a matching tag, for example:
git tag v0.1.1
git push origin main --tagsThe Publish to npm workflow verifies the package, checks that the tag matches
package.json, then runs:
npm publish --access public --provenance