@stryke/url 0.3.39 → 0.4.0
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/CHANGELOG.md +8 -0
- package/dist/index.cjs +12 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +3 -1
- package/dist/validate.cjs +210 -0
- package/dist/validate.d.cts +67 -0
- package/dist/validate.d.cts.map +1 -0
- package/dist/validate.d.mts +67 -0
- package/dist/validate.d.mts.map +1 -0
- package/dist/validate.mjs +204 -0
- package/dist/validate.mjs.map +1 -0
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog for Stryke - URL
|
|
4
4
|
|
|
5
|
+
## [0.3.39](https://github.com/storm-software/stryke/releases/tag/url%400.3.39) (03/02/2026)
|
|
6
|
+
|
|
7
|
+
### Updated Dependencies
|
|
8
|
+
|
|
9
|
+
- Updated **type-checks** to **v0.5.28**
|
|
10
|
+
- Updated **json** to **v0.11.0**
|
|
11
|
+
- Updated **path** to **v0.26.9**
|
|
12
|
+
|
|
5
13
|
## [0.3.38](https://github.com/storm-software/stryke/releases/tag/url%400.3.38) (03/02/2026)
|
|
6
14
|
|
|
7
15
|
### Updated Dependencies
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
const require_helpers = require('./helpers.cjs');
|
|
1
2
|
const require_storm_url = require('./storm-url.cjs');
|
|
2
3
|
const require_types = require('./types.cjs');
|
|
4
|
+
const require_validate = require('./validate.cjs');
|
|
3
5
|
|
|
6
|
+
exports.BLOCKED_IPV4_RANGES = require_validate.BLOCKED_IPV4_RANGES;
|
|
4
7
|
exports.PROTOCOL_RELATIVE_SYMBOL = require_types.PROTOCOL_RELATIVE_SYMBOL;
|
|
5
|
-
exports.StormURL = require_storm_url.StormURL;
|
|
8
|
+
exports.StormURL = require_storm_url.StormURL;
|
|
9
|
+
exports.formatLocalePath = require_helpers.formatLocalePath;
|
|
10
|
+
exports.ipv4ToInt = require_validate.ipv4ToInt;
|
|
11
|
+
exports.isBlockedHostname = require_validate.isBlockedHostname;
|
|
12
|
+
exports.isBlockedIPAddress = require_validate.isBlockedIPAddress;
|
|
13
|
+
exports.isBlockedIPv4 = require_validate.isBlockedIPv4;
|
|
14
|
+
exports.isValidURL = require_helpers.isValidURL;
|
|
15
|
+
exports.validateUrl = require_validate.validateUrl;
|
|
16
|
+
exports.validateUrlForPreview = require_validate.validateUrlForPreview;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatLocalePath, isValidURL } from "./helpers.cjs";
|
|
1
2
|
import { PROTOCOL_RELATIVE_SYMBOL, StormURLInterface, StormURLOptions } from "./types.cjs";
|
|
2
3
|
import { StormURL } from "./storm-url.cjs";
|
|
3
|
-
|
|
4
|
+
import { BLOCKED_IPV4_RANGES, URLValidationResult, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, validateUrl, validateUrlForPreview } from "./validate.cjs";
|
|
5
|
+
export { BLOCKED_IPV4_RANGES, PROTOCOL_RELATIVE_SYMBOL, StormURL, StormURLInterface, StormURLOptions, URLValidationResult, formatLocalePath, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, isValidURL, validateUrl, validateUrlForPreview };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatLocalePath, isValidURL } from "./helpers.mjs";
|
|
1
2
|
import { PROTOCOL_RELATIVE_SYMBOL, StormURLInterface, StormURLOptions } from "./types.mjs";
|
|
2
3
|
import { StormURL } from "./storm-url.mjs";
|
|
3
|
-
|
|
4
|
+
import { BLOCKED_IPV4_RANGES, URLValidationResult, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, validateUrl, validateUrlForPreview } from "./validate.mjs";
|
|
5
|
+
export { BLOCKED_IPV4_RANGES, PROTOCOL_RELATIVE_SYMBOL, StormURL, StormURLInterface, StormURLOptions, URLValidationResult, formatLocalePath, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, isValidURL, validateUrl, validateUrlForPreview };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { formatLocalePath, isValidURL } from "./helpers.mjs";
|
|
1
2
|
import { StormURL } from "./storm-url.mjs";
|
|
2
3
|
import { PROTOCOL_RELATIVE_SYMBOL } from "./types.mjs";
|
|
4
|
+
import { BLOCKED_IPV4_RANGES, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, validateUrl, validateUrlForPreview } from "./validate.mjs";
|
|
3
5
|
|
|
4
|
-
export { PROTOCOL_RELATIVE_SYMBOL, StormURL };
|
|
6
|
+
export { BLOCKED_IPV4_RANGES, PROTOCOL_RELATIVE_SYMBOL, StormURL, formatLocalePath, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, isValidURL, validateUrl, validateUrlForPreview };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/validate.ts
|
|
3
|
+
/**
|
|
4
|
+
* List of blocked IPv4 ranges for Server-Side Request Forgery (SSRF) protection
|
|
5
|
+
*/
|
|
6
|
+
const BLOCKED_IPV4_RANGES = [
|
|
7
|
+
{
|
|
8
|
+
start: "127.0.0.0",
|
|
9
|
+
end: "127.255.255.255"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
start: "10.0.0.0",
|
|
13
|
+
end: "10.255.255.255"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
start: "172.16.0.0",
|
|
17
|
+
end: "172.31.255.255"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
start: "192.168.0.0",
|
|
21
|
+
end: "192.168.255.255"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
start: "169.254.0.0",
|
|
25
|
+
end: "169.254.255.255"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
start: "169.254.169.254",
|
|
29
|
+
end: "169.254.169.254"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
start: "255.255.255.255",
|
|
33
|
+
end: "255.255.255.255"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
start: "0.0.0.0",
|
|
37
|
+
end: "0.255.255.255"
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* List of blocked hostnames for Server-Side Request Forgery (SSRF) protection
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* This includes common internal hostnames and patterns that should not be accessed
|
|
45
|
+
*/
|
|
46
|
+
const BLOCKED_HOSTNAMES = [
|
|
47
|
+
"localhost",
|
|
48
|
+
"localhost.localdomain",
|
|
49
|
+
"ip6-localhost",
|
|
50
|
+
"ip6-loopback",
|
|
51
|
+
"kubernetes.default",
|
|
52
|
+
"kubernetes.default.svc",
|
|
53
|
+
"kubernetes.default.svc.cluster.local",
|
|
54
|
+
"internal",
|
|
55
|
+
"metadata",
|
|
56
|
+
"metadata.google.internal"
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Convert an IPv4 address to a 32-bit integer for range comparison
|
|
60
|
+
*
|
|
61
|
+
* @param ip - The IPv4 address as a string
|
|
62
|
+
* @returns The 32-bit integer representation of the IPv4 address, or -1 if invalid
|
|
63
|
+
*/
|
|
64
|
+
function ipv4ToInt(ip) {
|
|
65
|
+
const parts = ip.split(".").map(Number);
|
|
66
|
+
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255) || !parts[0] || !parts[1] || !parts[2] || !parts[3]) return -1;
|
|
67
|
+
return (parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3]) >>> 0;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if an IPv4 address is in a blocked range for Server-Side Request Forgery (SSRF) protection
|
|
71
|
+
*
|
|
72
|
+
* @param ip - The IPv4 address as a string
|
|
73
|
+
* @returns True if the IP is in a blocked range, false otherwise
|
|
74
|
+
*/
|
|
75
|
+
function isBlockedIPv4(ip) {
|
|
76
|
+
const ipInt = ipv4ToInt(ip);
|
|
77
|
+
if (ipInt === -1) return false;
|
|
78
|
+
for (const range of BLOCKED_IPV4_RANGES) {
|
|
79
|
+
const startInt = ipv4ToInt(range.start);
|
|
80
|
+
const endInt = ipv4ToInt(range.end);
|
|
81
|
+
if (ipInt >= startInt && ipInt <= endInt) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if an IPv6 address is a blocked address for Server-Side Request Forgery (SSRF) protection
|
|
87
|
+
*
|
|
88
|
+
* @param ip - The IPv6 address as a string
|
|
89
|
+
* @returns True if the IP is blocked, false otherwise
|
|
90
|
+
*/
|
|
91
|
+
function isBlockedIPv6(ip) {
|
|
92
|
+
const normalized = ip.toLowerCase();
|
|
93
|
+
if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") return true;
|
|
94
|
+
if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") return true;
|
|
95
|
+
const ipv4Mapped = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
96
|
+
if (ipv4Mapped && ipv4Mapped[1]) return isBlockedIPv4(ipv4Mapped[1]);
|
|
97
|
+
if (normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb")) return true;
|
|
98
|
+
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a hostname is blocked for Server-Side Request Forgery (SSRF) protection by comparing against a list of blocked hostnames and patterns. This includes exact matches, subdomain checks, and common internal domain patterns.
|
|
103
|
+
*
|
|
104
|
+
* @param hostname - The hostname to check
|
|
105
|
+
* @returns True if the hostname is blocked, false otherwise
|
|
106
|
+
*/
|
|
107
|
+
function isBlockedHostname(hostname) {
|
|
108
|
+
const lower = hostname.toLowerCase();
|
|
109
|
+
if (BLOCKED_HOSTNAMES.includes(lower)) return true;
|
|
110
|
+
for (const blocked of BLOCKED_HOSTNAMES) if (lower.endsWith(`.${blocked}`)) return true;
|
|
111
|
+
if (lower.endsWith(".local")) return true;
|
|
112
|
+
if (lower.endsWith(".internal")) return true;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if a hostname appears to be an IP address and if so, whether it's blocked for Server-Side Request Forgery (SSRF) protection
|
|
117
|
+
*
|
|
118
|
+
* @param hostname - The hostname to check
|
|
119
|
+
* @returns True if the hostname is a blocked IP address, false otherwise
|
|
120
|
+
*/
|
|
121
|
+
function isBlockedIPAddress(hostname) {
|
|
122
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return isBlockedIPv4(hostname);
|
|
123
|
+
const ipv6 = hostname.replace(/^\[|\]$/g, "");
|
|
124
|
+
if (ipv6.includes(":")) return isBlockedIPv6(ipv6);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate a URL for use in general contexts (e.g. fetching data, making API calls) with Server-Side Request Forgery (SSRF) protection. This function checks if the URL is well-formed, uses allowed protocols (http/https), and ensures that the hostname does not resolve to internal IP addresses or hostnames. It also checks for non-standard ports that might indicate internal services.
|
|
129
|
+
*
|
|
130
|
+
* @param url - The URL to validate
|
|
131
|
+
* @returns A URLValidationResult object indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid
|
|
132
|
+
*/
|
|
133
|
+
function validateUrl(url) {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = new URL(url);
|
|
136
|
+
if (!["http:", "https:"].includes(parsed.protocol)) return {
|
|
137
|
+
valid: false,
|
|
138
|
+
error: "Only HTTP and HTTPS protocols are allowed"
|
|
139
|
+
};
|
|
140
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
141
|
+
if (isBlockedHostname(hostname)) return {
|
|
142
|
+
valid: false,
|
|
143
|
+
error: "Access to internal hostnames is not allowed"
|
|
144
|
+
};
|
|
145
|
+
if (isBlockedIPAddress(hostname)) return {
|
|
146
|
+
valid: false,
|
|
147
|
+
error: "Access to internal IP addresses is not allowed"
|
|
148
|
+
};
|
|
149
|
+
const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
|
|
150
|
+
if ([
|
|
151
|
+
22,
|
|
152
|
+
23,
|
|
153
|
+
25,
|
|
154
|
+
53,
|
|
155
|
+
135,
|
|
156
|
+
139,
|
|
157
|
+
445,
|
|
158
|
+
1433,
|
|
159
|
+
1521,
|
|
160
|
+
3306,
|
|
161
|
+
3389,
|
|
162
|
+
5432,
|
|
163
|
+
5900,
|
|
164
|
+
6379,
|
|
165
|
+
9200,
|
|
166
|
+
27017
|
|
167
|
+
].includes(port)) return {
|
|
168
|
+
valid: false,
|
|
169
|
+
error: `Access to port ${port} is not allowed`
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
valid: true,
|
|
173
|
+
sanitizedUrl: parsed.toString()
|
|
174
|
+
};
|
|
175
|
+
} catch {
|
|
176
|
+
return {
|
|
177
|
+
valid: false,
|
|
178
|
+
error: "Invalid URL format"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Validate a URL for use in URL preview.
|
|
184
|
+
*
|
|
185
|
+
* @remarks
|
|
186
|
+
* These validations are more restrictive than general URL validations. For URL previews, we want to be extra cautious to prevent any potential SSRF vulnerabilities, so we enforce stricter rules on allowed ports and ensure that the URL is well-formed and does not point to internal resources. This function builds upon the general `validateUrl` function and adds additional checks specific to URL previews, such as blocking non-standard HTTP ports that are commonly used for internal services. By using this function for URL previews, we can help ensure that the URLs being previewed are safe and do not pose a security risk to the application or its users.
|
|
187
|
+
*
|
|
188
|
+
* @param url - The URL to validate for preview
|
|
189
|
+
* @returns A URLValidationResult object indicating whether the URL is valid for preview, any error message if invalid, and a sanitized version of the URL if valid
|
|
190
|
+
*/
|
|
191
|
+
function validateUrlForPreview(url) {
|
|
192
|
+
const result = validateUrl(url);
|
|
193
|
+
if (!result.valid) return result;
|
|
194
|
+
const parsed = new URL(url);
|
|
195
|
+
const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
|
|
196
|
+
if (port !== 80 && port !== 443 && port !== 8080 && port !== 8443) return {
|
|
197
|
+
valid: false,
|
|
198
|
+
error: "Only standard HTTP ports (80, 443, 8080, 8443) are allowed for URL preview"
|
|
199
|
+
};
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
exports.BLOCKED_IPV4_RANGES = BLOCKED_IPV4_RANGES;
|
|
205
|
+
exports.ipv4ToInt = ipv4ToInt;
|
|
206
|
+
exports.isBlockedHostname = isBlockedHostname;
|
|
207
|
+
exports.isBlockedIPAddress = isBlockedIPAddress;
|
|
208
|
+
exports.isBlockedIPv4 = isBlockedIPv4;
|
|
209
|
+
exports.validateUrl = validateUrl;
|
|
210
|
+
exports.validateUrlForPreview = validateUrlForPreview;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/validate.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* List of blocked IPv4 ranges for Server-Side Request Forgery (SSRF) protection
|
|
4
|
+
*/
|
|
5
|
+
declare const BLOCKED_IPV4_RANGES: {
|
|
6
|
+
start: string;
|
|
7
|
+
end: string;
|
|
8
|
+
}[];
|
|
9
|
+
/**
|
|
10
|
+
* Convert an IPv4 address to a 32-bit integer for range comparison
|
|
11
|
+
*
|
|
12
|
+
* @param ip - The IPv4 address as a string
|
|
13
|
+
* @returns The 32-bit integer representation of the IPv4 address, or -1 if invalid
|
|
14
|
+
*/
|
|
15
|
+
declare function ipv4ToInt(ip: string): number;
|
|
16
|
+
/**
|
|
17
|
+
* Check if an IPv4 address is in a blocked range for Server-Side Request Forgery (SSRF) protection
|
|
18
|
+
*
|
|
19
|
+
* @param ip - The IPv4 address as a string
|
|
20
|
+
* @returns True if the IP is in a blocked range, false otherwise
|
|
21
|
+
*/
|
|
22
|
+
declare function isBlockedIPv4(ip: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a hostname is blocked for Server-Side Request Forgery (SSRF) protection by comparing against a list of blocked hostnames and patterns. This includes exact matches, subdomain checks, and common internal domain patterns.
|
|
25
|
+
*
|
|
26
|
+
* @param hostname - The hostname to check
|
|
27
|
+
* @returns True if the hostname is blocked, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
declare function isBlockedHostname(hostname: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a hostname appears to be an IP address and if so, whether it's blocked for Server-Side Request Forgery (SSRF) protection
|
|
32
|
+
*
|
|
33
|
+
* @param hostname - The hostname to check
|
|
34
|
+
* @returns True if the hostname is a blocked IP address, false otherwise
|
|
35
|
+
*/
|
|
36
|
+
declare function isBlockedIPAddress(hostname: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* A result object for URL validation, indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid. This structure allows for clear communication of validation results and can be easily extended in the future if needed.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* The `valid` property indicates if the URL passed validation. The `error` property provides a message explaining why the URL is invalid, if applicable. The `sanitizedUrl` property contains a cleaned-up version of the URL that can be safely used if the original URL is valid.
|
|
42
|
+
*/
|
|
43
|
+
interface URLValidationResult {
|
|
44
|
+
valid: boolean;
|
|
45
|
+
error?: string;
|
|
46
|
+
sanitizedUrl?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validate a URL for use in general contexts (e.g. fetching data, making API calls) with Server-Side Request Forgery (SSRF) protection. This function checks if the URL is well-formed, uses allowed protocols (http/https), and ensures that the hostname does not resolve to internal IP addresses or hostnames. It also checks for non-standard ports that might indicate internal services.
|
|
50
|
+
*
|
|
51
|
+
* @param url - The URL to validate
|
|
52
|
+
* @returns A URLValidationResult object indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid
|
|
53
|
+
*/
|
|
54
|
+
declare function validateUrl(url: string): URLValidationResult;
|
|
55
|
+
/**
|
|
56
|
+
* Validate a URL for use in URL preview.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* These validations are more restrictive than general URL validations. For URL previews, we want to be extra cautious to prevent any potential SSRF vulnerabilities, so we enforce stricter rules on allowed ports and ensure that the URL is well-formed and does not point to internal resources. This function builds upon the general `validateUrl` function and adds additional checks specific to URL previews, such as blocking non-standard HTTP ports that are commonly used for internal services. By using this function for URL previews, we can help ensure that the URLs being previewed are safe and do not pose a security risk to the application or its users.
|
|
60
|
+
*
|
|
61
|
+
* @param url - The URL to validate for preview
|
|
62
|
+
* @returns A URLValidationResult object indicating whether the URL is valid for preview, any error message if invalid, and a sanitized version of the URL if valid
|
|
63
|
+
*/
|
|
64
|
+
declare function validateUrlForPreview(url: string): URLValidationResult;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { BLOCKED_IPV4_RANGES, URLValidationResult, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, validateUrl, validateUrlForPreview };
|
|
67
|
+
//# sourceMappingURL=validate.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.cts","names":[],"sources":["../src/validate.ts"],"sourcesContent":[],"mappings":";;AAqBA;AA4CA;AAuBgB,cAnEH,mBAmEgB,EAAA;EAiEb,KAAA,EAAA,MAAA;EAkCA,GAAA,EAAA,MAAA;AAqBhB,CAAA,EAAA;AAYA;AA4EA;;;;;iBAvOgB,SAAA;;;;;;;iBAuBA,aAAA;;;;;;;iBAiEA,iBAAA;;;;;;;iBAkCA,kBAAA;;;;;;;UAqBC,mBAAA;;;;;;;;;;;iBAYD,WAAA,eAA0B;;;;;;;;;;iBA4E1B,qBAAA,eAAoC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/validate.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* List of blocked IPv4 ranges for Server-Side Request Forgery (SSRF) protection
|
|
4
|
+
*/
|
|
5
|
+
declare const BLOCKED_IPV4_RANGES: {
|
|
6
|
+
start: string;
|
|
7
|
+
end: string;
|
|
8
|
+
}[];
|
|
9
|
+
/**
|
|
10
|
+
* Convert an IPv4 address to a 32-bit integer for range comparison
|
|
11
|
+
*
|
|
12
|
+
* @param ip - The IPv4 address as a string
|
|
13
|
+
* @returns The 32-bit integer representation of the IPv4 address, or -1 if invalid
|
|
14
|
+
*/
|
|
15
|
+
declare function ipv4ToInt(ip: string): number;
|
|
16
|
+
/**
|
|
17
|
+
* Check if an IPv4 address is in a blocked range for Server-Side Request Forgery (SSRF) protection
|
|
18
|
+
*
|
|
19
|
+
* @param ip - The IPv4 address as a string
|
|
20
|
+
* @returns True if the IP is in a blocked range, false otherwise
|
|
21
|
+
*/
|
|
22
|
+
declare function isBlockedIPv4(ip: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a hostname is blocked for Server-Side Request Forgery (SSRF) protection by comparing against a list of blocked hostnames and patterns. This includes exact matches, subdomain checks, and common internal domain patterns.
|
|
25
|
+
*
|
|
26
|
+
* @param hostname - The hostname to check
|
|
27
|
+
* @returns True if the hostname is blocked, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
declare function isBlockedHostname(hostname: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a hostname appears to be an IP address and if so, whether it's blocked for Server-Side Request Forgery (SSRF) protection
|
|
32
|
+
*
|
|
33
|
+
* @param hostname - The hostname to check
|
|
34
|
+
* @returns True if the hostname is a blocked IP address, false otherwise
|
|
35
|
+
*/
|
|
36
|
+
declare function isBlockedIPAddress(hostname: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* A result object for URL validation, indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid. This structure allows for clear communication of validation results and can be easily extended in the future if needed.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* The `valid` property indicates if the URL passed validation. The `error` property provides a message explaining why the URL is invalid, if applicable. The `sanitizedUrl` property contains a cleaned-up version of the URL that can be safely used if the original URL is valid.
|
|
42
|
+
*/
|
|
43
|
+
interface URLValidationResult {
|
|
44
|
+
valid: boolean;
|
|
45
|
+
error?: string;
|
|
46
|
+
sanitizedUrl?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validate a URL for use in general contexts (e.g. fetching data, making API calls) with Server-Side Request Forgery (SSRF) protection. This function checks if the URL is well-formed, uses allowed protocols (http/https), and ensures that the hostname does not resolve to internal IP addresses or hostnames. It also checks for non-standard ports that might indicate internal services.
|
|
50
|
+
*
|
|
51
|
+
* @param url - The URL to validate
|
|
52
|
+
* @returns A URLValidationResult object indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid
|
|
53
|
+
*/
|
|
54
|
+
declare function validateUrl(url: string): URLValidationResult;
|
|
55
|
+
/**
|
|
56
|
+
* Validate a URL for use in URL preview.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* These validations are more restrictive than general URL validations. For URL previews, we want to be extra cautious to prevent any potential SSRF vulnerabilities, so we enforce stricter rules on allowed ports and ensure that the URL is well-formed and does not point to internal resources. This function builds upon the general `validateUrl` function and adds additional checks specific to URL previews, such as blocking non-standard HTTP ports that are commonly used for internal services. By using this function for URL previews, we can help ensure that the URLs being previewed are safe and do not pose a security risk to the application or its users.
|
|
60
|
+
*
|
|
61
|
+
* @param url - The URL to validate for preview
|
|
62
|
+
* @returns A URLValidationResult object indicating whether the URL is valid for preview, any error message if invalid, and a sanitized version of the URL if valid
|
|
63
|
+
*/
|
|
64
|
+
declare function validateUrlForPreview(url: string): URLValidationResult;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { BLOCKED_IPV4_RANGES, URLValidationResult, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, validateUrl, validateUrlForPreview };
|
|
67
|
+
//# sourceMappingURL=validate.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.mts","names":[],"sources":["../src/validate.ts"],"sourcesContent":[],"mappings":";;AAqBA;AA4CA;AAuBgB,cAnEH,mBAmEgB,EAAA;EAiEb,KAAA,EAAA,MAAA;EAkCA,GAAA,EAAA,MAAA;AAqBhB,CAAA,EAAA;AAYA;AA4EA;;;;;iBAvOgB,SAAA;;;;;;;iBAuBA,aAAA;;;;;;;iBAiEA,iBAAA;;;;;;;iBAkCA,kBAAA;;;;;;;UAqBC,mBAAA;;;;;;;;;;;iBAYD,WAAA,eAA0B;;;;;;;;;;iBA4E1B,qBAAA,eAAoC"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
//#region src/validate.ts
|
|
2
|
+
/**
|
|
3
|
+
* List of blocked IPv4 ranges for Server-Side Request Forgery (SSRF) protection
|
|
4
|
+
*/
|
|
5
|
+
const BLOCKED_IPV4_RANGES = [
|
|
6
|
+
{
|
|
7
|
+
start: "127.0.0.0",
|
|
8
|
+
end: "127.255.255.255"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
start: "10.0.0.0",
|
|
12
|
+
end: "10.255.255.255"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
start: "172.16.0.0",
|
|
16
|
+
end: "172.31.255.255"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
start: "192.168.0.0",
|
|
20
|
+
end: "192.168.255.255"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
start: "169.254.0.0",
|
|
24
|
+
end: "169.254.255.255"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
start: "169.254.169.254",
|
|
28
|
+
end: "169.254.169.254"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
start: "255.255.255.255",
|
|
32
|
+
end: "255.255.255.255"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
start: "0.0.0.0",
|
|
36
|
+
end: "0.255.255.255"
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* List of blocked hostnames for Server-Side Request Forgery (SSRF) protection
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* This includes common internal hostnames and patterns that should not be accessed
|
|
44
|
+
*/
|
|
45
|
+
const BLOCKED_HOSTNAMES = [
|
|
46
|
+
"localhost",
|
|
47
|
+
"localhost.localdomain",
|
|
48
|
+
"ip6-localhost",
|
|
49
|
+
"ip6-loopback",
|
|
50
|
+
"kubernetes.default",
|
|
51
|
+
"kubernetes.default.svc",
|
|
52
|
+
"kubernetes.default.svc.cluster.local",
|
|
53
|
+
"internal",
|
|
54
|
+
"metadata",
|
|
55
|
+
"metadata.google.internal"
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Convert an IPv4 address to a 32-bit integer for range comparison
|
|
59
|
+
*
|
|
60
|
+
* @param ip - The IPv4 address as a string
|
|
61
|
+
* @returns The 32-bit integer representation of the IPv4 address, or -1 if invalid
|
|
62
|
+
*/
|
|
63
|
+
function ipv4ToInt(ip) {
|
|
64
|
+
const parts = ip.split(".").map(Number);
|
|
65
|
+
if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255) || !parts[0] || !parts[1] || !parts[2] || !parts[3]) return -1;
|
|
66
|
+
return (parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3]) >>> 0;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if an IPv4 address is in a blocked range for Server-Side Request Forgery (SSRF) protection
|
|
70
|
+
*
|
|
71
|
+
* @param ip - The IPv4 address as a string
|
|
72
|
+
* @returns True if the IP is in a blocked range, false otherwise
|
|
73
|
+
*/
|
|
74
|
+
function isBlockedIPv4(ip) {
|
|
75
|
+
const ipInt = ipv4ToInt(ip);
|
|
76
|
+
if (ipInt === -1) return false;
|
|
77
|
+
for (const range of BLOCKED_IPV4_RANGES) {
|
|
78
|
+
const startInt = ipv4ToInt(range.start);
|
|
79
|
+
const endInt = ipv4ToInt(range.end);
|
|
80
|
+
if (ipInt >= startInt && ipInt <= endInt) return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if an IPv6 address is a blocked address for Server-Side Request Forgery (SSRF) protection
|
|
86
|
+
*
|
|
87
|
+
* @param ip - The IPv6 address as a string
|
|
88
|
+
* @returns True if the IP is blocked, false otherwise
|
|
89
|
+
*/
|
|
90
|
+
function isBlockedIPv6(ip) {
|
|
91
|
+
const normalized = ip.toLowerCase();
|
|
92
|
+
if (normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") return true;
|
|
93
|
+
if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") return true;
|
|
94
|
+
const ipv4Mapped = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
95
|
+
if (ipv4Mapped && ipv4Mapped[1]) return isBlockedIPv4(ipv4Mapped[1]);
|
|
96
|
+
if (normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb")) return true;
|
|
97
|
+
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if a hostname is blocked for Server-Side Request Forgery (SSRF) protection by comparing against a list of blocked hostnames and patterns. This includes exact matches, subdomain checks, and common internal domain patterns.
|
|
102
|
+
*
|
|
103
|
+
* @param hostname - The hostname to check
|
|
104
|
+
* @returns True if the hostname is blocked, false otherwise
|
|
105
|
+
*/
|
|
106
|
+
function isBlockedHostname(hostname) {
|
|
107
|
+
const lower = hostname.toLowerCase();
|
|
108
|
+
if (BLOCKED_HOSTNAMES.includes(lower)) return true;
|
|
109
|
+
for (const blocked of BLOCKED_HOSTNAMES) if (lower.endsWith(`.${blocked}`)) return true;
|
|
110
|
+
if (lower.endsWith(".local")) return true;
|
|
111
|
+
if (lower.endsWith(".internal")) return true;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if a hostname appears to be an IP address and if so, whether it's blocked for Server-Side Request Forgery (SSRF) protection
|
|
116
|
+
*
|
|
117
|
+
* @param hostname - The hostname to check
|
|
118
|
+
* @returns True if the hostname is a blocked IP address, false otherwise
|
|
119
|
+
*/
|
|
120
|
+
function isBlockedIPAddress(hostname) {
|
|
121
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) return isBlockedIPv4(hostname);
|
|
122
|
+
const ipv6 = hostname.replace(/^\[|\]$/g, "");
|
|
123
|
+
if (ipv6.includes(":")) return isBlockedIPv6(ipv6);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Validate a URL for use in general contexts (e.g. fetching data, making API calls) with Server-Side Request Forgery (SSRF) protection. This function checks if the URL is well-formed, uses allowed protocols (http/https), and ensures that the hostname does not resolve to internal IP addresses or hostnames. It also checks for non-standard ports that might indicate internal services.
|
|
128
|
+
*
|
|
129
|
+
* @param url - The URL to validate
|
|
130
|
+
* @returns A URLValidationResult object indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid
|
|
131
|
+
*/
|
|
132
|
+
function validateUrl(url) {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = new URL(url);
|
|
135
|
+
if (!["http:", "https:"].includes(parsed.protocol)) return {
|
|
136
|
+
valid: false,
|
|
137
|
+
error: "Only HTTP and HTTPS protocols are allowed"
|
|
138
|
+
};
|
|
139
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
140
|
+
if (isBlockedHostname(hostname)) return {
|
|
141
|
+
valid: false,
|
|
142
|
+
error: "Access to internal hostnames is not allowed"
|
|
143
|
+
};
|
|
144
|
+
if (isBlockedIPAddress(hostname)) return {
|
|
145
|
+
valid: false,
|
|
146
|
+
error: "Access to internal IP addresses is not allowed"
|
|
147
|
+
};
|
|
148
|
+
const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
|
|
149
|
+
if ([
|
|
150
|
+
22,
|
|
151
|
+
23,
|
|
152
|
+
25,
|
|
153
|
+
53,
|
|
154
|
+
135,
|
|
155
|
+
139,
|
|
156
|
+
445,
|
|
157
|
+
1433,
|
|
158
|
+
1521,
|
|
159
|
+
3306,
|
|
160
|
+
3389,
|
|
161
|
+
5432,
|
|
162
|
+
5900,
|
|
163
|
+
6379,
|
|
164
|
+
9200,
|
|
165
|
+
27017
|
|
166
|
+
].includes(port)) return {
|
|
167
|
+
valid: false,
|
|
168
|
+
error: `Access to port ${port} is not allowed`
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
valid: true,
|
|
172
|
+
sanitizedUrl: parsed.toString()
|
|
173
|
+
};
|
|
174
|
+
} catch {
|
|
175
|
+
return {
|
|
176
|
+
valid: false,
|
|
177
|
+
error: "Invalid URL format"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Validate a URL for use in URL preview.
|
|
183
|
+
*
|
|
184
|
+
* @remarks
|
|
185
|
+
* These validations are more restrictive than general URL validations. For URL previews, we want to be extra cautious to prevent any potential SSRF vulnerabilities, so we enforce stricter rules on allowed ports and ensure that the URL is well-formed and does not point to internal resources. This function builds upon the general `validateUrl` function and adds additional checks specific to URL previews, such as blocking non-standard HTTP ports that are commonly used for internal services. By using this function for URL previews, we can help ensure that the URLs being previewed are safe and do not pose a security risk to the application or its users.
|
|
186
|
+
*
|
|
187
|
+
* @param url - The URL to validate for preview
|
|
188
|
+
* @returns A URLValidationResult object indicating whether the URL is valid for preview, any error message if invalid, and a sanitized version of the URL if valid
|
|
189
|
+
*/
|
|
190
|
+
function validateUrlForPreview(url) {
|
|
191
|
+
const result = validateUrl(url);
|
|
192
|
+
if (!result.valid) return result;
|
|
193
|
+
const parsed = new URL(url);
|
|
194
|
+
const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
|
|
195
|
+
if (port !== 80 && port !== 443 && port !== 8080 && port !== 8443) return {
|
|
196
|
+
valid: false,
|
|
197
|
+
error: "Only standard HTTP ports (80, 443, 8080, 8443) are allowed for URL preview"
|
|
198
|
+
};
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
export { BLOCKED_IPV4_RANGES, ipv4ToInt, isBlockedHostname, isBlockedIPAddress, isBlockedIPv4, validateUrl, validateUrlForPreview };
|
|
204
|
+
//# sourceMappingURL=validate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.mjs","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/* -------------------------------------------------------------------\n\n ⚡ Storm Software - Stryke\n\n This code was released as part of the Stryke project. Stryke\n is maintained by Storm Software under the Apache-2.0 license, and is\n free for commercial and private use. For more information, please visit\n our licensing page at https://stormsoftware.com/licenses/projects/stryke.\n\n Website: https://stormsoftware.com\n Repository: https://github.com/storm-software/stryke\n Documentation: https://docs.stormsoftware.com/projects/stryke\n Contact: https://stormsoftware.com/contact\n\n SPDX-License-Identifier: Apache-2.0\n\n ------------------------------------------------------------------- */\n\n/**\n * List of blocked IPv4 ranges for Server-Side Request Forgery (SSRF) protection\n */\nexport const BLOCKED_IPV4_RANGES = [\n // Loopback\n { start: \"127.0.0.0\", end: \"127.255.255.255\" },\n // Private networks (RFC 1918)\n { start: \"10.0.0.0\", end: \"10.255.255.255\" },\n { start: \"172.16.0.0\", end: \"172.31.255.255\" },\n { start: \"192.168.0.0\", end: \"192.168.255.255\" },\n // Link-local\n { start: \"169.254.0.0\", end: \"169.254.255.255\" },\n // AWS metadata service\n { start: \"169.254.169.254\", end: \"169.254.169.254\" },\n // Broadcast\n { start: \"255.255.255.255\", end: \"255.255.255.255\" },\n // Current network\n { start: \"0.0.0.0\", end: \"0.255.255.255\" }\n];\n\n/**\n * List of blocked hostnames for Server-Side Request Forgery (SSRF) protection\n *\n * @remarks\n * This includes common internal hostnames and patterns that should not be accessed\n */\nconst BLOCKED_HOSTNAMES = [\n \"localhost\",\n \"localhost.localdomain\",\n \"ip6-localhost\",\n \"ip6-loopback\",\n // Kubernetes internal\n \"kubernetes.default\",\n \"kubernetes.default.svc\",\n \"kubernetes.default.svc.cluster.local\",\n // Common internal service names\n \"internal\",\n \"metadata\",\n \"metadata.google.internal\"\n];\n\n/**\n * Convert an IPv4 address to a 32-bit integer for range comparison\n *\n * @param ip - The IPv4 address as a string\n * @returns The 32-bit integer representation of the IPv4 address, or -1 if invalid\n */\nexport function ipv4ToInt(ip: string): number {\n const parts = ip.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some(p => Number.isNaN(p) || p < 0 || p > 255) ||\n !parts[0] ||\n !parts[1] ||\n !parts[2] ||\n !parts[3]\n ) {\n return -1;\n }\n return (\n ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0\n );\n}\n\n/**\n * Check if an IPv4 address is in a blocked range for Server-Side Request Forgery (SSRF) protection\n *\n * @param ip - The IPv4 address as a string\n * @returns True if the IP is in a blocked range, false otherwise\n */\nexport function isBlockedIPv4(ip: string): boolean {\n const ipInt = ipv4ToInt(ip);\n if (ipInt === -1) return false;\n\n for (const range of BLOCKED_IPV4_RANGES) {\n const startInt = ipv4ToInt(range.start);\n const endInt = ipv4ToInt(range.end);\n if (ipInt >= startInt && ipInt <= endInt) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check if an IPv6 address is a blocked address for Server-Side Request Forgery (SSRF) protection\n *\n * @param ip - The IPv6 address as a string\n * @returns True if the IP is blocked, false otherwise\n */\nfunction isBlockedIPv6(ip: string): boolean {\n // Normalize the address\n const normalized = ip.toLowerCase();\n\n // Loopback\n if (normalized === \"::1\" || normalized === \"0:0:0:0:0:0:0:1\") {\n return true;\n }\n\n // Unspecified\n if (normalized === \"::\" || normalized === \"0:0:0:0:0:0:0:0\") {\n return true;\n }\n\n // IPv4-mapped IPv6 addresses (::ffff:x.x.x.x)\n const ipv4Mapped = normalized.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (ipv4Mapped && ipv4Mapped[1]) {\n return isBlockedIPv4(ipv4Mapped[1]);\n }\n\n // Link-local (fe80::/10)\n if (\n normalized.startsWith(\"fe8\") ||\n normalized.startsWith(\"fe9\") ||\n normalized.startsWith(\"fea\") ||\n normalized.startsWith(\"feb\")\n ) {\n return true;\n }\n\n // Unique local (fc00::/7)\n if (normalized.startsWith(\"fc\") || normalized.startsWith(\"fd\")) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Check if a hostname is blocked for Server-Side Request Forgery (SSRF) protection by comparing against a list of blocked hostnames and patterns. This includes exact matches, subdomain checks, and common internal domain patterns.\n *\n * @param hostname - The hostname to check\n * @returns True if the hostname is blocked, false otherwise\n */\nexport function isBlockedHostname(hostname: string): boolean {\n const lower = hostname.toLowerCase();\n\n // Check exact matches\n if (BLOCKED_HOSTNAMES.includes(lower)) {\n return true;\n }\n\n // Check if it's a subdomain of a blocked hostname\n for (const blocked of BLOCKED_HOSTNAMES) {\n if (lower.endsWith(`.${blocked}`)) {\n return true;\n }\n }\n\n // Check for .local TLD (mDNS)\n if (lower.endsWith(\".local\")) {\n return true;\n }\n\n // Check for .internal domains\n if (lower.endsWith(\".internal\")) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Check if a hostname appears to be an IP address and if so, whether it's blocked for Server-Side Request Forgery (SSRF) protection\n *\n * @param hostname - The hostname to check\n * @returns True if the hostname is a blocked IP address, false otherwise\n */\nexport function isBlockedIPAddress(hostname: string): boolean {\n // IPv4 check\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(hostname)) {\n return isBlockedIPv4(hostname);\n }\n\n // IPv6 check (with brackets removed if present)\n const ipv6 = hostname.replace(/^\\[|\\]$/g, \"\");\n if (ipv6.includes(\":\")) {\n return isBlockedIPv6(ipv6);\n }\n\n return false;\n}\n\n/**\n * A result object for URL validation, indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid. This structure allows for clear communication of validation results and can be easily extended in the future if needed.\n *\n * @remarks\n * The `valid` property indicates if the URL passed validation. The `error` property provides a message explaining why the URL is invalid, if applicable. The `sanitizedUrl` property contains a cleaned-up version of the URL that can be safely used if the original URL is valid.\n */\nexport interface URLValidationResult {\n valid: boolean;\n error?: string;\n sanitizedUrl?: string;\n}\n\n/**\n * Validate a URL for use in general contexts (e.g. fetching data, making API calls) with Server-Side Request Forgery (SSRF) protection. This function checks if the URL is well-formed, uses allowed protocols (http/https), and ensures that the hostname does not resolve to internal IP addresses or hostnames. It also checks for non-standard ports that might indicate internal services.\n *\n * @param url - The URL to validate\n * @returns A URLValidationResult object indicating whether the URL is valid, any error message if invalid, and a sanitized version of the URL if valid\n */\nexport function validateUrl(url: string): URLValidationResult {\n try {\n const parsed = new URL(url);\n\n // Only allow http and https\n if (![\"http:\", \"https:\"].includes(parsed.protocol)) {\n return {\n valid: false,\n error: \"Only HTTP and HTTPS protocols are allowed\"\n };\n }\n\n const hostname = parsed.hostname.toLowerCase();\n\n // Check for blocked hostnames\n if (isBlockedHostname(hostname)) {\n return {\n valid: false,\n error: \"Access to internal hostnames is not allowed\"\n };\n }\n\n // Check for blocked IP addresses\n if (isBlockedIPAddress(hostname)) {\n return {\n valid: false,\n error: \"Access to internal IP addresses is not allowed\"\n };\n }\n\n // Check for non-standard ports that might indicate internal services\n const port = parsed.port\n ? Number.parseInt(parsed.port, 10)\n : parsed.protocol === \"https:\"\n ? 443\n : 80;\n\n // Block common internal service ports\n const blockedPorts = [\n 22, // SSH\n 23, // Telnet\n 25, // SMTP\n 53, // DNS\n 135, // RPC\n 139, // NetBIOS\n 445, // SMB\n 1433, // MSSQL\n 1521, // Oracle\n 3306, // MySQL\n 3389, // RDP\n 5432, // PostgreSQL\n 5900, // VNC\n 6379, // Redis\n 9200, // Elasticsearch\n 27017 // MongoDB\n ];\n\n if (blockedPorts.includes(port)) {\n return { valid: false, error: `Access to port ${port} is not allowed` };\n }\n\n return { valid: true, sanitizedUrl: parsed.toString() };\n } catch {\n return { valid: false, error: \"Invalid URL format\" };\n }\n}\n\n/**\n * Validate a URL for use in URL preview.\n *\n * @remarks\n * These validations are more restrictive than general URL validations. For URL previews, we want to be extra cautious to prevent any potential SSRF vulnerabilities, so we enforce stricter rules on allowed ports and ensure that the URL is well-formed and does not point to internal resources. This function builds upon the general `validateUrl` function and adds additional checks specific to URL previews, such as blocking non-standard HTTP ports that are commonly used for internal services. By using this function for URL previews, we can help ensure that the URLs being previewed are safe and do not pose a security risk to the application or its users.\n *\n * @param url - The URL to validate for preview\n * @returns A URLValidationResult object indicating whether the URL is valid for preview, any error message if invalid, and a sanitized version of the URL if valid\n */\nexport function validateUrlForPreview(url: string): URLValidationResult {\n const result = validateUrl(url);\n if (!result.valid) {\n return result;\n }\n\n const parsed = new URL(url);\n\n // For previews, only allow standard HTTP ports\n const port = parsed.port\n ? Number.parseInt(parsed.port, 10)\n : parsed.protocol === \"https:\"\n ? 443\n : 80;\n if (port !== 80 && port !== 443 && port !== 8080 && port !== 8443) {\n return {\n valid: false,\n error:\n \"Only standard HTTP ports (80, 443, 8080, 8443) are allowed for URL preview\"\n };\n }\n\n return result;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stryke/url",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A package containing the `StormURL` class, which is used to parse and manipulate URLs.",
|
|
6
6
|
"repository": {
|
|
@@ -22,11 +22,15 @@
|
|
|
22
22
|
"import": "./dist/storm-url.mjs"
|
|
23
23
|
},
|
|
24
24
|
"./types": { "require": "./dist/types.cjs", "import": "./dist/types.mjs" },
|
|
25
|
+
"./validate": {
|
|
26
|
+
"require": "./dist/validate.cjs",
|
|
27
|
+
"import": "./dist/validate.mjs"
|
|
28
|
+
},
|
|
25
29
|
"./*": "./*"
|
|
26
30
|
},
|
|
27
31
|
"types": "./dist/index.d.cts",
|
|
28
32
|
"dependencies": { "ufo": "^1.6.3" },
|
|
29
33
|
"devDependencies": { "@types/node": "^24.11.0", "tsdown": "^0.17.2" },
|
|
30
34
|
"publishConfig": { "access": "public" },
|
|
31
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "b8125bf046febc1991df62777827d0481c2c89c0"
|
|
32
36
|
}
|