@workglow/tasks 0.2.3 → 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 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 === "private" && !allowPrivate) {
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: classification.kind === "private"
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=D1A8BE957A811A1A64756E2164756E21
12434
+ //# debugId=F4110230C527F84264756E2164756E21