@executor-js/sdk 1.5.8 → 1.5.10
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/dist/host-internal.js
CHANGED
|
@@ -88,27 +88,34 @@ var validateHostedOutboundUrl = (value, options = {}) => Effect.gen(function* ()
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
|
+
var CREDENTIAL_HEADERS = ["authorization", "proxy-authorization", "cookie"];
|
|
92
|
+
var stripCredentialHeaders = (init) => {
|
|
93
|
+
const headers = new Headers(init?.headers);
|
|
94
|
+
for (const name of CREDENTIAL_HEADERS) headers.delete(name);
|
|
95
|
+
return { ...init, headers };
|
|
96
|
+
};
|
|
91
97
|
var guardFetch = (underlying, options) => (async (input, init) => {
|
|
92
98
|
const maxRedirects = options.maxRedirects ?? 10;
|
|
93
99
|
let current = input;
|
|
100
|
+
let currentInit = init;
|
|
94
101
|
for (let redirects = 0; redirects <= maxRedirects; redirects++) {
|
|
95
102
|
const url = current instanceof Request ? current.url : String(current);
|
|
96
103
|
Effect.runSync(validateHostedOutboundUrl(url, options));
|
|
97
|
-
const response = await underlying(current, {
|
|
104
|
+
const response = await underlying(current, {
|
|
105
|
+
...currentInit,
|
|
106
|
+
redirect: "manual"
|
|
107
|
+
});
|
|
98
108
|
if (response.status >= 300 && response.status < 400 && response.headers.has("location") && redirects < maxRedirects) {
|
|
99
109
|
const next = new URL(response.headers.get("location"), url);
|
|
100
110
|
if (next.origin !== new URL(url).origin) {
|
|
101
|
-
|
|
102
|
-
url: next.toString(),
|
|
103
|
-
reason: "Cross-origin redirects are not allowed"
|
|
104
|
-
});
|
|
111
|
+
currentInit = stripCredentialHeaders(currentInit);
|
|
105
112
|
}
|
|
106
113
|
current = next.toString();
|
|
107
114
|
continue;
|
|
108
115
|
}
|
|
109
116
|
return response;
|
|
110
117
|
}
|
|
111
|
-
return await underlying(current, { ...
|
|
118
|
+
return await underlying(current, { ...currentInit, redirect: "manual" });
|
|
112
119
|
});
|
|
113
120
|
var makeHostedHttpClientLayer = (options = {}) => FetchHttpClient.layer.pipe(
|
|
114
121
|
Layer.provide(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hosted-http-client.ts"],"sourcesContent":["import { Effect, Layer, Schema } from \"effect\";\nimport { FetchHttpClient, HttpClient } from \"effect/unstable/http\";\n\nexport class HostedOutboundRequestBlocked extends Schema.TaggedErrorClass<HostedOutboundRequestBlocked>()(\n \"HostedOutboundRequestBlocked\",\n {\n url: Schema.String,\n reason: Schema.String,\n },\n) {}\n\nexport interface HostedHttpClientOptions {\n readonly allowLocalNetwork?: boolean;\n readonly maxRedirects?: number;\n readonly fetch?: typeof globalThis.fetch;\n}\n\nconst parseIpv4 = (hostname: string): readonly [number, number, number, number] | null => {\n const parts = hostname.split(\".\");\n if (parts.length !== 4) return null;\n const parsed: number[] = [];\n for (const part of parts) {\n if (!/^\\d+$/.test(part)) return null;\n const value = Number(part);\n if (!Number.isInteger(value) || value < 0 || value > 255) return null;\n parsed.push(value);\n }\n return parsed as [number, number, number, number];\n};\n\nconst parseIpv4MappedIpv6 = (\n hostname: string,\n): readonly [number, number, number, number] | null => {\n const prefix = \"::ffff:\";\n if (!hostname.startsWith(prefix)) return null;\n const embedded = hostname.slice(prefix.length);\n const dotted = parseIpv4(embedded);\n if (dotted) return dotted;\n\n const parts = embedded.split(\":\");\n if (parts.length !== 2) return null;\n\n const words = parts.map((part) => Number.parseInt(part, 16));\n if (\n words.some(\n (word, index) =>\n parts[index] === \"\" ||\n !/^[0-9a-f]+$/i.test(parts[index]) ||\n !Number.isInteger(word) ||\n word < 0 ||\n word > 0xffff,\n )\n ) {\n return null;\n }\n\n const [high, low] = words;\n return [high >> 8, high & 0xff, low >> 8, low & 0xff];\n};\n\nconst isPrivateIpv4 = ([a, b]: readonly [number, number, number, number]): boolean =>\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168);\n\nconst isBlockedMetadataHostname = (hostname: string): boolean => {\n const normalized = hostname.toLowerCase();\n return (\n normalized === \"metadata.google.internal\" ||\n normalized === \"metadata\" ||\n normalized === \"instance-data\" ||\n normalized === \"169.254.169.254\"\n );\n};\n\nconst isLocalOrPrivateHostname = (hostname: string): boolean => {\n const normalized = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (normalized === \"localhost\" || normalized.endsWith(\".localhost\")) return true;\n const ipv4 = parseIpv4(normalized);\n if (ipv4) return isPrivateIpv4(ipv4);\n const mappedIpv4 = parseIpv4MappedIpv6(normalized);\n if (mappedIpv4) return isPrivateIpv4(mappedIpv4);\n return (\n normalized === \"::1\" ||\n normalized.startsWith(\"fe80:\") ||\n normalized.startsWith(\"fc\") ||\n normalized.startsWith(\"fd\")\n );\n};\n\nexport const validateHostedOutboundUrl = (\n value: string,\n options: HostedHttpClientOptions = {},\n): Effect.Effect<void, HostedOutboundRequestBlocked> =>\n Effect.gen(function* () {\n const url = yield* Effect.try({\n try: () => new URL(value),\n catch: () =>\n new HostedOutboundRequestBlocked({\n url: value,\n reason: \"URL is invalid\",\n }),\n });\n\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return yield* new HostedOutboundRequestBlocked({\n url: value,\n reason: \"Only HTTP and HTTPS outbound requests are allowed\",\n });\n }\n\n if (isBlockedMetadataHostname(url.hostname)) {\n return yield* new HostedOutboundRequestBlocked({\n url: value,\n reason: \"Metadata service addresses are not allowed\",\n });\n }\n\n if (!options.allowLocalNetwork && isLocalOrPrivateHostname(url.hostname)) {\n return yield* new HostedOutboundRequestBlocked({\n url: value,\n reason: \"Local and private network addresses are not allowed\",\n });\n }\n });\n\nconst guardFetch = (\n underlying: typeof globalThis.fetch,\n options: HostedHttpClientOptions,\n): typeof globalThis.fetch =>\n (async (input, init) => {\n const maxRedirects = options.maxRedirects ?? 10;\n let current: Parameters<typeof globalThis.fetch>[0] | URL = input;\n for (let redirects = 0; redirects <= maxRedirects; redirects++) {\n const url = current instanceof Request ? current.url : String(current);\n Effect.runSync(validateHostedOutboundUrl(url, options));\n const response = await underlying(current, {
|
|
1
|
+
{"version":3,"sources":["../src/hosted-http-client.ts"],"sourcesContent":["import { Effect, Layer, Schema } from \"effect\";\nimport { FetchHttpClient, HttpClient } from \"effect/unstable/http\";\n\nexport class HostedOutboundRequestBlocked extends Schema.TaggedErrorClass<HostedOutboundRequestBlocked>()(\n \"HostedOutboundRequestBlocked\",\n {\n url: Schema.String,\n reason: Schema.String,\n },\n) {}\n\nexport interface HostedHttpClientOptions {\n readonly allowLocalNetwork?: boolean;\n readonly maxRedirects?: number;\n readonly fetch?: typeof globalThis.fetch;\n}\n\nconst parseIpv4 = (hostname: string): readonly [number, number, number, number] | null => {\n const parts = hostname.split(\".\");\n if (parts.length !== 4) return null;\n const parsed: number[] = [];\n for (const part of parts) {\n if (!/^\\d+$/.test(part)) return null;\n const value = Number(part);\n if (!Number.isInteger(value) || value < 0 || value > 255) return null;\n parsed.push(value);\n }\n return parsed as [number, number, number, number];\n};\n\nconst parseIpv4MappedIpv6 = (\n hostname: string,\n): readonly [number, number, number, number] | null => {\n const prefix = \"::ffff:\";\n if (!hostname.startsWith(prefix)) return null;\n const embedded = hostname.slice(prefix.length);\n const dotted = parseIpv4(embedded);\n if (dotted) return dotted;\n\n const parts = embedded.split(\":\");\n if (parts.length !== 2) return null;\n\n const words = parts.map((part) => Number.parseInt(part, 16));\n if (\n words.some(\n (word, index) =>\n parts[index] === \"\" ||\n !/^[0-9a-f]+$/i.test(parts[index]) ||\n !Number.isInteger(word) ||\n word < 0 ||\n word > 0xffff,\n )\n ) {\n return null;\n }\n\n const [high, low] = words;\n return [high >> 8, high & 0xff, low >> 8, low & 0xff];\n};\n\nconst isPrivateIpv4 = ([a, b]: readonly [number, number, number, number]): boolean =>\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168);\n\nconst isBlockedMetadataHostname = (hostname: string): boolean => {\n const normalized = hostname.toLowerCase();\n return (\n normalized === \"metadata.google.internal\" ||\n normalized === \"metadata\" ||\n normalized === \"instance-data\" ||\n normalized === \"169.254.169.254\"\n );\n};\n\nconst isLocalOrPrivateHostname = (hostname: string): boolean => {\n const normalized = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (normalized === \"localhost\" || normalized.endsWith(\".localhost\")) return true;\n const ipv4 = parseIpv4(normalized);\n if (ipv4) return isPrivateIpv4(ipv4);\n const mappedIpv4 = parseIpv4MappedIpv6(normalized);\n if (mappedIpv4) return isPrivateIpv4(mappedIpv4);\n return (\n normalized === \"::1\" ||\n normalized.startsWith(\"fe80:\") ||\n normalized.startsWith(\"fc\") ||\n normalized.startsWith(\"fd\")\n );\n};\n\nexport const validateHostedOutboundUrl = (\n value: string,\n options: HostedHttpClientOptions = {},\n): Effect.Effect<void, HostedOutboundRequestBlocked> =>\n Effect.gen(function* () {\n const url = yield* Effect.try({\n try: () => new URL(value),\n catch: () =>\n new HostedOutboundRequestBlocked({\n url: value,\n reason: \"URL is invalid\",\n }),\n });\n\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return yield* new HostedOutboundRequestBlocked({\n url: value,\n reason: \"Only HTTP and HTTPS outbound requests are allowed\",\n });\n }\n\n if (isBlockedMetadataHostname(url.hostname)) {\n return yield* new HostedOutboundRequestBlocked({\n url: value,\n reason: \"Metadata service addresses are not allowed\",\n });\n }\n\n if (!options.allowLocalNetwork && isLocalOrPrivateHostname(url.hostname)) {\n return yield* new HostedOutboundRequestBlocked({\n url: value,\n reason: \"Local and private network addresses are not allowed\",\n });\n }\n });\n\nconst CREDENTIAL_HEADERS = [\"authorization\", \"proxy-authorization\", \"cookie\"] as const;\n\nconst stripCredentialHeaders = (init: RequestInit | undefined): RequestInit => {\n const headers = new Headers(init?.headers);\n for (const name of CREDENTIAL_HEADERS) headers.delete(name);\n return { ...init, headers };\n};\n\nconst guardFetch = (\n underlying: typeof globalThis.fetch,\n options: HostedHttpClientOptions,\n): typeof globalThis.fetch =>\n (async (input, init) => {\n const maxRedirects = options.maxRedirects ?? 10;\n let current: Parameters<typeof globalThis.fetch>[0] | URL = input;\n let currentInit = init;\n for (let redirects = 0; redirects <= maxRedirects; redirects++) {\n const url = current instanceof Request ? current.url : String(current);\n Effect.runSync(validateHostedOutboundUrl(url, options));\n const response = await underlying(current, {\n ...currentInit,\n redirect: \"manual\",\n });\n if (\n response.status >= 300 &&\n response.status < 400 &&\n response.headers.has(\"location\") &&\n redirects < maxRedirects\n ) {\n const next = new URL(response.headers.get(\"location\")!, url);\n // Cross-origin redirects are followed (the loop re-validates every\n // hop), but credentials minted for the original origin must not leak\n // to the redirect target — same as fetch/curl behavior.\n if (next.origin !== new URL(url).origin) {\n currentInit = stripCredentialHeaders(currentInit);\n }\n current = next.toString();\n continue;\n }\n return response;\n }\n return await underlying(current, { ...currentInit, redirect: \"manual\" });\n }) as typeof globalThis.fetch;\n\nexport const makeHostedHttpClientLayer = (\n options: HostedHttpClientOptions = {},\n): Layer.Layer<HttpClient.HttpClient> =>\n FetchHttpClient.layer.pipe(\n Layer.provide(\n options.fetch\n ? Layer.succeed(FetchHttpClient.Fetch)(guardFetch(options.fetch, options))\n : Layer.effect(\n FetchHttpClient.Fetch,\n Effect.map(Effect.service(FetchHttpClient.Fetch), (underlying) =>\n guardFetch(underlying, options),\n ),\n ),\n ),\n );\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-http-client.d.ts","sourceRoot":"","sources":["../src/hosted-http-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAmB,UAAU,EAAE,MAAM,sBAAsB,CAAC;;;;;AAEnE,qBAAa,4BAA6B,SAAQ,iCAMjD;CAAG;AAEJ,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC1C;AA8ED,eAAO,MAAM,yBAAyB,GACpC,OAAO,MAAM,EACb,UAAS,uBAA4B,KACpC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,4BAA4B,CA+B/C,CAAC;
|
|
1
|
+
{"version":3,"file":"hosted-http-client.d.ts","sourceRoot":"","sources":["../src/hosted-http-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAmB,UAAU,EAAE,MAAM,sBAAsB,CAAC;;;;;AAEnE,qBAAa,4BAA6B,SAAQ,iCAMjD;CAAG;AAEJ,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC1C;AA8ED,eAAO,MAAM,yBAAyB,GACpC,OAAO,MAAM,EACb,UAAS,uBAA4B,KACpC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,4BAA4B,CA+B/C,CAAC;AA8CL,eAAO,MAAM,yBAAyB,GACpC,UAAS,uBAA4B,KACpC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAYjC,CAAC"}
|