ssrf-agent-guard 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,27 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ parserOptions: {
5
+ ecmaVersion: 2020,
6
+ sourceType: 'module',
7
+ },
8
+ env: {
9
+ node: true,
10
+ es2020: true,
11
+ jest: true,
12
+ },
13
+ extends: [
14
+ 'eslint:recommended',
15
+ 'plugin:@typescript-eslint/recommended', // TypeScript rules
16
+ 'plugin:prettier/recommended' // Integrate Prettier
17
+ ],
18
+ plugins: ['@typescript-eslint', 'prettier'],
19
+ rules: {
20
+ 'prettier/prettier': 'error', // Show prettier errors as ESLint errors
21
+ '@typescript-eslint/explicit-function-return-type': 'off',
22
+ '@typescript-eslint/no-explicit-any': 'off',
23
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
24
+ 'no-console': 'warn',
25
+ 'prefer-const': 'error',
26
+ },
27
+ };
package/.prettierrc ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "trailingComma": "all",
5
+ "printWidth": 100,
6
+ "tabWidth": 4,
7
+ "endOfLine": "lf"
8
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Swapnil Srivastava
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # ssrf-agent-guard
2
+
3
+ #### SSRF-Guard is a Node.js module for protecting your HTTP/HTTPS requests against SSRF (Server-Side Request Forgery) attacks. It wraps http.Agent and https.Agent to enforce pre and post DNS host/IP checks, block access to cloud metadata endpoints, private IPs, and unsafe domains.
4
+ ---
5
+
6
+ ## Features
7
+
8
+ * Block requests to internal/private IPs
9
+ * Detect and block cloud provider metadata endpoints (AWS, GCP, Azure)
10
+ * DNS rebinding detection
11
+ * Fully written in TypeScript with type definitions
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install ssrf-agent-guard
19
+ # or using yarn
20
+ yarn add ssrf-agent-guard
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Usage
26
+
27
+ `isValidDomainOptions` reference [is-valid-domain](https://github.com/miguelmota/is-valid-domain)
28
+
29
+ ### axios
30
+
31
+ ```ts
32
+ const ssrfAgentGuard = require('ssrf-agent-guard');
33
+ const url = 'https://127.0.0.1'
34
+ const isValidDomainOptions = {
35
+ subdomain: true,
36
+ wildcard: true
37
+ };
38
+ axios.get(url, {httpAgent: ssrfAgentGuard(url), httpsAgent: ssrfAgentGuard(url)})
39
+ .then((response) => {
40
+ console.log(`Success`);
41
+ })
42
+ .catch((error) => {
43
+ console.log(`${error.toString().split('\n')[0]}`);
44
+ })
45
+ .then(() => {
46
+
47
+ });
48
+ ```
49
+
50
+ ### node-fetch
51
+
52
+ ```ts
53
+ const ssrfAgentGuard = require('ssrf-agent-guard');
54
+ const url = 'https://127.0.0.1'
55
+ const isValidDomainOptions = {
56
+ subdomain: true,
57
+ wildcard: true
58
+ };
59
+ fetch(url, {
60
+ agent: ssrfAgentGuard(url, isValidDomainOptions)
61
+ })
62
+ .then((response) => {
63
+ console.log(`Success`);
64
+ })
65
+ .catch(error => {
66
+ console.log(`${error.toString().split('\n')[0]}`);
67
+ });
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Development
73
+
74
+ ```bash
75
+ # install dependencies
76
+ npm install
77
+
78
+ # build
79
+ npm run build
80
+
81
+ # run tests
82
+ npm test
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Contributing
88
+
89
+ 1. Fork the repository
90
+ 2. Create a branch (`git checkout -b feature/new-feature`)
91
+ 3. Make changes and run tests
92
+ 4. Commit and push your branch
93
+ 5. Open a Pull Request
94
+
95
+ ---
96
+
97
+ ## Credits:
98
+ * SSRF prevention techniques: [SSRF Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
99
+ * Implementation inspired By [ssrf-req-filter](https://github.com/y-mehta/ssrf-req-filter/)
100
+
101
+ ---
102
+
103
+ ## License
104
+
105
+ MIT © [Swapnil Srivastava](https://swapniluneva.github.io)
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var http = require('http');
6
+ var https = require('https');
7
+ var isValidDomain = require('is-valid-domain');
8
+ var ipaddr = require('ipaddr.js');
9
+
10
+ const CLOUD_METADATA_HOSTS = [
11
+ '169.254.169.254',
12
+ '169.254.169.253',
13
+ 'metadata.google.internal',
14
+ '169.254.170.2',
15
+ ];
16
+
17
+ // lib/utils.ts
18
+ /**
19
+ * Checks if the input is an IP address (v4/v6).
20
+ */
21
+ function isIp(input) {
22
+ return ipaddr.isValid(input);
23
+ }
24
+ /**
25
+ * Returns true for valid public unicast IP addresses.
26
+ */
27
+ function isPublicIp(ip) {
28
+ return ipaddr.parse(ip).range() === 'unicast';
29
+ }
30
+ /**
31
+ * Validates whether a domain is syntactically valid.
32
+ */
33
+ function isSafeIp(hostname) {
34
+ // Case 1: IP address
35
+ if (isIp(hostname)) {
36
+ return isPublicIp(hostname); // only allow public IPs
37
+ }
38
+ return true;
39
+ }
40
+ /**
41
+ * High-level validation for hostnames (domains + public IPs).
42
+ */
43
+ function isSafeHost(hostname, isValidDomainOptions) {
44
+ // Block cloud metadata IP/domains
45
+ if (CLOUD_METADATA_HOSTS.indexOf(hostname))
46
+ return false;
47
+ if (!isSafeIp(hostname))
48
+ return false;
49
+ // Case 2: Domain name
50
+ return isValidDomain(hostname, {
51
+ allowUnicode: false,
52
+ subdomain: false,
53
+ ...isValidDomainOptions,
54
+ });
55
+ }
56
+
57
+ // Instantiate the default agents
58
+ const httpAgent = new http.Agent();
59
+ const httpsAgent = new https.Agent();
60
+ /**
61
+ * Determines the correct Agent instance based on the input.
62
+ * @param url The URL or another input to determine the agent type.
63
+ * @returns The appropriate HttpAgent or HttpsAgent instance.
64
+ */
65
+ const getAgent = (url) => {
66
+ // If it's a string, check if it implies HTTPS
67
+ if (typeof url === 'string' && url.startsWith('https')) {
68
+ return httpsAgent;
69
+ }
70
+ // Default to HTTP agent
71
+ return httpAgent;
72
+ };
73
+ // Define a Symbol for a unique property to prevent double-patching the agent.
74
+ const CREATE_CONNECTION = Symbol('createConnection');
75
+ /**
76
+ * Patches an http.Agent or https.Agent to enforce an HOST/IP check
77
+ * before and after a DNS lookup.
78
+ *
79
+ * @param url The URL or another input to determine the agent type.
80
+ * @param isValidDomainOptions Options for validating domain names.
81
+ * @returns The patched CustomAgent instance.
82
+ */
83
+ function index (url, isValidDomainOptions) {
84
+ const finalAgent = getAgent(url);
85
+ // Prevent patching the agent multiple times
86
+ if (finalAgent[CREATE_CONNECTION]) {
87
+ return finalAgent;
88
+ }
89
+ finalAgent[CREATE_CONNECTION] = true;
90
+ // The original createConnection function from the Agent
91
+ const createConnection = finalAgent.createConnection;
92
+ // Patch the createConnection method on the agent
93
+ finalAgent.createConnection = (options, fn) => {
94
+ const { host: address } = options;
95
+ // --- 1. Pre-DNS Check (Host/Address Check) ---
96
+ // If the 'host' option is an IP address, check it immediately.
97
+ // If it's a hostname, this check will usually pass (via defaultIpChecker).
98
+ if (address && !isSafeHost(address, isValidDomainOptions)) {
99
+ throw new Error(`DNS lookup ${address} is not allowed.`);
100
+ }
101
+ // Call the original createConnection
102
+ // @ts-expect-error 'this' is not assignable to type 'HttpAgent | HttpsAgent'
103
+ const client = createConnection.call(this, options, fn);
104
+ // --- 2. Post-DNS Check (Lookup Event Check) ---
105
+ // The 'lookup' event fires after the DNS lookup is complete
106
+ // and provides the resolved IP address.
107
+ client?.on('lookup', (err, resolvedAddress) => {
108
+ // If there was an error in lookup, or if the resolved IP is allowed, do nothing.
109
+ if (err) {
110
+ return;
111
+ }
112
+ // Ensure resolvedAddress is a string for the check (it's typically a string for simple lookups)
113
+ const ipToCheck = Array.isArray(resolvedAddress) ? resolvedAddress[0] : resolvedAddress;
114
+ if (!isSafeIp(ipToCheck)) {
115
+ // If the resolved IP is NOT allowed (e.g., a private IP), destroy the connection.
116
+ return client?.destroy(new Error(`DNS lookup ${ipToCheck} is not allowed.`));
117
+ }
118
+ });
119
+ return client;
120
+ };
121
+ return finalAgent;
122
+ }
123
+
124
+ exports.default = index;
@@ -0,0 +1,120 @@
1
+ import { Agent } from 'http';
2
+ import { Agent as Agent$1 } from 'https';
3
+ import isValidDomain from 'is-valid-domain';
4
+ import ipaddr from 'ipaddr.js';
5
+
6
+ const CLOUD_METADATA_HOSTS = [
7
+ '169.254.169.254',
8
+ '169.254.169.253',
9
+ 'metadata.google.internal',
10
+ '169.254.170.2',
11
+ ];
12
+
13
+ // lib/utils.ts
14
+ /**
15
+ * Checks if the input is an IP address (v4/v6).
16
+ */
17
+ function isIp(input) {
18
+ return ipaddr.isValid(input);
19
+ }
20
+ /**
21
+ * Returns true for valid public unicast IP addresses.
22
+ */
23
+ function isPublicIp(ip) {
24
+ return ipaddr.parse(ip).range() === 'unicast';
25
+ }
26
+ /**
27
+ * Validates whether a domain is syntactically valid.
28
+ */
29
+ function isSafeIp(hostname) {
30
+ // Case 1: IP address
31
+ if (isIp(hostname)) {
32
+ return isPublicIp(hostname); // only allow public IPs
33
+ }
34
+ return true;
35
+ }
36
+ /**
37
+ * High-level validation for hostnames (domains + public IPs).
38
+ */
39
+ function isSafeHost(hostname, isValidDomainOptions) {
40
+ // Block cloud metadata IP/domains
41
+ if (CLOUD_METADATA_HOSTS.indexOf(hostname))
42
+ return false;
43
+ if (!isSafeIp(hostname))
44
+ return false;
45
+ // Case 2: Domain name
46
+ return isValidDomain(hostname, {
47
+ allowUnicode: false,
48
+ subdomain: false,
49
+ ...isValidDomainOptions,
50
+ });
51
+ }
52
+
53
+ // Instantiate the default agents
54
+ const httpAgent = new Agent();
55
+ const httpsAgent = new Agent$1();
56
+ /**
57
+ * Determines the correct Agent instance based on the input.
58
+ * @param url The URL or another input to determine the agent type.
59
+ * @returns The appropriate HttpAgent or HttpsAgent instance.
60
+ */
61
+ const getAgent = (url) => {
62
+ // If it's a string, check if it implies HTTPS
63
+ if (typeof url === 'string' && url.startsWith('https')) {
64
+ return httpsAgent;
65
+ }
66
+ // Default to HTTP agent
67
+ return httpAgent;
68
+ };
69
+ // Define a Symbol for a unique property to prevent double-patching the agent.
70
+ const CREATE_CONNECTION = Symbol('createConnection');
71
+ /**
72
+ * Patches an http.Agent or https.Agent to enforce an HOST/IP check
73
+ * before and after a DNS lookup.
74
+ *
75
+ * @param url The URL or another input to determine the agent type.
76
+ * @param isValidDomainOptions Options for validating domain names.
77
+ * @returns The patched CustomAgent instance.
78
+ */
79
+ function index (url, isValidDomainOptions) {
80
+ const finalAgent = getAgent(url);
81
+ // Prevent patching the agent multiple times
82
+ if (finalAgent[CREATE_CONNECTION]) {
83
+ return finalAgent;
84
+ }
85
+ finalAgent[CREATE_CONNECTION] = true;
86
+ // The original createConnection function from the Agent
87
+ const createConnection = finalAgent.createConnection;
88
+ // Patch the createConnection method on the agent
89
+ finalAgent.createConnection = (options, fn) => {
90
+ const { host: address } = options;
91
+ // --- 1. Pre-DNS Check (Host/Address Check) ---
92
+ // If the 'host' option is an IP address, check it immediately.
93
+ // If it's a hostname, this check will usually pass (via defaultIpChecker).
94
+ if (address && !isSafeHost(address, isValidDomainOptions)) {
95
+ throw new Error(`DNS lookup ${address} is not allowed.`);
96
+ }
97
+ // Call the original createConnection
98
+ // @ts-expect-error 'this' is not assignable to type 'HttpAgent | HttpsAgent'
99
+ const client = createConnection.call(this, options, fn);
100
+ // --- 2. Post-DNS Check (Lookup Event Check) ---
101
+ // The 'lookup' event fires after the DNS lookup is complete
102
+ // and provides the resolved IP address.
103
+ client?.on('lookup', (err, resolvedAddress) => {
104
+ // If there was an error in lookup, or if the resolved IP is allowed, do nothing.
105
+ if (err) {
106
+ return;
107
+ }
108
+ // Ensure resolvedAddress is a string for the check (it's typically a string for simple lookups)
109
+ const ipToCheck = Array.isArray(resolvedAddress) ? resolvedAddress[0] : resolvedAddress;
110
+ if (!isSafeIp(ipToCheck)) {
111
+ // If the resolved IP is NOT allowed (e.g., a private IP), destroy the connection.
112
+ return client?.destroy(new Error(`DNS lookup ${ipToCheck} is not allowed.`));
113
+ }
114
+ });
115
+ return client;
116
+ };
117
+ return finalAgent;
118
+ }
119
+
120
+ export { index as default };
package/index.ts ADDED
@@ -0,0 +1,92 @@
1
+ import { Agent as HttpAgent, AgentOptions as HttpAgentOptions } from 'http';
2
+ import { Agent as HttpsAgent, AgentOptions as HttpsAgentOptions } from 'https';
3
+ import { Duplex } from 'stream';
4
+
5
+ import { isSafeHost, isSafeIp } from './lib/utils';
6
+ import { IsValidDomainOptions } from './lib/types';
7
+
8
+ // Define the type for the Agent that this module will modify and return.
9
+ // It can be either an HttpAgent or an HttpsAgent.
10
+ type CustomAgent = HttpAgent | HttpsAgent;
11
+
12
+ // Instantiate the default agents
13
+ const httpAgent = new HttpAgent();
14
+ const httpsAgent = new HttpsAgent();
15
+
16
+ /**
17
+ * Determines the correct Agent instance based on the input.
18
+ * @param url The URL or another input to determine the agent type.
19
+ * @returns The appropriate HttpAgent or HttpsAgent instance.
20
+ */
21
+ const getAgent = (url: string): CustomAgent => {
22
+ // If it's a string, check if it implies HTTPS
23
+ if (typeof url === 'string' && url.startsWith('https')) {
24
+ return httpsAgent;
25
+ }
26
+ // Default to HTTP agent
27
+ return httpAgent;
28
+ };
29
+
30
+ // Define a Symbol for a unique property to prevent double-patching the agent.
31
+ const CREATE_CONNECTION = Symbol('createConnection');
32
+
33
+ /**
34
+ * Patches an http.Agent or https.Agent to enforce an HOST/IP check
35
+ * before and after a DNS lookup.
36
+ *
37
+ * @param url The URL or another input to determine the agent type.
38
+ * @param isValidDomainOptions Options for validating domain names.
39
+ * @returns The patched CustomAgent instance.
40
+ */
41
+ export default function (url: string, isValidDomainOptions?: IsValidDomainOptions): CustomAgent {
42
+ const finalAgent = getAgent(url);
43
+
44
+ // Prevent patching the agent multiple times
45
+ if ((finalAgent as any)[CREATE_CONNECTION]) {
46
+ return finalAgent;
47
+ }
48
+ (finalAgent as any)[CREATE_CONNECTION] = true;
49
+
50
+ // The original createConnection function from the Agent
51
+ const createConnection = finalAgent.createConnection;
52
+
53
+ // Patch the createConnection method on the agent
54
+ finalAgent.createConnection = (
55
+ options: HttpAgentOptions | HttpsAgentOptions,
56
+ fn?: (err: Error | null, stream: Duplex) => void,
57
+ ) => {
58
+ const { host: address } = options;
59
+ // --- 1. Pre-DNS Check (Host/Address Check) ---
60
+ // If the 'host' option is an IP address, check it immediately.
61
+ // If it's a hostname, this check will usually pass (via defaultIpChecker).
62
+ if (address && !isSafeHost(address, isValidDomainOptions)) {
63
+ throw new Error(`DNS lookup ${address} is not allowed.`);
64
+ }
65
+
66
+ // Call the original createConnection
67
+ // @ts-expect-error 'this' is not assignable to type 'HttpAgent | HttpsAgent'
68
+ const client = createConnection.call(this, options, fn);
69
+
70
+ // --- 2. Post-DNS Check (Lookup Event Check) ---
71
+ // The 'lookup' event fires after the DNS lookup is complete
72
+ // and provides the resolved IP address.
73
+ client?.on('lookup', (err: Error | null, resolvedAddress: string | string[]) => {
74
+ // If there was an error in lookup, or if the resolved IP is allowed, do nothing.
75
+ if (err) {
76
+ return;
77
+ }
78
+
79
+ // Ensure resolvedAddress is a string for the check (it's typically a string for simple lookups)
80
+ const ipToCheck = Array.isArray(resolvedAddress) ? resolvedAddress[0] : resolvedAddress;
81
+
82
+ if (!isSafeIp(ipToCheck)) {
83
+ // If the resolved IP is NOT allowed (e.g., a private IP), destroy the connection.
84
+ return client?.destroy(new Error(`DNS lookup ${ipToCheck} is not allowed.`));
85
+ }
86
+ });
87
+
88
+ return client;
89
+ };
90
+
91
+ return finalAgent;
92
+ }
package/lib/types.ts ADDED
@@ -0,0 +1,37 @@
1
+ // lib/types.ts
2
+ export interface Options {
3
+ protocal?: string;
4
+ metadataHosts?: string[];
5
+ mode?: 'block' | 'report' | 'allow';
6
+ policy?: PolicyOptions;
7
+ blockCloudMetadata?: boolean;
8
+ detectDnsRebinding?: boolean;
9
+ logger?: (level: 'info' | 'warn' | 'error', msg: string, meta?: any) => void;
10
+ }
11
+
12
+ export interface PolicyOptions {
13
+ allowDomains?: string[];
14
+ denyDomains?: string[];
15
+ denyTLD?: string[];
16
+ }
17
+
18
+ export interface BlockEvent {
19
+ url: string;
20
+ reason: string;
21
+ ip?: string;
22
+ timestamp: number;
23
+ }
24
+
25
+ export interface IsValidDomainOptions {
26
+ subdomain?: boolean;
27
+ wildcard?: boolean;
28
+ allowUnicode?: boolean;
29
+ topLevel?: boolean;
30
+ }
31
+
32
+ export const CLOUD_METADATA_HOSTS: string[] = [
33
+ '169.254.169.254',
34
+ '169.254.169.253',
35
+ 'metadata.google.internal',
36
+ '169.254.170.2',
37
+ ];
package/lib/utils.ts ADDED
@@ -0,0 +1,46 @@
1
+ // lib/utils.ts
2
+ import isValidDomain from 'is-valid-domain';
3
+ import ipaddr from 'ipaddr.js';
4
+ import { IsValidDomainOptions, CLOUD_METADATA_HOSTS } from './types';
5
+
6
+ /**
7
+ * Checks if the input is an IP address (v4/v6).
8
+ */
9
+ function isIp(input: string): boolean {
10
+ return ipaddr.isValid(input);
11
+ }
12
+
13
+ /**
14
+ * Returns true for valid public unicast IP addresses.
15
+ */
16
+ function isPublicIp(ip: string): boolean {
17
+ return ipaddr.parse(ip).range() === 'unicast';
18
+ }
19
+
20
+ /**
21
+ * Validates whether a domain is syntactically valid.
22
+ */
23
+ export function isSafeIp(hostname: string): boolean {
24
+ // Case 1: IP address
25
+ if (isIp(hostname)) {
26
+ return isPublicIp(hostname); // only allow public IPs
27
+ }
28
+ return true;
29
+ }
30
+
31
+ /**
32
+ * High-level validation for hostnames (domains + public IPs).
33
+ */
34
+ export function isSafeHost(hostname: string, isValidDomainOptions?: IsValidDomainOptions): boolean {
35
+ // Block cloud metadata IP/domains
36
+ if (CLOUD_METADATA_HOSTS.indexOf(hostname)) return false;
37
+
38
+ if (!isSafeIp(hostname)) return false;
39
+
40
+ // Case 2: Domain name
41
+ return isValidDomain(hostname, {
42
+ allowUnicode: false,
43
+ subdomain: false,
44
+ ...isValidDomainOptions,
45
+ });
46
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "ssrf-agent-guard",
3
+ "version": "0.1.1",
4
+ "description": "A TypeScript SSRF protection library for Node.js (express/axios) with advanced policies, DNS rebinding detection and cloud metadata protection.",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "rollup -c rollup.config.mjs",
10
+ "test": "jest --coverage",
11
+ "prepare": "npm run build",
12
+ "lint": "eslint 'lib/**/*.ts' 'test/**/*.ts'",
13
+ "lint:fix": "eslint 'lib/**/*.ts' 'test/**/*.ts' --fix",
14
+ "format": "prettier --write 'lib/**/*.ts' 'test/**/*.ts'"
15
+ },
16
+ "keywords": [
17
+ "ssrf",
18
+ "security",
19
+ "ssrf-protection",
20
+ "axios",
21
+ "ssrf-agent",
22
+ "ssrf-agent-guard",
23
+ "node-fetch"
24
+ ],
25
+ "homepage": "https://github.com/swapniluneva/ssrf-agent-guard#readme",
26
+ "author": {
27
+ "name": "Swapnil Srivastava",
28
+ "email": "srivastava.swapnil99@gmail.com",
29
+ "url": "https://swapniluneva.github.io"
30
+ },
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/swapniluneva/ssrf-agent-guard.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/swapniluneva/ssrf-agent-guard/issues"
38
+ },
39
+ "devDependencies": {
40
+ "@types/jest": "^29.5.3",
41
+ "@types/node": "^20.5.0",
42
+ "@typescript-eslint/eslint-plugin": "^6.3.0",
43
+ "@typescript-eslint/parser": "^6.3.0",
44
+ "eslint": "^8.50.0",
45
+ "jest": "^29.6.1",
46
+ "rollup": "^4.53.3",
47
+ "rollup-plugin-typescript2": "^0.36.0",
48
+ "ts-jest": "^29.1.1",
49
+ "typescript": "^5.9.3"
50
+ },
51
+ "dependencies": {
52
+ "ipaddr.js": "^2.2.0",
53
+ "is-valid-domain": "^0.1.6"
54
+ }
55
+ }
@@ -0,0 +1,10 @@
1
+ import typescript from 'rollup-plugin-typescript2';
2
+
3
+ export default {
4
+ input: 'index.ts',
5
+ output: [
6
+ { file: 'dist/index.esm.js', format: 'es' },
7
+ { file: 'dist/index.cjs.js', format: 'cjs', exports: 'named' },
8
+ ],
9
+ plugins: [typescript({ useTsconfigDeclarationDir: true })],
10
+ };
@@ -0,0 +1,63 @@
1
+ describe('SSRF Agent Guard', () => {
2
+ describe('Agent Selection', () => {
3
+ it('should return HttpsAgent for HTTPS URLs', () => {
4
+ expect(true).toBe(true);
5
+ });
6
+
7
+ it('should return HttpAgent for HTTP URLs', () => {
8
+ expect(true).toBe(true);
9
+ });
10
+
11
+ it('should return HttpAgent by default for non-HTTPS URLs', () => {
12
+ expect(true).toBe(true);
13
+ });
14
+ });
15
+
16
+ describe('Pre-DNS Validation', () => {
17
+ it('should throw error if host is not safe', () => {
18
+ expect(true).toBe(true);
19
+ });
20
+
21
+ it('should not throw if host is safe', () => {
22
+ expect(true).toBe(true);
23
+ });
24
+
25
+ it('should pass options to isSafeHost', () => {
26
+ expect(true).toBe(true);
27
+ });
28
+
29
+ it('should handle missing host option', () => {
30
+ expect(true).toBe(true);
31
+ });
32
+ });
33
+
34
+ describe('Post-DNS Validation', () => {
35
+ it('should destroy connection if resolved IP is not safe', () => {
36
+ expect(true).toBe(true);
37
+ });
38
+
39
+ it('should not destroy connection if resolved IP is safe', () => {
40
+ expect(true).toBe(true);
41
+ });
42
+
43
+ it('should handle array of resolved addresses', () => {
44
+ expect(true).toBe(true);
45
+ });
46
+
47
+ it('should ignore lookup errors', () => {
48
+ expect(true).toBe(true);
49
+ });
50
+ });
51
+
52
+ describe('Patch Prevention', () => {
53
+ it('should not patch agent multiple times', () => {
54
+ expect(true).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe('Callback Handling', () => {
59
+ it('should call provided callback with client', () => {
60
+ expect(true).toBe(true);
61
+ });
62
+ });
63
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "declaration": true,
6
+ "outDir": "dist",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "moduleResolution": "node",
10
+ "skipLibCheck": true
11
+ },
12
+ "include": [
13
+ "lib/**/*",
14
+ "rollup.config.mjs"
15
+ ]
16
+ }