ssrf-agent-guard 0.1.6 → 0.1.8
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/API.md +896 -0
- package/README.md +31 -13
- package/dist/index.cjs.js +262 -59
- package/dist/index.d.ts +8 -6
- package/dist/index.esm.js +260 -62
- package/dist/lib/types.d.ts +48 -9
- package/dist/lib/utils.d.ts +41 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,10 +6,17 @@
|
|
|
6
6
|
## Features
|
|
7
7
|
|
|
8
8
|
* Block requests to internal/private IPs
|
|
9
|
-
* Detect and block cloud provider metadata endpoints (AWS, GCP, Azure)
|
|
9
|
+
* Detect and block cloud provider metadata endpoints (AWS, GCP, Azure, Oracle, DigitalOcean, Kubernetes)
|
|
10
10
|
* DNS rebinding detection
|
|
11
|
+
* Policy-based domain filtering (allowlists, denylists, TLD blocking)
|
|
12
|
+
* Multiple operation modes (block, report, allow)
|
|
13
|
+
* Custom logging support
|
|
11
14
|
* Fully written in TypeScript with type definitions
|
|
12
15
|
|
|
16
|
+
## Documentation
|
|
17
|
+
|
|
18
|
+
For complete API documentation, see [API.md](./API.md).
|
|
19
|
+
|
|
13
20
|
---
|
|
14
21
|
|
|
15
22
|
## Installation
|
|
@@ -24,20 +31,15 @@ yarn add ssrf-agent-guard
|
|
|
24
31
|
|
|
25
32
|
## Usage
|
|
26
33
|
|
|
27
|
-
`isValidDomainOptions` reference [is-valid-domain](https://github.com/miguelmota/is-valid-domain)
|
|
28
|
-
|
|
29
34
|
### axios
|
|
30
35
|
|
|
31
36
|
```ts
|
|
32
37
|
const ssrfAgentGuard = require('ssrf-agent-guard');
|
|
33
38
|
const url = 'https://127.0.0.1'
|
|
34
|
-
const isValidDomainOptions = {
|
|
35
|
-
subdomain: true,
|
|
36
|
-
wildcard: true
|
|
37
|
-
};
|
|
38
39
|
axios.get(
|
|
39
40
|
url, {
|
|
40
|
-
httpAgent: ssrfAgentGuard(url
|
|
41
|
+
httpAgent: ssrfAgentGuard(url),
|
|
42
|
+
httpsAgent: ssrfAgentGuard(url)
|
|
41
43
|
})
|
|
42
44
|
.then((response) => {
|
|
43
45
|
console.log(`Success`);
|
|
@@ -55,12 +57,8 @@ axios.get(
|
|
|
55
57
|
```ts
|
|
56
58
|
const ssrfAgentGuard = require('ssrf-agent-guard');
|
|
57
59
|
const url = 'https://127.0.0.1'
|
|
58
|
-
const isValidDomainOptions = {
|
|
59
|
-
subdomain: true,
|
|
60
|
-
wildcard: true
|
|
61
|
-
};
|
|
62
60
|
fetch(url, {
|
|
63
|
-
agent: ssrfAgentGuard(url
|
|
61
|
+
agent: ssrfAgentGuard(url)
|
|
64
62
|
})
|
|
65
63
|
.then((response) => {
|
|
66
64
|
console.log(`Success`);
|
|
@@ -70,6 +68,26 @@ fetch(url, {
|
|
|
70
68
|
});
|
|
71
69
|
```
|
|
72
70
|
|
|
71
|
+
### Advanced Configuration
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const ssrfAgentGuard = require('ssrf-agent-guard');
|
|
75
|
+
|
|
76
|
+
const agent = ssrfAgentGuard('https://api.example.com', {
|
|
77
|
+
mode: 'block', // 'block' | 'report' | 'allow'
|
|
78
|
+
blockCloudMetadata: true, // Block AWS/GCP/Azure metadata endpoints
|
|
79
|
+
detectDnsRebinding: true, // Detect DNS rebinding attacks
|
|
80
|
+
policy: {
|
|
81
|
+
allowDomains: ['*.trusted.com'], // Only allow these domains
|
|
82
|
+
denyDomains: ['evil.com'], // Block these domains
|
|
83
|
+
denyTLD: ['local', 'internal'] // Block these TLDs
|
|
84
|
+
},
|
|
85
|
+
logger: (level, msg, meta) => {
|
|
86
|
+
console.log(`[${level}] ${msg}`, meta);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
73
91
|
---
|
|
74
92
|
|
|
75
93
|
## Development
|
package/dist/index.cjs.js
CHANGED
|
@@ -7,12 +7,36 @@ var https = require('https');
|
|
|
7
7
|
var isValidDomain = require('is-valid-domain');
|
|
8
8
|
var ipaddr = require('ipaddr.js');
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// lib/types.ts
|
|
11
|
+
/**
|
|
12
|
+
* Default cloud metadata hosts to block.
|
|
13
|
+
* Includes AWS, GCP, Azure, Oracle Cloud, DigitalOcean, and Kubernetes.
|
|
14
|
+
*/
|
|
15
|
+
const CLOUD_METADATA_HOSTS = new Set([
|
|
16
|
+
// AWS EC2 metadata service
|
|
11
17
|
'169.254.169.254',
|
|
12
18
|
'169.254.169.253',
|
|
19
|
+
// GCP metadata service
|
|
13
20
|
'metadata.google.internal',
|
|
21
|
+
'metadata.goog',
|
|
22
|
+
// Azure IMDS
|
|
23
|
+
'169.254.169.254',
|
|
24
|
+
'168.63.129.16',
|
|
25
|
+
// ECS task metadata (AWS Fargate)
|
|
14
26
|
'169.254.170.2',
|
|
15
|
-
|
|
27
|
+
// Kubernetes metadata
|
|
28
|
+
'kubernetes.default',
|
|
29
|
+
'kubernetes.default.svc',
|
|
30
|
+
'kubernetes.default.svc.cluster.local',
|
|
31
|
+
// Oracle Cloud
|
|
32
|
+
'169.254.169.254',
|
|
33
|
+
// DigitalOcean
|
|
34
|
+
'169.254.169.254',
|
|
35
|
+
// Alibaba Cloud
|
|
36
|
+
'100.100.100.200',
|
|
37
|
+
// Link-local for metadata
|
|
38
|
+
'169.254.0.0',
|
|
39
|
+
]);
|
|
16
40
|
|
|
17
41
|
// lib/utils.ts
|
|
18
42
|
/**
|
|
@@ -27,87 +51,266 @@ function isIp(input) {
|
|
|
27
51
|
function isPublicIp(ip) {
|
|
28
52
|
return ipaddr.parse(ip).range() === 'unicast';
|
|
29
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Extracts the TLD from a hostname.
|
|
56
|
+
* @param hostname The hostname to extract TLD from
|
|
57
|
+
* @returns The TLD or empty string if not found
|
|
58
|
+
*/
|
|
59
|
+
function getTLD(hostname) {
|
|
60
|
+
const parts = hostname.toLowerCase().split('.');
|
|
61
|
+
return parts.length > 0 ? parts[parts.length - 1] : '';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Checks if a hostname matches a domain pattern.
|
|
65
|
+
* Supports exact match and wildcard subdomain matching.
|
|
66
|
+
* @param hostname The hostname to check
|
|
67
|
+
* @param pattern The domain pattern (e.g., 'example.com' or '*.example.com')
|
|
68
|
+
*/
|
|
69
|
+
function matchesDomain(hostname, pattern) {
|
|
70
|
+
const normalizedHost = hostname.toLowerCase();
|
|
71
|
+
const normalizedPattern = pattern.toLowerCase();
|
|
72
|
+
// Exact match
|
|
73
|
+
if (normalizedHost === normalizedPattern) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
// Wildcard match (*.example.com matches sub.example.com)
|
|
77
|
+
if (normalizedPattern.startsWith('*.')) {
|
|
78
|
+
const baseDomain = normalizedPattern.slice(2);
|
|
79
|
+
return normalizedHost.endsWith('.' + baseDomain) || normalizedHost === baseDomain;
|
|
80
|
+
}
|
|
81
|
+
// Subdomain match (example.com matches sub.example.com)
|
|
82
|
+
return normalizedHost.endsWith('.' + normalizedPattern);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Checks if a hostname matches any domain in a list.
|
|
86
|
+
*/
|
|
87
|
+
function matchesAnyDomain(hostname, domains) {
|
|
88
|
+
return domains.some(domain => matchesDomain(hostname, domain));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Validates a host against policy options.
|
|
92
|
+
* @param hostname The hostname to validate
|
|
93
|
+
* @param policy The policy options
|
|
94
|
+
* @returns ValidationResult with safe status and reason if blocked
|
|
95
|
+
*/
|
|
96
|
+
function validatePolicy(hostname, policy) {
|
|
97
|
+
if (!policy) {
|
|
98
|
+
return { safe: true };
|
|
99
|
+
}
|
|
100
|
+
// Check allowDomains first (explicit allowlist takes precedence)
|
|
101
|
+
if (policy.allowDomains && policy.allowDomains.length > 0) {
|
|
102
|
+
if (matchesAnyDomain(hostname, policy.allowDomains)) {
|
|
103
|
+
return { safe: true };
|
|
104
|
+
}
|
|
105
|
+
// If allowDomains is specified but host doesn't match, it's not allowed
|
|
106
|
+
return { safe: false, reason: 'not_allowed_domain' };
|
|
107
|
+
}
|
|
108
|
+
// Check denyDomains
|
|
109
|
+
if (policy.denyDomains && policy.denyDomains.length > 0) {
|
|
110
|
+
if (matchesAnyDomain(hostname, policy.denyDomains)) {
|
|
111
|
+
return { safe: false, reason: 'denied_domain' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Check denyTLD
|
|
115
|
+
if (policy.denyTLD && policy.denyTLD.length > 0) {
|
|
116
|
+
const tld = getTLD(hostname);
|
|
117
|
+
if (policy.denyTLD.map(t => t.toLowerCase()).includes(tld)) {
|
|
118
|
+
return { safe: false, reason: 'denied_tld' };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { safe: true };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Checks if a hostname is a cloud metadata endpoint.
|
|
125
|
+
* @param hostname The hostname to check
|
|
126
|
+
* @param customHosts Additional custom metadata hosts to check
|
|
127
|
+
*/
|
|
128
|
+
function isCloudMetadata(hostname, customHosts) {
|
|
129
|
+
if (CLOUD_METADATA_HOSTS.has(hostname)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (customHosts && customHosts.includes(hostname)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
30
137
|
/**
|
|
31
138
|
* High-level validation for hostnames (domains + public IPs).
|
|
139
|
+
* Returns detailed validation result with reason for blocking.
|
|
140
|
+
*
|
|
141
|
+
* @param hostname The hostname or IP to validate
|
|
142
|
+
* @param options Configuration options including policy and metadata settings
|
|
143
|
+
* @returns ValidationResult with safe status and optional reason
|
|
32
144
|
*/
|
|
33
|
-
function
|
|
145
|
+
function validateHost(hostname, options) {
|
|
146
|
+
const blockCloudMetadata = options?.blockCloudMetadata !== false; // default true
|
|
34
147
|
// Block cloud metadata IP/domains
|
|
35
|
-
if (
|
|
36
|
-
return false;
|
|
148
|
+
if (blockCloudMetadata && isCloudMetadata(hostname, options?.metadataHosts)) {
|
|
149
|
+
return { safe: false, reason: 'cloud_metadata' };
|
|
150
|
+
}
|
|
151
|
+
// Check policy-based rules (only for non-IP hostnames)
|
|
152
|
+
if (!isIp(hostname)) {
|
|
153
|
+
const policyResult = validatePolicy(hostname, options?.policy);
|
|
154
|
+
if (!policyResult.safe) {
|
|
155
|
+
return policyResult;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
37
158
|
// Case 1: IP address
|
|
38
|
-
if (isIp(hostname))
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
})
|
|
159
|
+
if (isIp(hostname)) {
|
|
160
|
+
if (!isPublicIp(hostname)) {
|
|
161
|
+
return { safe: false, reason: 'private_ip' };
|
|
162
|
+
}
|
|
163
|
+
return { safe: true };
|
|
164
|
+
}
|
|
165
|
+
// Case 2: Domain name validation
|
|
166
|
+
if (!isValidDomain(hostname, { allowUnicode: false, subdomain: true })) {
|
|
167
|
+
return { safe: false, reason: 'invalid_domain' };
|
|
168
|
+
}
|
|
169
|
+
return { safe: true };
|
|
46
170
|
}
|
|
47
171
|
|
|
48
|
-
//
|
|
49
|
-
const
|
|
50
|
-
const httpsAgent = new https.Agent();
|
|
172
|
+
// WeakMap to track patched agents without modifying the agent object
|
|
173
|
+
const patchedAgents = new WeakMap();
|
|
51
174
|
/**
|
|
52
|
-
* Determines the correct Agent instance based on the
|
|
53
|
-
* @param url The URL or
|
|
54
|
-
* @
|
|
175
|
+
* Determines the correct Agent instance based on the protocol.
|
|
176
|
+
* @param url The URL or protocol hint to determine the agent type.
|
|
177
|
+
* @param options Optional options that may contain protocol hint.
|
|
178
|
+
* @returns A new HttpAgent or HttpsAgent instance.
|
|
55
179
|
*/
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
if (typeof
|
|
59
|
-
return
|
|
180
|
+
const createAgent = (url, options) => {
|
|
181
|
+
const protocol = options?.protocol || url;
|
|
182
|
+
if (typeof protocol === 'string' && protocol.startsWith('https')) {
|
|
183
|
+
return new https.Agent();
|
|
60
184
|
}
|
|
61
|
-
|
|
62
|
-
return httpAgent;
|
|
185
|
+
return new http.Agent();
|
|
63
186
|
};
|
|
64
|
-
// Define a Symbol for a unique property to prevent double-patching the agent.
|
|
65
|
-
const CREATE_CONNECTION = Symbol('createConnection');
|
|
66
187
|
/**
|
|
67
|
-
*
|
|
68
|
-
|
|
188
|
+
* Creates a BlockEvent for logging.
|
|
189
|
+
*/
|
|
190
|
+
function createBlockEvent(url, reason, ip, hostname) {
|
|
191
|
+
return {
|
|
192
|
+
url,
|
|
193
|
+
reason,
|
|
194
|
+
ip,
|
|
195
|
+
hostname,
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Handles a block action based on mode.
|
|
201
|
+
* @returns true if the request should be blocked, false if allowed
|
|
202
|
+
*/
|
|
203
|
+
function handleBlock(options, url, reason, ip, hostname) {
|
|
204
|
+
const mode = options?.mode || 'block';
|
|
205
|
+
const logger = options?.logger;
|
|
206
|
+
// Create event for logging
|
|
207
|
+
const event = createBlockEvent(url, reason, ip, hostname);
|
|
208
|
+
// Log based on mode
|
|
209
|
+
if (logger) {
|
|
210
|
+
if (mode === 'block') {
|
|
211
|
+
logger('error', `SSRF blocked: ${reason}`, event);
|
|
212
|
+
}
|
|
213
|
+
else if (mode === 'report') {
|
|
214
|
+
logger('warn', `SSRF detected (report mode): ${reason}`, event);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Return whether to actually block
|
|
218
|
+
return mode === 'block';
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Gets a human-readable error message for a block reason.
|
|
222
|
+
*/
|
|
223
|
+
function getErrorMessage(reason, target) {
|
|
224
|
+
switch (reason) {
|
|
225
|
+
case 'private_ip':
|
|
226
|
+
return `Private IP address ${target} is not allowed`;
|
|
227
|
+
case 'cloud_metadata':
|
|
228
|
+
return `Cloud metadata endpoint ${target} is not allowed`;
|
|
229
|
+
case 'invalid_domain':
|
|
230
|
+
return `Invalid domain ${target}`;
|
|
231
|
+
case 'dns_rebinding':
|
|
232
|
+
return `DNS rebinding attack detected for ${target}`;
|
|
233
|
+
case 'denied_domain':
|
|
234
|
+
return `Domain ${target} is denied by policy`;
|
|
235
|
+
case 'denied_tld':
|
|
236
|
+
return `TLD of ${target} is denied by policy`;
|
|
237
|
+
case 'not_allowed_domain':
|
|
238
|
+
return `Domain ${target} is not in the allowed list`;
|
|
239
|
+
default:
|
|
240
|
+
return `Request to ${target} is not allowed`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Patches an http.Agent or https.Agent to enforce HOST/IP checks
|
|
245
|
+
* before and after DNS lookup, with full policy support.
|
|
69
246
|
*
|
|
70
|
-
* @param url The URL or
|
|
71
|
-
* @param
|
|
247
|
+
* @param url The URL or protocol hint to determine the agent type.
|
|
248
|
+
* @param options Configuration options for SSRF protection.
|
|
72
249
|
* @returns The patched CustomAgent instance.
|
|
73
250
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
251
|
+
function ssrfAgentGuard(url, options) {
|
|
252
|
+
// Create a new agent for each call to avoid shared state issues
|
|
253
|
+
const finalAgent = createAgent(url, options);
|
|
254
|
+
// If mode is 'allow', return unpatched agent
|
|
255
|
+
if (options?.mode === 'allow') {
|
|
78
256
|
return finalAgent;
|
|
79
257
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
258
|
+
// Check if already patched (shouldn't happen with new agents, but safety check)
|
|
259
|
+
if (patchedAgents.get(finalAgent)) {
|
|
260
|
+
return finalAgent;
|
|
261
|
+
}
|
|
262
|
+
patchedAgents.set(finalAgent, true);
|
|
263
|
+
// Store original createConnection
|
|
264
|
+
const originalCreateConnection = finalAgent.createConnection;
|
|
265
|
+
// Whether to detect DNS rebinding (default: true)
|
|
266
|
+
const detectDnsRebinding = options?.detectDnsRebinding !== false;
|
|
267
|
+
// Patch createConnection
|
|
268
|
+
finalAgent.createConnection = function (connectionOptions, callback) {
|
|
269
|
+
const hostname = connectionOptions.host || '';
|
|
86
270
|
// --- 1. Pre-DNS Check (Host/Address Check) ---
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
271
|
+
const preCheckResult = validateHost(hostname, options);
|
|
272
|
+
if (!preCheckResult.safe && preCheckResult.reason) {
|
|
273
|
+
const shouldBlock = handleBlock(options, hostname, preCheckResult.reason, undefined, hostname);
|
|
274
|
+
if (shouldBlock) {
|
|
275
|
+
throw new Error(getErrorMessage(preCheckResult.reason, hostname));
|
|
276
|
+
}
|
|
91
277
|
}
|
|
92
278
|
// Call the original createConnection
|
|
93
|
-
const client =
|
|
279
|
+
const client = originalCreateConnection.call(this, connectionOptions, callback);
|
|
94
280
|
// --- 2. Post-DNS Check (Lookup Event Check) ---
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
281
|
+
// Only add listener if DNS rebinding detection is enabled
|
|
282
|
+
if (detectDnsRebinding && client) {
|
|
283
|
+
client.on('lookup', (err, resolvedAddress) => {
|
|
284
|
+
if (err) {
|
|
285
|
+
return; // DNS lookup failed, let it propagate naturally
|
|
286
|
+
}
|
|
287
|
+
// Check all resolved IPs (handle both single IP and array)
|
|
288
|
+
const ipsToCheck = Array.isArray(resolvedAddress) ? resolvedAddress : [resolvedAddress];
|
|
289
|
+
for (const ip of ipsToCheck) {
|
|
290
|
+
if (!ip)
|
|
291
|
+
continue;
|
|
292
|
+
const postCheckResult = validateHost(ip, options);
|
|
293
|
+
if (!postCheckResult.safe && postCheckResult.reason) {
|
|
294
|
+
// For post-DNS check, the reason is DNS rebinding
|
|
295
|
+
const reason = 'dns_rebinding';
|
|
296
|
+
const shouldBlock = handleBlock(options, hostname, reason, ip, hostname);
|
|
297
|
+
if (shouldBlock) {
|
|
298
|
+
client.destroy(new Error(getErrorMessage(reason, `${hostname} -> ${ip}`)));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
107
305
|
return client;
|
|
108
306
|
};
|
|
109
307
|
return finalAgent;
|
|
110
|
-
}
|
|
308
|
+
}
|
|
111
309
|
module.exports = ssrfAgentGuard;
|
|
112
310
|
|
|
113
311
|
exports.default = ssrfAgentGuard;
|
|
312
|
+
exports.getTLD = getTLD;
|
|
313
|
+
exports.isCloudMetadata = isCloudMetadata;
|
|
314
|
+
exports.matchesDomain = matchesDomain;
|
|
315
|
+
exports.validateHost = validateHost;
|
|
316
|
+
exports.validatePolicy = validatePolicy;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { Agent as HttpAgent } from 'http';
|
|
2
2
|
import { Agent as HttpsAgent } from 'https';
|
|
3
|
-
import {
|
|
3
|
+
import { Options } from './lib/types';
|
|
4
|
+
export { Options, PolicyOptions, BlockEvent, BlockReason, ValidationResult } from './lib/types';
|
|
5
|
+
export { validateHost, isCloudMetadata, validatePolicy, matchesDomain, getTLD } from './lib/utils';
|
|
4
6
|
type CustomAgent = HttpAgent | HttpsAgent;
|
|
5
7
|
/**
|
|
6
|
-
* Patches an http.Agent or https.Agent to enforce
|
|
7
|
-
* before and after
|
|
8
|
+
* Patches an http.Agent or https.Agent to enforce HOST/IP checks
|
|
9
|
+
* before and after DNS lookup, with full policy support.
|
|
8
10
|
*
|
|
9
|
-
* @param url The URL or
|
|
10
|
-
* @param
|
|
11
|
+
* @param url The URL or protocol hint to determine the agent type.
|
|
12
|
+
* @param options Configuration options for SSRF protection.
|
|
11
13
|
* @returns The patched CustomAgent instance.
|
|
12
14
|
*/
|
|
13
|
-
declare
|
|
15
|
+
declare function ssrfAgentGuard(url: string, options?: Options): CustomAgent;
|
|
14
16
|
export default ssrfAgentGuard;
|