@workglow/tasks 0.2.4 → 0.2.5
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/browser.js +26 -6
- package/dist/browser.js.map +5 -5
- package/dist/bun.js +35 -7
- package/dist/bun.js.map +6 -6
- package/dist/node.js +35 -7
- package/dist/node.js.map +6 -6
- package/dist/task/FetchUrlTask.d.ts.map +1 -1
- package/dist/util/SafeFetch.d.ts +16 -0
- package/dist/util/SafeFetch.d.ts.map +1 -1
- package/dist/util/SafeFetch.server.d.ts.map +1 -1
- package/dist/util/UrlClassifier.d.ts +15 -0
- package/dist/util/UrlClassifier.d.ts.map +1 -1
- package/package.json +9 -9
package/dist/browser.js
CHANGED
|
@@ -725,6 +725,7 @@ Workflow10.prototype.sum = CreateAdaptiveWorkflow(ScalarSumTask, VectorSumTask);
|
|
|
725
725
|
import { PermanentJobError } from "@workglow/job-queue";
|
|
726
726
|
|
|
727
727
|
// src/util/UrlClassifier.ts
|
|
728
|
+
import { resourcePatternMatches } from "@workglow/task-graph";
|
|
728
729
|
import ipaddr from "ipaddr.js";
|
|
729
730
|
var PRIVATE_EXACT_HOSTS = new Set([
|
|
730
731
|
"localhost",
|
|
@@ -938,27 +939,43 @@ function urlResourcePattern(urlStr) {
|
|
|
938
939
|
const origin = parsed.port.length > 0 ? `${parsed.protocol}//${parsed.host}` : `${parsed.protocol}//${parsed.hostname}`;
|
|
939
940
|
return `${origin}/*`;
|
|
940
941
|
}
|
|
942
|
+
function urlMatchesScope(url, patterns) {
|
|
943
|
+
let canonical;
|
|
944
|
+
try {
|
|
945
|
+
const parsed = new URL(url);
|
|
946
|
+
parsed.hostname = normalizeHost(parsed.hostname);
|
|
947
|
+
canonical = parsed.toString();
|
|
948
|
+
} catch {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
return patterns.some((pat) => resourcePatternMatches(pat, canonical));
|
|
952
|
+
}
|
|
941
953
|
|
|
942
954
|
// src/util/SafeFetch.ts
|
|
943
955
|
var MAX_REDIRECT_HOPS = 20;
|
|
944
|
-
function assertAllowedUrl(url, allowPrivate) {
|
|
956
|
+
function assertAllowedUrl(url, allowPrivate, privateResourceScopes) {
|
|
945
957
|
const classification = classifyUrl(url);
|
|
946
958
|
if (classification.kind === "invalid") {
|
|
947
959
|
throw new PermanentJobError(`Refusing to fetch invalid URL: ${classification.reason}`);
|
|
948
960
|
}
|
|
949
|
-
if (classification.kind
|
|
961
|
+
if (classification.kind !== "private")
|
|
962
|
+
return;
|
|
963
|
+
if (!allowPrivate) {
|
|
950
964
|
throw new PermanentJobError(`Refusing to fetch private/internal URL ${url}: ${classification.reason}. ` + `Grant the 'network:private' entitlement to allow this request.`);
|
|
951
965
|
}
|
|
966
|
+
if (privateResourceScopes !== undefined && !urlMatchesScope(url, privateResourceScopes)) {
|
|
967
|
+
throw new PermanentJobError(`Refusing to fetch private/internal URL ${url}: outside granted network:private scope ` + `[${privateResourceScopes.join(", ")}]. A compromised upstream may be attempting ` + `to escape the task's authorized private-host origin.`);
|
|
968
|
+
}
|
|
952
969
|
}
|
|
953
970
|
function isRedirectStatus(status) {
|
|
954
971
|
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
955
972
|
}
|
|
956
973
|
async function defaultSafeFetch(url, options) {
|
|
957
974
|
const requestedRedirectMode = options.redirect ?? "follow";
|
|
958
|
-
const { allowPrivate, redirect: _redirect, ...fetchOptions } = options;
|
|
975
|
+
const { allowPrivate, privateResourceScopes, redirect: _redirect, ...fetchOptions } = options;
|
|
959
976
|
let currentUrl = url;
|
|
960
977
|
for (let hops = 0;hops <= MAX_REDIRECT_HOPS; hops += 1) {
|
|
961
|
-
assertAllowedUrl(currentUrl, allowPrivate);
|
|
978
|
+
assertAllowedUrl(currentUrl, allowPrivate, privateResourceScopes);
|
|
962
979
|
const response = await globalThis.fetch(currentUrl, {
|
|
963
980
|
...fetchOptions,
|
|
964
981
|
redirect: "manual"
|
|
@@ -2338,12 +2355,14 @@ class FetchUrlJob extends Job {
|
|
|
2338
2355
|
if (classification.kind === "invalid") {
|
|
2339
2356
|
throw new PermanentJobError2(`Refusing to fetch invalid URL ${input.url}: ${classification.reason ?? "malformed"}`);
|
|
2340
2357
|
}
|
|
2358
|
+
const isPrivate = classification.kind === "private";
|
|
2341
2359
|
const response = await fetchWithProgress(input.url, {
|
|
2342
2360
|
method: input.method,
|
|
2343
2361
|
headers: input.headers,
|
|
2344
2362
|
body: input.body,
|
|
2345
2363
|
signal: context.signal,
|
|
2346
|
-
allowPrivate:
|
|
2364
|
+
allowPrivate: isPrivate,
|
|
2365
|
+
privateResourceScopes: isPrivate ? [urlResourcePattern(input.url)] : undefined
|
|
2347
2366
|
}, async (progress) => await context.updateProgress(progress));
|
|
2348
2367
|
if (response.ok) {
|
|
2349
2368
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -12262,6 +12281,7 @@ var registerCommonTasks2 = () => {
|
|
|
12262
12281
|
};
|
|
12263
12282
|
export {
|
|
12264
12283
|
urlResourcePattern,
|
|
12284
|
+
urlMatchesScope,
|
|
12265
12285
|
tryNormalizeIPv4,
|
|
12266
12286
|
split,
|
|
12267
12287
|
setGlobalMcpServerRepository,
|
|
@@ -12411,4 +12431,4 @@ export {
|
|
|
12411
12431
|
ArrayTask
|
|
12412
12432
|
};
|
|
12413
12433
|
|
|
12414
|
-
//# debugId=
|
|
12434
|
+
//# debugId=F4110230C527F84264756E2164756E21
|