@teneo-protocol/sdk 1.0.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/.dockerignore +14 -0
- package/.env.test.example +14 -0
- package/.eslintrc.json +26 -0
- package/.github/workflows/claude-code-review.yml +78 -0
- package/.github/workflows/claude-reviewer.yml +64 -0
- package/.github/workflows/publish-npm.yml +38 -0
- package/.github/workflows/push-to-main.yml +23 -0
- package/.node-version +1 -0
- package/.prettierrc +11 -0
- package/Dockerfile +25 -0
- package/LICENCE +661 -0
- package/README.md +709 -0
- package/dist/constants.d.ts +42 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +45 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/websocket-client.d.ts +261 -0
- package/dist/core/websocket-client.d.ts.map +1 -0
- package/dist/core/websocket-client.js +875 -0
- package/dist/core/websocket-client.js.map +1 -0
- package/dist/formatters/response-formatter.d.ts +354 -0
- package/dist/formatters/response-formatter.d.ts.map +1 -0
- package/dist/formatters/response-formatter.js +575 -0
- package/dist/formatters/response-formatter.js.map +1 -0
- package/dist/handlers/message-handler-registry.d.ts +155 -0
- package/dist/handlers/message-handler-registry.d.ts.map +1 -0
- package/dist/handlers/message-handler-registry.js +216 -0
- package/dist/handlers/message-handler-registry.js.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
- package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
- package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
- package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
- package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
- package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/base-handler.js +83 -0
- package/dist/handlers/message-handlers/base-handler.js.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.js +47 -0
- package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.js +29 -0
- package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/index.d.ts +28 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -0
- package/dist/handlers/message-handlers/index.js +100 -0
- package/dist/handlers/message-handlers/index.js.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.js +44 -0
- package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/types.d.ts +51 -0
- package/dist/handlers/message-handlers/types.d.ts.map +1 -0
- package/dist/handlers/message-handlers/types.js +7 -0
- package/dist/handlers/message-handlers/types.js.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
- package/dist/handlers/webhook-handler.d.ts +202 -0
- package/dist/handlers/webhook-handler.d.ts.map +1 -0
- package/dist/handlers/webhook-handler.js +511 -0
- package/dist/handlers/webhook-handler.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/agent-registry.d.ts +173 -0
- package/dist/managers/agent-registry.d.ts.map +1 -0
- package/dist/managers/agent-registry.js +310 -0
- package/dist/managers/agent-registry.js.map +1 -0
- package/dist/managers/connection-manager.d.ts +134 -0
- package/dist/managers/connection-manager.d.ts.map +1 -0
- package/dist/managers/connection-manager.js +176 -0
- package/dist/managers/connection-manager.js.map +1 -0
- package/dist/managers/index.d.ts +9 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/managers/index.js +16 -0
- package/dist/managers/index.js.map +1 -0
- package/dist/managers/message-router.d.ts +112 -0
- package/dist/managers/message-router.d.ts.map +1 -0
- package/dist/managers/message-router.js +260 -0
- package/dist/managers/message-router.js.map +1 -0
- package/dist/managers/room-manager.d.ts +165 -0
- package/dist/managers/room-manager.d.ts.map +1 -0
- package/dist/managers/room-manager.js +227 -0
- package/dist/managers/room-manager.js.map +1 -0
- package/dist/teneo-sdk.d.ts +703 -0
- package/dist/teneo-sdk.d.ts.map +1 -0
- package/dist/teneo-sdk.js +907 -0
- package/dist/teneo-sdk.js.map +1 -0
- package/dist/types/config.d.ts +1047 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +720 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/error-codes.d.ts +29 -0
- package/dist/types/error-codes.d.ts.map +1 -0
- package/dist/types/error-codes.js +41 -0
- package/dist/types/error-codes.js.map +1 -0
- package/dist/types/events.d.ts +616 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +261 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/health.d.ts +40 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +6 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +123 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +3734 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +482 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/validation.d.ts +81 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +115 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/utils/bounded-queue.d.ts +127 -0
- package/dist/utils/bounded-queue.d.ts.map +1 -0
- package/dist/utils/bounded-queue.js +181 -0
- package/dist/utils/bounded-queue.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +141 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +215 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/deduplication-cache.d.ts +110 -0
- package/dist/utils/deduplication-cache.d.ts.map +1 -0
- package/dist/utils/deduplication-cache.js +177 -0
- package/dist/utils/deduplication-cache.js.map +1 -0
- package/dist/utils/event-waiter.d.ts +101 -0
- package/dist/utils/event-waiter.d.ts.map +1 -0
- package/dist/utils/event-waiter.js +118 -0
- package/dist/utils/event-waiter.js.map +1 -0
- package/dist/utils/index.d.ts +51 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +72 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +22 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +122 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +190 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry-policy.d.ts +191 -0
- package/dist/utils/retry-policy.d.ts.map +1 -0
- package/dist/utils/retry-policy.js +225 -0
- package/dist/utils/retry-policy.js.map +1 -0
- package/dist/utils/secure-private-key.d.ts +113 -0
- package/dist/utils/secure-private-key.d.ts.map +1 -0
- package/dist/utils/secure-private-key.js +188 -0
- package/dist/utils/secure-private-key.js.map +1 -0
- package/dist/utils/signature-verifier.d.ts +143 -0
- package/dist/utils/signature-verifier.d.ts.map +1 -0
- package/dist/utils/signature-verifier.js +238 -0
- package/dist/utils/signature-verifier.js.map +1 -0
- package/dist/utils/ssrf-validator.d.ts +36 -0
- package/dist/utils/ssrf-validator.d.ts.map +1 -0
- package/dist/utils/ssrf-validator.js +195 -0
- package/dist/utils/ssrf-validator.js.map +1 -0
- package/examples/.env.example +17 -0
- package/examples/basic-usage.ts +211 -0
- package/examples/production-dashboard/.env.example +153 -0
- package/examples/production-dashboard/package.json +39 -0
- package/examples/production-dashboard/public/dashboard.html +642 -0
- package/examples/production-dashboard/server.ts +753 -0
- package/examples/webhook-integration.ts +239 -0
- package/examples/x-influencer-battle-redesign.html +1065 -0
- package/examples/x-influencer-battle-server.ts +217 -0
- package/examples/x-influencer-battle.html +787 -0
- package/package.json +65 -0
- package/src/constants.ts +43 -0
- package/src/core/websocket-client.test.ts +512 -0
- package/src/core/websocket-client.ts +1056 -0
- package/src/formatters/response-formatter.test.ts +571 -0
- package/src/formatters/response-formatter.ts +677 -0
- package/src/handlers/message-handler-registry.ts +239 -0
- package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
- package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
- package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
- package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
- package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
- package/src/handlers/message-handlers/base-handler.ts +101 -0
- package/src/handlers/message-handlers/challenge-handler.ts +57 -0
- package/src/handlers/message-handlers/error-message-handler.ts +27 -0
- package/src/handlers/message-handlers/index.ts +77 -0
- package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
- package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
- package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
- package/src/handlers/message-handlers/task-response-handler.ts +45 -0
- package/src/handlers/message-handlers/types.ts +77 -0
- package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
- package/src/handlers/webhook-handler.test.ts +789 -0
- package/src/handlers/webhook-handler.ts +576 -0
- package/src/index.ts +269 -0
- package/src/managers/agent-registry.test.ts +466 -0
- package/src/managers/agent-registry.ts +347 -0
- package/src/managers/connection-manager.ts +195 -0
- package/src/managers/index.ts +9 -0
- package/src/managers/message-router.ts +349 -0
- package/src/managers/room-manager.ts +248 -0
- package/src/teneo-sdk.ts +1022 -0
- package/src/types/config.test.ts +325 -0
- package/src/types/config.ts +799 -0
- package/src/types/error-codes.ts +44 -0
- package/src/types/events.test.ts +302 -0
- package/src/types/events.ts +382 -0
- package/src/types/health.ts +46 -0
- package/src/types/index.ts +199 -0
- package/src/types/messages.test.ts +660 -0
- package/src/types/messages.ts +570 -0
- package/src/types/validation.ts +123 -0
- package/src/utils/bounded-queue.test.ts +356 -0
- package/src/utils/bounded-queue.ts +205 -0
- package/src/utils/circuit-breaker.test.ts +394 -0
- package/src/utils/circuit-breaker.ts +262 -0
- package/src/utils/deduplication-cache.test.ts +380 -0
- package/src/utils/deduplication-cache.ts +198 -0
- package/src/utils/event-waiter.test.ts +381 -0
- package/src/utils/event-waiter.ts +172 -0
- package/src/utils/index.ts +74 -0
- package/src/utils/logger.ts +87 -0
- package/src/utils/rate-limiter.test.ts +341 -0
- package/src/utils/rate-limiter.ts +211 -0
- package/src/utils/retry-policy.test.ts +558 -0
- package/src/utils/retry-policy.ts +272 -0
- package/src/utils/secure-private-key.test.ts +356 -0
- package/src/utils/secure-private-key.ts +205 -0
- package/src/utils/signature-verifier.test.ts +464 -0
- package/src/utils/signature-verifier.ts +298 -0
- package/src/utils/ssrf-validator.test.ts +372 -0
- package/src/utils/ssrf-validator.ts +224 -0
- package/tests/integration/real-server.test.ts +740 -0
- package/tests/integration/websocket.test.ts +381 -0
- package/tests/integration-setup.ts +16 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +32 -0
- package/vitest.config.ts +42 -0
- package/vitest.integration.config.ts +23 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSRF (Server-Side Request Forgery) Validator
|
|
3
|
+
* Prevents webhook URLs from pointing to internal/private network resources
|
|
4
|
+
*
|
|
5
|
+
* This utility blocks:
|
|
6
|
+
* - Private IP ranges (RFC1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
|
|
7
|
+
* - Loopback addresses (127.0.0.0/8, ::1)
|
|
8
|
+
* - Link-local addresses (169.254.0.0/16, fe80::/10)
|
|
9
|
+
* - Cloud metadata endpoints (AWS, GCP, Azure, DigitalOcean)
|
|
10
|
+
* - IPv6 private ranges (fc00::/7, fd00::/8)
|
|
11
|
+
* - Multicast and broadcast addresses
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { URL } from "url";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Blocked hostnames that should never be accessible via webhooks
|
|
18
|
+
* Includes cloud provider metadata endpoints and localhost variations
|
|
19
|
+
*/
|
|
20
|
+
const BLOCKED_HOSTNAMES = [
|
|
21
|
+
// AWS metadata endpoints
|
|
22
|
+
"169.254.169.254",
|
|
23
|
+
"fd00:ec2::254",
|
|
24
|
+
"instance-data",
|
|
25
|
+
"instance-data.ec2.internal",
|
|
26
|
+
|
|
27
|
+
// Google Cloud metadata
|
|
28
|
+
"metadata.google.internal",
|
|
29
|
+
"metadata.google.com",
|
|
30
|
+
|
|
31
|
+
// Azure metadata
|
|
32
|
+
"169.254.169.254", // Same as AWS
|
|
33
|
+
|
|
34
|
+
// DigitalOcean metadata
|
|
35
|
+
"169.254.169.254", // Same as AWS/Azure
|
|
36
|
+
|
|
37
|
+
// Kubernetes metadata
|
|
38
|
+
"kubernetes.default",
|
|
39
|
+
"kubernetes.default.svc",
|
|
40
|
+
"kubernetes.default.svc.cluster.local",
|
|
41
|
+
|
|
42
|
+
// Localhost bind-all (not safe for webhooks)
|
|
43
|
+
"0.0.0.0",
|
|
44
|
+
"[::]",
|
|
45
|
+
"::"
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Localhost hostnames that are allowed for development
|
|
50
|
+
*/
|
|
51
|
+
const LOCALHOST_HOSTNAMES = ["localhost", "127.0.0.1", "::1", "[::1]"];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if an IP address is in a private range
|
|
55
|
+
* Supports both IPv4 and IPv6
|
|
56
|
+
*/
|
|
57
|
+
export function isPrivateIP(hostname: string): boolean {
|
|
58
|
+
// Remove IPv6 brackets if present
|
|
59
|
+
const cleanHostname = hostname.replace(/^\[|\]$/g, "");
|
|
60
|
+
|
|
61
|
+
// IPv4 private ranges (RFC1918)
|
|
62
|
+
if (/^10\./.test(cleanHostname)) return true;
|
|
63
|
+
if (/^192\.168\./.test(cleanHostname)) return true;
|
|
64
|
+
if (/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(cleanHostname)) return true; // 172.16-31
|
|
65
|
+
|
|
66
|
+
// IPv4 loopback (127.0.0.0/8)
|
|
67
|
+
if (/^127\./.test(cleanHostname)) return true;
|
|
68
|
+
|
|
69
|
+
// IPv4 link-local (169.254.0.0/16)
|
|
70
|
+
if (/^169\.254\./.test(cleanHostname)) return true;
|
|
71
|
+
|
|
72
|
+
// IPv4 multicast (224.0.0.0/4)
|
|
73
|
+
if (/^2(2[4-9]|3[0-9])\./.test(cleanHostname)) return true;
|
|
74
|
+
|
|
75
|
+
// IPv4 broadcast
|
|
76
|
+
if (cleanHostname === "255.255.255.255") return true;
|
|
77
|
+
|
|
78
|
+
// IPv6 private ranges
|
|
79
|
+
// Link-local (fe80::/10)
|
|
80
|
+
if (/^fe[89ab][0-9a-f]:/i.test(cleanHostname)) return true;
|
|
81
|
+
|
|
82
|
+
// Unique local addresses (fc00::/7)
|
|
83
|
+
if (/^f[cd][0-9a-f]{2}:/i.test(cleanHostname)) return true;
|
|
84
|
+
|
|
85
|
+
// IPv6 loopback (::1)
|
|
86
|
+
if (cleanHostname === "::1") return true;
|
|
87
|
+
if (cleanHostname === "0:0:0:0:0:0:0:1") return true;
|
|
88
|
+
|
|
89
|
+
// IPv6 unspecified (::)
|
|
90
|
+
if (cleanHostname === "::") return true;
|
|
91
|
+
if (cleanHostname === "0:0:0:0:0:0:0:0") return true;
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if a hostname is a blocked cloud metadata endpoint
|
|
98
|
+
*/
|
|
99
|
+
export function isCloudMetadataEndpoint(hostname: string): boolean {
|
|
100
|
+
const cleanHostname = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
101
|
+
|
|
102
|
+
return BLOCKED_HOSTNAMES.some(
|
|
103
|
+
(blocked) =>
|
|
104
|
+
cleanHostname === blocked.toLowerCase() || cleanHostname.endsWith(`.${blocked.toLowerCase()}`)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if a hostname is an allowed localhost exception
|
|
110
|
+
* Only true localhost addresses, not bind-all addresses
|
|
111
|
+
*/
|
|
112
|
+
export function isLocalhostException(hostname: string): boolean {
|
|
113
|
+
const cleanHostname = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
114
|
+
|
|
115
|
+
return LOCALHOST_HOSTNAMES.some((allowed) => cleanHostname === allowed.toLowerCase());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if hostname resolves to a Kubernetes service
|
|
120
|
+
* Blocks internal k8s service discovery
|
|
121
|
+
*/
|
|
122
|
+
function isKubernetesService(hostname: string): boolean {
|
|
123
|
+
const cleanHostname = hostname.toLowerCase();
|
|
124
|
+
|
|
125
|
+
// Block .svc, .svc.cluster, .svc.cluster.local
|
|
126
|
+
if (cleanHostname.includes(".svc")) return true;
|
|
127
|
+
|
|
128
|
+
// Block direct kubernetes service names
|
|
129
|
+
if (cleanHostname.startsWith("kubernetes")) return true;
|
|
130
|
+
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate a webhook URL for SSRF vulnerabilities
|
|
136
|
+
* Throws an error if the URL is unsafe
|
|
137
|
+
*
|
|
138
|
+
* @param url - The webhook URL to validate
|
|
139
|
+
* @param allowLocalhost - Whether to allow localhost URLs (for development)
|
|
140
|
+
* @throws Error if URL is unsafe or invalid
|
|
141
|
+
*/
|
|
142
|
+
export function validateWebhookUrl(url: string, allowLocalhost: boolean = false): void {
|
|
143
|
+
let parsed: URL;
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
parsed = new URL(url);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new Error(`Invalid webhook URL: ${(error as Error).message}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const hostname = parsed.hostname;
|
|
152
|
+
|
|
153
|
+
// Check protocol first (basic validation)
|
|
154
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
155
|
+
throw new Error(`Webhook URL must use HTTP or HTTPS protocol (got: ${parsed.protocol})`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Block cloud metadata endpoints FIRST (before any other checks)
|
|
159
|
+
if (isCloudMetadataEndpoint(hostname)) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Webhook URL blocked for security: ${hostname} is a cloud metadata endpoint. ` +
|
|
162
|
+
"This could expose sensitive credentials and secrets."
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Block Kubernetes services
|
|
167
|
+
if (isKubernetesService(hostname)) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Webhook URL blocked for security: ${hostname} appears to be a Kubernetes service. ` +
|
|
170
|
+
"Internal service discovery is not allowed for webhooks."
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check localhost BEFORE private IP check (localhost is specific case of private IP)
|
|
175
|
+
if (isLocalhostException(hostname)) {
|
|
176
|
+
if (allowLocalhost) {
|
|
177
|
+
return; // Localhost is allowed for development
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Webhook URL blocked: ${hostname} is a localhost address. ` +
|
|
181
|
+
"Set allowInsecureWebhooks=true to allow localhost webhooks for testing."
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Block private IP ranges
|
|
187
|
+
if (isPrivateIP(hostname)) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Webhook URL blocked for security: ${hostname} is a private IP address. ` +
|
|
190
|
+
"Webhooks cannot point to internal network resources. " +
|
|
191
|
+
"Use a publicly accessible endpoint or set allowInsecureWebhooks=true for testing."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// For non-HTTPS, only allow localhost (which already passed checks above)
|
|
196
|
+
if (parsed.protocol === "http:") {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"Webhook URL must use HTTPS for non-localhost endpoints. " +
|
|
199
|
+
"Use HTTPS or set allowInsecureWebhooks=true for testing with localhost."
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Additional safety: block ports that are commonly internal
|
|
204
|
+
if (parsed.port) {
|
|
205
|
+
const port = parseInt(parsed.port, 10);
|
|
206
|
+
const dangerousPorts = [
|
|
207
|
+
22, // SSH
|
|
208
|
+
23, // Telnet
|
|
209
|
+
25, // SMTP
|
|
210
|
+
3306, // MySQL
|
|
211
|
+
5432, // PostgreSQL
|
|
212
|
+
6379, // Redis
|
|
213
|
+
9200, // Elasticsearch
|
|
214
|
+
27017 // MongoDB
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
if (dangerousPorts.includes(port)) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Webhook URL blocked for security: Port ${port} is commonly used for internal services. ` +
|
|
220
|
+
"Use a different port or set allowInsecureWebhooks=true for testing."
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|