@workglow/tasks 0.2.2 → 0.2.4
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 +421 -76
- package/dist/browser.js.map +9 -6
- package/dist/bun.d.ts +1 -0
- package/dist/bun.d.ts.map +1 -1
- package/dist/bun.js +540 -68
- package/dist/bun.js.map +11 -7
- package/dist/common.d.ts +3 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/node.d.ts +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +540 -68
- package/dist/node.js.map +11 -7
- package/dist/task/FetchUrlTask.d.ts +13 -0
- package/dist/task/FetchUrlTask.d.ts.map +1 -1
- package/dist/task/image/imageCodecLimits.d.ts +80 -0
- package/dist/task/image/imageCodecLimits.d.ts.map +1 -0
- package/dist/task/image/imageRasterCodecBrowser.d.ts.map +1 -1
- package/dist/task/image/imageRasterCodecNode.d.ts.map +1 -1
- package/dist/util/SafeFetch.d.ts +51 -0
- package/dist/util/SafeFetch.d.ts.map +1 -0
- package/dist/util/SafeFetch.server.d.ts +22 -0
- package/dist/util/SafeFetch.server.d.ts.map +1 -0
- package/dist/util/UrlClassifier.d.ts +64 -0
- package/dist/util/UrlClassifier.d.ts.map +1 -0
- package/package.json +13 -9
package/dist/browser.js
CHANGED
|
@@ -6,17 +6,69 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
// src/task/image/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// src/task/image/imageCodecLimits.ts
|
|
10
|
+
var MAX_DECODED_PIXELS = 1e8;
|
|
11
|
+
var MAX_INPUT_BYTES_NODE = 64 * 1024 * 1024;
|
|
12
|
+
var MAX_INPUT_BYTES_BROWSER = 8 * 1024 * 1024;
|
|
13
|
+
var REJECTED_DECODE_MIME_TYPES = new Set([
|
|
14
|
+
"image/svg+xml",
|
|
15
|
+
"image/svg",
|
|
16
|
+
"image/gif",
|
|
17
|
+
"image/apng"
|
|
18
|
+
]);
|
|
19
|
+
var SUPPORTED_OUTPUT_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
|
20
|
+
function assertWithinPixelBudget(width, height) {
|
|
21
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
22
|
+
throw new Error(`Image raster codec: invalid dimensions ${width}x${height}`);
|
|
23
|
+
}
|
|
24
|
+
const pixels = width * height;
|
|
25
|
+
if (pixels > MAX_DECODED_PIXELS) {
|
|
26
|
+
throw new Error(`Image raster codec: decoded image exceeds pixel budget ` + `(${width}x${height} = ${pixels} > ${MAX_DECODED_PIXELS})`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function assertWithinByteBudget(byteLength, limit) {
|
|
30
|
+
if (byteLength > limit) {
|
|
31
|
+
throw new Error(`Image raster codec: input exceeds byte budget (${byteLength} > ${limit})`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function formatDataUriErrorPreview(value) {
|
|
35
|
+
if (typeof value !== "string") {
|
|
36
|
+
return String(value);
|
|
37
|
+
}
|
|
38
|
+
if (value.startsWith("data:")) {
|
|
39
|
+
const commaIndex = value.indexOf(",");
|
|
40
|
+
if (commaIndex >= 0) {
|
|
41
|
+
return `${value.slice(0, commaIndex)},[REDACTED]`;
|
|
42
|
+
}
|
|
43
|
+
return `${value.slice(0, 80)}${value.length > 80 ? "…" : ""}`;
|
|
44
|
+
}
|
|
45
|
+
return `${value.slice(0, 80)}${value.length > 80 ? "…" : ""}`;
|
|
46
|
+
}
|
|
47
|
+
function assertIsDataUri(value) {
|
|
48
|
+
if (typeof value !== "string" || !value.startsWith("data:")) {
|
|
49
|
+
const preview = formatDataUriErrorPreview(value);
|
|
50
|
+
throw new Error(`Image raster codec: expected a data: URI but received "${preview}"`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function extractDataUriMimeType(dataUri) {
|
|
54
|
+
const match = dataUri.match(/^data:([^;,]+)/);
|
|
55
|
+
return match?.[1]?.trim().toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
function normalizeOutputMimeType(mimeType) {
|
|
58
|
+
const m = mimeType.toLowerCase().trim();
|
|
59
|
+
if (m === "image/jpeg" || m === "image/jpg") {
|
|
13
60
|
return "image/jpeg";
|
|
14
61
|
}
|
|
15
|
-
if (m
|
|
62
|
+
if (m === "image/png") {
|
|
63
|
+
return "image/png";
|
|
64
|
+
}
|
|
65
|
+
if (m === "image/webp") {
|
|
16
66
|
return "image/webp";
|
|
17
67
|
}
|
|
18
|
-
|
|
68
|
+
throw new Error(`Image raster codec: unsupported output mime type "${mimeType}". ` + `Supported: ${SUPPORTED_OUTPUT_MIME_TYPES.join(", ")}.`);
|
|
19
69
|
}
|
|
70
|
+
|
|
71
|
+
// src/task/image/imageRasterCodecBrowser.ts
|
|
20
72
|
function arrayBufferToBase64(buffer) {
|
|
21
73
|
const bytes = new Uint8Array(buffer);
|
|
22
74
|
const CHUNK = 8192;
|
|
@@ -76,22 +128,32 @@ function get2dContext(width, height) {
|
|
|
76
128
|
throw new Error("No Canvas implementation available in this environment");
|
|
77
129
|
}
|
|
78
130
|
async function decodeDataUri(dataUri) {
|
|
131
|
+
assertIsDataUri(dataUri);
|
|
132
|
+
const declaredMime = extractDataUriMimeType(dataUri);
|
|
133
|
+
if (declaredMime && REJECTED_DECODE_MIME_TYPES.has(declaredMime)) {
|
|
134
|
+
throw new Error(`Image raster codec: refusing to rasterize "${declaredMime}". ` + `Vector and animated formats lose information when converted to pixels. ` + `Convert to PNG, JPEG, or WebP before passing to the codec.`);
|
|
135
|
+
}
|
|
79
136
|
const response = await fetch(dataUri);
|
|
80
137
|
const blob = await response.blob();
|
|
138
|
+
assertWithinByteBudget(blob.size, MAX_INPUT_BYTES_BROWSER);
|
|
81
139
|
const bmp = await createImageBitmap(blob);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
140
|
+
try {
|
|
141
|
+
assertWithinPixelBudget(bmp.width, bmp.height);
|
|
142
|
+
const { ctx } = get2dContext(bmp.width, bmp.height);
|
|
143
|
+
ctx.drawImage(bmp, 0, 0);
|
|
144
|
+
const id = ctx.getImageData(0, 0, bmp.width, bmp.height);
|
|
145
|
+
return {
|
|
146
|
+
data: new Uint8ClampedArray(id.data),
|
|
147
|
+
width: id.width,
|
|
148
|
+
height: id.height,
|
|
149
|
+
channels: 4
|
|
150
|
+
};
|
|
151
|
+
} finally {
|
|
152
|
+
bmp.close();
|
|
153
|
+
}
|
|
92
154
|
}
|
|
93
155
|
async function encodeDataUri(image, mimeType) {
|
|
94
|
-
const fmt =
|
|
156
|
+
const fmt = normalizeOutputMimeType(mimeType);
|
|
95
157
|
const { canvas, ctx } = get2dContext(image.width, image.height);
|
|
96
158
|
ctx.putImageData(rasterToImageData(image), 0, 0);
|
|
97
159
|
if (canvas instanceof OffscreenCanvas) {
|
|
@@ -659,6 +721,280 @@ Workflow10.prototype.multiply = CreateAdaptiveWorkflow(ScalarMultiplyTask, Vecto
|
|
|
659
721
|
Workflow10.prototype.divide = CreateAdaptiveWorkflow(ScalarDivideTask, VectorDivideTask);
|
|
660
722
|
Workflow10.prototype.sum = CreateAdaptiveWorkflow(ScalarSumTask, VectorSumTask);
|
|
661
723
|
|
|
724
|
+
// src/util/SafeFetch.ts
|
|
725
|
+
import { PermanentJobError } from "@workglow/job-queue";
|
|
726
|
+
|
|
727
|
+
// src/util/UrlClassifier.ts
|
|
728
|
+
import ipaddr from "ipaddr.js";
|
|
729
|
+
var PRIVATE_EXACT_HOSTS = new Set([
|
|
730
|
+
"localhost",
|
|
731
|
+
"localhost.localdomain",
|
|
732
|
+
"ip6-localhost",
|
|
733
|
+
"ip6-loopback",
|
|
734
|
+
"metadata.google.internal",
|
|
735
|
+
"metadata.internal",
|
|
736
|
+
"metadata.azure.com",
|
|
737
|
+
"instance-data"
|
|
738
|
+
]);
|
|
739
|
+
var PRIVATE_DOMAIN_SUFFIXES = [
|
|
740
|
+
"local",
|
|
741
|
+
"localhost",
|
|
742
|
+
"internal",
|
|
743
|
+
"lan",
|
|
744
|
+
"home.arpa",
|
|
745
|
+
"corp",
|
|
746
|
+
"intranet",
|
|
747
|
+
"private",
|
|
748
|
+
"localdomain"
|
|
749
|
+
];
|
|
750
|
+
var PRIVATE_IPV4_RANGES = new Set([
|
|
751
|
+
"unspecified",
|
|
752
|
+
"broadcast",
|
|
753
|
+
"multicast",
|
|
754
|
+
"linkLocal",
|
|
755
|
+
"loopback",
|
|
756
|
+
"carrierGradeNat",
|
|
757
|
+
"private",
|
|
758
|
+
"reserved",
|
|
759
|
+
"benchmarking"
|
|
760
|
+
]);
|
|
761
|
+
var PRIVATE_IPV6_RANGES = new Set([
|
|
762
|
+
"unspecified",
|
|
763
|
+
"linkLocal",
|
|
764
|
+
"multicast",
|
|
765
|
+
"loopback",
|
|
766
|
+
"uniqueLocal",
|
|
767
|
+
"ipv4Mapped",
|
|
768
|
+
"ipv4Compat",
|
|
769
|
+
"rfc6145",
|
|
770
|
+
"rfc6052",
|
|
771
|
+
"6to4",
|
|
772
|
+
"teredo",
|
|
773
|
+
"reserved",
|
|
774
|
+
"benchmarking",
|
|
775
|
+
"amt",
|
|
776
|
+
"as112v6",
|
|
777
|
+
"deprecated",
|
|
778
|
+
"orchid2",
|
|
779
|
+
"droneRemoteIdProtocolEntityTags"
|
|
780
|
+
]);
|
|
781
|
+
function tryNormalizeIPv4(host) {
|
|
782
|
+
if (host.length === 0)
|
|
783
|
+
return;
|
|
784
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) {
|
|
785
|
+
const octets = host.split(".").map((s) => parseInt(s, 10));
|
|
786
|
+
if (octets.every((n) => n >= 0 && n <= 255)) {
|
|
787
|
+
return octets.join(".");
|
|
788
|
+
}
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const parts = host.split(".");
|
|
792
|
+
if (parts.length < 1 || parts.length > 4)
|
|
793
|
+
return;
|
|
794
|
+
const nums = [];
|
|
795
|
+
for (const p of parts) {
|
|
796
|
+
if (p.length === 0)
|
|
797
|
+
return;
|
|
798
|
+
let n;
|
|
799
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(p)) {
|
|
800
|
+
n = parseInt(p.slice(2), 16);
|
|
801
|
+
} else if (/^0[0-7]+$/.test(p)) {
|
|
802
|
+
n = parseInt(p, 8);
|
|
803
|
+
} else if (/^\d+$/.test(p)) {
|
|
804
|
+
n = parseInt(p, 10);
|
|
805
|
+
} else {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (!Number.isFinite(n) || n < 0)
|
|
809
|
+
return;
|
|
810
|
+
nums.push(n);
|
|
811
|
+
}
|
|
812
|
+
let addr;
|
|
813
|
+
if (nums.length === 1) {
|
|
814
|
+
if (nums[0] > 4294967295)
|
|
815
|
+
return;
|
|
816
|
+
addr = nums[0];
|
|
817
|
+
} else if (nums.length === 2) {
|
|
818
|
+
if (nums[0] > 255 || nums[1] > 16777215)
|
|
819
|
+
return;
|
|
820
|
+
addr = nums[0] * 16777216 + nums[1];
|
|
821
|
+
} else if (nums.length === 3) {
|
|
822
|
+
if (nums[0] > 255 || nums[1] > 255 || nums[2] > 65535)
|
|
823
|
+
return;
|
|
824
|
+
addr = nums[0] * 16777216 + nums[1] * 65536 + nums[2];
|
|
825
|
+
} else {
|
|
826
|
+
if (nums.some((n) => n > 255))
|
|
827
|
+
return;
|
|
828
|
+
addr = nums[0] * 16777216 + nums[1] * 65536 + nums[2] * 256 + nums[3];
|
|
829
|
+
}
|
|
830
|
+
const o1 = Math.floor(addr / 16777216) & 255;
|
|
831
|
+
const o2 = Math.floor(addr / 65536) & 255;
|
|
832
|
+
const o3 = Math.floor(addr / 256) & 255;
|
|
833
|
+
const o4 = addr & 255;
|
|
834
|
+
return `${o1}.${o2}.${o3}.${o4}`;
|
|
835
|
+
}
|
|
836
|
+
function classifyIpLiteral(host) {
|
|
837
|
+
if (host.includes(":")) {
|
|
838
|
+
let ipv6;
|
|
839
|
+
try {
|
|
840
|
+
ipv6 = ipaddr.IPv6.parse(host);
|
|
841
|
+
} catch {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (ipv6.isIPv4MappedAddress()) {
|
|
845
|
+
const v4 = ipv6.toIPv4Address();
|
|
846
|
+
const range3 = v4.range();
|
|
847
|
+
const canonical3 = v4.toNormalizedString();
|
|
848
|
+
if (PRIVATE_IPV4_RANGES.has(range3)) {
|
|
849
|
+
return { kind: "private", reason: `IPv4-mapped IPv6 in ${range3} range`, canonical: canonical3 };
|
|
850
|
+
}
|
|
851
|
+
return { kind: "public", canonical: canonical3 };
|
|
852
|
+
}
|
|
853
|
+
const range2 = ipv6.range();
|
|
854
|
+
const canonical2 = ipv6.toNormalizedString();
|
|
855
|
+
if (PRIVATE_IPV6_RANGES.has(range2)) {
|
|
856
|
+
return { kind: "private", reason: `IPv6 in ${range2} range`, canonical: canonical2 };
|
|
857
|
+
}
|
|
858
|
+
return { kind: "public", canonical: canonical2 };
|
|
859
|
+
}
|
|
860
|
+
const canonical = tryNormalizeIPv4(host);
|
|
861
|
+
if (canonical === undefined)
|
|
862
|
+
return;
|
|
863
|
+
let ipv4;
|
|
864
|
+
try {
|
|
865
|
+
ipv4 = ipaddr.IPv4.parse(canonical);
|
|
866
|
+
} catch {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const range = ipv4.range();
|
|
870
|
+
if (PRIVATE_IPV4_RANGES.has(range)) {
|
|
871
|
+
return { kind: "private", reason: `IPv4 in ${range} range`, canonical };
|
|
872
|
+
}
|
|
873
|
+
return { kind: "public", canonical };
|
|
874
|
+
}
|
|
875
|
+
function normalizeHost(host) {
|
|
876
|
+
let h = host;
|
|
877
|
+
if (h.startsWith("[") && h.endsWith("]")) {
|
|
878
|
+
h = h.slice(1, -1);
|
|
879
|
+
}
|
|
880
|
+
while (h.endsWith(".")) {
|
|
881
|
+
h = h.slice(0, -1);
|
|
882
|
+
}
|
|
883
|
+
return h.toLowerCase();
|
|
884
|
+
}
|
|
885
|
+
function matchesPrivateHostnamePattern(host) {
|
|
886
|
+
if (PRIVATE_EXACT_HOSTS.has(host)) {
|
|
887
|
+
return `host '${host}' is a reserved private name`;
|
|
888
|
+
}
|
|
889
|
+
for (const suffix of PRIVATE_DOMAIN_SUFFIXES) {
|
|
890
|
+
if (host === suffix || host.endsWith("." + suffix)) {
|
|
891
|
+
return `host matches private suffix '.${suffix}'`;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
function classifyUrl(urlStr) {
|
|
897
|
+
if (typeof urlStr !== "string" || urlStr.length === 0) {
|
|
898
|
+
return { kind: "invalid", reason: "empty or non-string URL" };
|
|
899
|
+
}
|
|
900
|
+
let parsed;
|
|
901
|
+
try {
|
|
902
|
+
parsed = new URL(urlStr);
|
|
903
|
+
} catch {
|
|
904
|
+
return { kind: "invalid", reason: "malformed URL" };
|
|
905
|
+
}
|
|
906
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
907
|
+
return { kind: "invalid", reason: `unsupported protocol '${parsed.protocol}'` };
|
|
908
|
+
}
|
|
909
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
910
|
+
return { kind: "invalid", reason: "URL credentials are not allowed" };
|
|
911
|
+
}
|
|
912
|
+
const host = normalizeHost(parsed.hostname);
|
|
913
|
+
if (host.length === 0) {
|
|
914
|
+
return { kind: "invalid", reason: "empty host" };
|
|
915
|
+
}
|
|
916
|
+
const ipClassification = classifyIpLiteral(host);
|
|
917
|
+
if (ipClassification !== undefined) {
|
|
918
|
+
return {
|
|
919
|
+
kind: ipClassification.kind,
|
|
920
|
+
reason: ipClassification.reason,
|
|
921
|
+
host,
|
|
922
|
+
literalIp: ipClassification.canonical
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
const hostnameReason = matchesPrivateHostnamePattern(host);
|
|
926
|
+
if (hostnameReason !== undefined) {
|
|
927
|
+
return { kind: "private", reason: hostnameReason, host };
|
|
928
|
+
}
|
|
929
|
+
return { kind: "public", host };
|
|
930
|
+
}
|
|
931
|
+
function urlResourcePattern(urlStr) {
|
|
932
|
+
let parsed;
|
|
933
|
+
try {
|
|
934
|
+
parsed = new URL(urlStr);
|
|
935
|
+
} catch {
|
|
936
|
+
return urlStr;
|
|
937
|
+
}
|
|
938
|
+
const origin = parsed.port.length > 0 ? `${parsed.protocol}//${parsed.host}` : `${parsed.protocol}//${parsed.hostname}`;
|
|
939
|
+
return `${origin}/*`;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// src/util/SafeFetch.ts
|
|
943
|
+
var MAX_REDIRECT_HOPS = 20;
|
|
944
|
+
function assertAllowedUrl(url, allowPrivate) {
|
|
945
|
+
const classification = classifyUrl(url);
|
|
946
|
+
if (classification.kind === "invalid") {
|
|
947
|
+
throw new PermanentJobError(`Refusing to fetch invalid URL: ${classification.reason}`);
|
|
948
|
+
}
|
|
949
|
+
if (classification.kind === "private" && !allowPrivate) {
|
|
950
|
+
throw new PermanentJobError(`Refusing to fetch private/internal URL ${url}: ${classification.reason}. ` + `Grant the 'network:private' entitlement to allow this request.`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function isRedirectStatus(status) {
|
|
954
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
955
|
+
}
|
|
956
|
+
async function defaultSafeFetch(url, options) {
|
|
957
|
+
const requestedRedirectMode = options.redirect ?? "follow";
|
|
958
|
+
const { allowPrivate, redirect: _redirect, ...fetchOptions } = options;
|
|
959
|
+
let currentUrl = url;
|
|
960
|
+
for (let hops = 0;hops <= MAX_REDIRECT_HOPS; hops += 1) {
|
|
961
|
+
assertAllowedUrl(currentUrl, allowPrivate);
|
|
962
|
+
const response = await globalThis.fetch(currentUrl, {
|
|
963
|
+
...fetchOptions,
|
|
964
|
+
redirect: "manual"
|
|
965
|
+
});
|
|
966
|
+
if (!isRedirectStatus(response.status)) {
|
|
967
|
+
return response;
|
|
968
|
+
}
|
|
969
|
+
if (requestedRedirectMode === "manual") {
|
|
970
|
+
return response;
|
|
971
|
+
}
|
|
972
|
+
if (requestedRedirectMode === "error") {
|
|
973
|
+
throw new TypeError(`Fetch for ${currentUrl} failed because redirect mode was set to 'error'.`);
|
|
974
|
+
}
|
|
975
|
+
const location = response.headers.get("location");
|
|
976
|
+
if (!location) {
|
|
977
|
+
throw new PermanentJobError(`Refusing to follow redirect from ${currentUrl}: missing Location header.`);
|
|
978
|
+
}
|
|
979
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
980
|
+
}
|
|
981
|
+
throw new PermanentJobError(`Refusing to fetch ${url}: too many redirects.`);
|
|
982
|
+
}
|
|
983
|
+
var currentImpl = defaultSafeFetch;
|
|
984
|
+
function registerSafeFetch(fn) {
|
|
985
|
+
const previousImpl = currentImpl;
|
|
986
|
+
currentImpl = fn;
|
|
987
|
+
return previousImpl;
|
|
988
|
+
}
|
|
989
|
+
function getSafeFetchImpl() {
|
|
990
|
+
return currentImpl;
|
|
991
|
+
}
|
|
992
|
+
function resetSafeFetch() {
|
|
993
|
+
currentImpl = defaultSafeFetch;
|
|
994
|
+
}
|
|
995
|
+
function safeFetch(url, options = {}) {
|
|
996
|
+
return currentImpl(url, options);
|
|
997
|
+
}
|
|
662
998
|
// src/mcp-server/getMcpServerConfig.ts
|
|
663
999
|
function getMcpServerConfig(configOrInput) {
|
|
664
1000
|
const server = configOrInput.server;
|
|
@@ -1846,7 +2182,7 @@ Workflow13.prototype.delay = CreateWorkflow12(DelayTask);
|
|
|
1846
2182
|
import {
|
|
1847
2183
|
AbortSignalJobError,
|
|
1848
2184
|
Job,
|
|
1849
|
-
PermanentJobError,
|
|
2185
|
+
PermanentJobError as PermanentJobError2,
|
|
1850
2186
|
RetryableJobError
|
|
1851
2187
|
} from "@workglow/job-queue";
|
|
1852
2188
|
import {
|
|
@@ -1855,52 +2191,13 @@ import {
|
|
|
1855
2191
|
getJobQueueFactory,
|
|
1856
2192
|
getTaskQueueRegistry,
|
|
1857
2193
|
JobTaskFailedError,
|
|
2194
|
+
mergeEntitlements,
|
|
1858
2195
|
Task as Task13,
|
|
1859
2196
|
TaskConfigSchema as TaskConfigSchema3,
|
|
1860
2197
|
TaskConfigurationError,
|
|
1861
2198
|
TaskInvalidInputError as TaskInvalidInputError2,
|
|
1862
2199
|
Workflow as Workflow14
|
|
1863
2200
|
} from "@workglow/task-graph";
|
|
1864
|
-
var PRIVATE_IP_RANGES = [
|
|
1865
|
-
/^127\./,
|
|
1866
|
-
/^10\./,
|
|
1867
|
-
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
1868
|
-
/^192\.168\./,
|
|
1869
|
-
/^169\.254\./,
|
|
1870
|
-
/^0\./,
|
|
1871
|
-
/^fc00:/i,
|
|
1872
|
-
/^fe80:/i,
|
|
1873
|
-
/^::1$/,
|
|
1874
|
-
/^::$/
|
|
1875
|
-
];
|
|
1876
|
-
var PRIVATE_HOSTNAMES = new Set(["localhost", "metadata.google.internal", "metadata.internal"]);
|
|
1877
|
-
function isAllowPrivateUrlsEnvSet() {
|
|
1878
|
-
if (globalThis?.process?.env?.WORKGLOW_ALLOW_PRIVATE_URLS === "true") {
|
|
1879
|
-
return true;
|
|
1880
|
-
}
|
|
1881
|
-
const viteEnv = import.meta.env;
|
|
1882
|
-
return viteEnv?.VITE_WORKGLOW_ALLOW_PRIVATE_URLS === "true";
|
|
1883
|
-
}
|
|
1884
|
-
function isPrivateUrl(urlStr) {
|
|
1885
|
-
if (isAllowPrivateUrlsEnvSet()) {
|
|
1886
|
-
return false;
|
|
1887
|
-
}
|
|
1888
|
-
try {
|
|
1889
|
-
const parsed = new URL(urlStr);
|
|
1890
|
-
const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1891
|
-
if (PRIVATE_HOSTNAMES.has(hostname) || hostname.endsWith(".local")) {
|
|
1892
|
-
return true;
|
|
1893
|
-
}
|
|
1894
|
-
for (const range of PRIVATE_IP_RANGES) {
|
|
1895
|
-
if (range.test(hostname)) {
|
|
1896
|
-
return true;
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
return false;
|
|
1900
|
-
} catch {
|
|
1901
|
-
return false;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
2201
|
var inputSchema13 = {
|
|
1905
2202
|
type: "object",
|
|
1906
2203
|
properties: {
|
|
@@ -1988,7 +2285,7 @@ async function fetchWithProgress(url, options = {}, onProgress) {
|
|
|
1988
2285
|
if (!options.signal) {
|
|
1989
2286
|
throw new TaskConfigurationError("An AbortSignal must be provided.");
|
|
1990
2287
|
}
|
|
1991
|
-
const response = await
|
|
2288
|
+
const response = await safeFetch(url, options);
|
|
1992
2289
|
if (!response.body) {
|
|
1993
2290
|
throw new Error("ReadableStream not supported in this environment.");
|
|
1994
2291
|
}
|
|
@@ -2037,14 +2334,16 @@ async function fetchWithProgress(url, options = {}, onProgress) {
|
|
|
2037
2334
|
class FetchUrlJob extends Job {
|
|
2038
2335
|
static type = "FetchUrlJob";
|
|
2039
2336
|
async execute(input, context) {
|
|
2040
|
-
|
|
2041
|
-
|
|
2337
|
+
const classification = classifyUrl(input.url);
|
|
2338
|
+
if (classification.kind === "invalid") {
|
|
2339
|
+
throw new PermanentJobError2(`Refusing to fetch invalid URL ${input.url}: ${classification.reason ?? "malformed"}`);
|
|
2042
2340
|
}
|
|
2043
2341
|
const response = await fetchWithProgress(input.url, {
|
|
2044
2342
|
method: input.method,
|
|
2045
2343
|
headers: input.headers,
|
|
2046
2344
|
body: input.body,
|
|
2047
|
-
signal: context.signal
|
|
2345
|
+
signal: context.signal,
|
|
2346
|
+
allowPrivate: classification.kind === "private"
|
|
2048
2347
|
}, async (progress) => await context.updateProgress(progress));
|
|
2049
2348
|
if (response.ok) {
|
|
2050
2349
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -2097,7 +2396,7 @@ class FetchUrlJob extends Job {
|
|
|
2097
2396
|
}
|
|
2098
2397
|
throw new RetryableJobError(`Failed to fetch ${input.url}: ${response.status} ${response.statusText}`, retryDate);
|
|
2099
2398
|
} else {
|
|
2100
|
-
throw new
|
|
2399
|
+
throw new PermanentJobError2(`Failed to fetch ${input.url}: ${response.status} ${response.statusText}`);
|
|
2101
2400
|
}
|
|
2102
2401
|
}
|
|
2103
2402
|
}
|
|
@@ -2121,6 +2420,7 @@ class FetchUrlTask extends Task13 {
|
|
|
2121
2420
|
static title = "Fetch";
|
|
2122
2421
|
static description = "Fetches data from a URL with progress tracking and automatic retry handling";
|
|
2123
2422
|
static hasDynamicSchemas = true;
|
|
2423
|
+
static hasDynamicEntitlements = true;
|
|
2124
2424
|
static entitlements() {
|
|
2125
2425
|
return {
|
|
2126
2426
|
entitlements: [
|
|
@@ -2133,6 +2433,33 @@ class FetchUrlTask extends Task13 {
|
|
|
2133
2433
|
]
|
|
2134
2434
|
};
|
|
2135
2435
|
}
|
|
2436
|
+
entitlements() {
|
|
2437
|
+
const base = FetchUrlTask.entitlements();
|
|
2438
|
+
const url = this.runInputData?.url;
|
|
2439
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
2440
|
+
return mergeEntitlements(base, {
|
|
2441
|
+
entitlements: [
|
|
2442
|
+
{
|
|
2443
|
+
id: Entitlements.NETWORK_PRIVATE,
|
|
2444
|
+
reason: "Runtime URL is not yet available during entitlement evaluation; private/internal destinations must be explicitly allowed"
|
|
2445
|
+
}
|
|
2446
|
+
]
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
const classification = classifyUrl(url);
|
|
2450
|
+
if (classification.kind !== "private") {
|
|
2451
|
+
return base;
|
|
2452
|
+
}
|
|
2453
|
+
return mergeEntitlements(base, {
|
|
2454
|
+
entitlements: [
|
|
2455
|
+
{
|
|
2456
|
+
id: Entitlements.NETWORK_PRIVATE,
|
|
2457
|
+
reason: `URL targets private/internal host: ${classification.reason ?? classification.host ?? "unknown"}`,
|
|
2458
|
+
resources: [urlResourcePattern(url)]
|
|
2459
|
+
}
|
|
2460
|
+
]
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2136
2463
|
static configSchema() {
|
|
2137
2464
|
return fetchUrlTaskConfigSchema;
|
|
2138
2465
|
}
|
|
@@ -8832,7 +9159,7 @@ Workflow38.prototype.lambda = CreateWorkflow37(LambdaTask);
|
|
|
8832
9159
|
import {
|
|
8833
9160
|
CreateWorkflow as CreateWorkflow38,
|
|
8834
9161
|
Entitlements as Entitlements3,
|
|
8835
|
-
mergeEntitlements,
|
|
9162
|
+
mergeEntitlements as mergeEntitlements2,
|
|
8836
9163
|
Task as Task37,
|
|
8837
9164
|
Workflow as Workflow39
|
|
8838
9165
|
} from "@workglow/task-graph";
|
|
@@ -9027,13 +9354,13 @@ class McpListTask extends Task37 {
|
|
|
9027
9354
|
const base = McpListTask.entitlements();
|
|
9028
9355
|
const transport = getMcpServerTransport(this);
|
|
9029
9356
|
if (transport === "stdio") {
|
|
9030
|
-
return
|
|
9357
|
+
return mergeEntitlements2(base, {
|
|
9031
9358
|
entitlements: [
|
|
9032
9359
|
{ id: Entitlements3.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9033
9360
|
]
|
|
9034
9361
|
});
|
|
9035
9362
|
}
|
|
9036
|
-
return
|
|
9363
|
+
return mergeEntitlements2(base, {
|
|
9037
9364
|
entitlements: [{ id: Entitlements3.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" }]
|
|
9038
9365
|
});
|
|
9039
9366
|
}
|
|
@@ -9120,7 +9447,7 @@ Workflow39.prototype.mcpList = CreateWorkflow38(McpListTask);
|
|
|
9120
9447
|
import {
|
|
9121
9448
|
CreateWorkflow as CreateWorkflow39,
|
|
9122
9449
|
Entitlements as Entitlements4,
|
|
9123
|
-
mergeEntitlements as
|
|
9450
|
+
mergeEntitlements as mergeEntitlements3,
|
|
9124
9451
|
Task as Task38,
|
|
9125
9452
|
TaskConfigSchema as TaskConfigSchema8,
|
|
9126
9453
|
Workflow as Workflow40
|
|
@@ -9276,13 +9603,13 @@ class McpPromptGetTask extends Task38 {
|
|
|
9276
9603
|
const base = McpPromptGetTask.entitlements();
|
|
9277
9604
|
const transport = getMcpServerTransport(this);
|
|
9278
9605
|
if (transport === "stdio") {
|
|
9279
|
-
return
|
|
9606
|
+
return mergeEntitlements3(base, {
|
|
9280
9607
|
entitlements: [
|
|
9281
9608
|
{ id: Entitlements4.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9282
9609
|
]
|
|
9283
9610
|
});
|
|
9284
9611
|
}
|
|
9285
|
-
return
|
|
9612
|
+
return mergeEntitlements3(base, {
|
|
9286
9613
|
entitlements: [
|
|
9287
9614
|
{ id: Entitlements4.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" },
|
|
9288
9615
|
{ id: Entitlements4.CREDENTIAL, reason: "May require authentication", optional: true }
|
|
@@ -9386,7 +9713,7 @@ Workflow40.prototype.mcpPromptGet = CreateWorkflow39(McpPromptGetTask);
|
|
|
9386
9713
|
import {
|
|
9387
9714
|
CreateWorkflow as CreateWorkflow40,
|
|
9388
9715
|
Entitlements as Entitlements5,
|
|
9389
|
-
mergeEntitlements as
|
|
9716
|
+
mergeEntitlements as mergeEntitlements4,
|
|
9390
9717
|
Task as Task39,
|
|
9391
9718
|
TaskConfigSchema as TaskConfigSchema9,
|
|
9392
9719
|
Workflow as Workflow41
|
|
@@ -9455,13 +9782,13 @@ class McpResourceReadTask extends Task39 {
|
|
|
9455
9782
|
const base = McpResourceReadTask.entitlements();
|
|
9456
9783
|
const transport = getMcpServerTransport(this);
|
|
9457
9784
|
if (transport === "stdio") {
|
|
9458
|
-
return
|
|
9785
|
+
return mergeEntitlements4(base, {
|
|
9459
9786
|
entitlements: [
|
|
9460
9787
|
{ id: Entitlements5.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9461
9788
|
]
|
|
9462
9789
|
});
|
|
9463
9790
|
}
|
|
9464
|
-
return
|
|
9791
|
+
return mergeEntitlements4(base, {
|
|
9465
9792
|
entitlements: [
|
|
9466
9793
|
{ id: Entitlements5.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" },
|
|
9467
9794
|
{ id: Entitlements5.CREDENTIAL, reason: "May require authentication", optional: true }
|
|
@@ -9687,7 +10014,7 @@ Workflow42.prototype.mcpSearch = CreateWorkflow41(McpSearchTask);
|
|
|
9687
10014
|
import {
|
|
9688
10015
|
CreateWorkflow as CreateWorkflow42,
|
|
9689
10016
|
Entitlements as Entitlements7,
|
|
9690
|
-
mergeEntitlements as
|
|
10017
|
+
mergeEntitlements as mergeEntitlements5,
|
|
9691
10018
|
Task as Task41,
|
|
9692
10019
|
TaskConfigSchema as TaskConfigSchema10,
|
|
9693
10020
|
Workflow as Workflow43
|
|
@@ -9835,13 +10162,13 @@ class McpToolCallTask extends Task41 {
|
|
|
9835
10162
|
const base = McpToolCallTask.entitlements();
|
|
9836
10163
|
const transport = getMcpServerTransport(this);
|
|
9837
10164
|
if (transport === "stdio") {
|
|
9838
|
-
return
|
|
10165
|
+
return mergeEntitlements5(base, {
|
|
9839
10166
|
entitlements: [
|
|
9840
10167
|
{ id: Entitlements7.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9841
10168
|
]
|
|
9842
10169
|
});
|
|
9843
10170
|
}
|
|
9844
|
-
return
|
|
10171
|
+
return mergeEntitlements5(base, {
|
|
9845
10172
|
entitlements: [
|
|
9846
10173
|
{ id: Entitlements7.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" },
|
|
9847
10174
|
{ id: Entitlements7.CREDENTIAL, reason: "May require authentication", optional: true }
|
|
@@ -11934,18 +12261,24 @@ var registerCommonTasks2 = () => {
|
|
|
11934
12261
|
return [...tasks, FileLoaderTask];
|
|
11935
12262
|
};
|
|
11936
12263
|
export {
|
|
12264
|
+
urlResourcePattern,
|
|
12265
|
+
tryNormalizeIPv4,
|
|
11937
12266
|
split,
|
|
11938
12267
|
setGlobalMcpServerRepository,
|
|
11939
12268
|
searchMcpRegistryPage,
|
|
11940
12269
|
searchMcpRegistry,
|
|
12270
|
+
safeFetch,
|
|
11941
12271
|
resolveImageInput,
|
|
11942
12272
|
resolveHumanConnector,
|
|
11943
12273
|
resolveAuthSecrets,
|
|
12274
|
+
resetSafeFetch,
|
|
12275
|
+
registerSafeFetch,
|
|
11944
12276
|
registerMcpTaskDeps,
|
|
11945
12277
|
registerMcpServer,
|
|
11946
12278
|
registerImageRasterCodec,
|
|
11947
12279
|
registerCommonTasks2 as registerCommonTasks,
|
|
11948
12280
|
produceImageOutput,
|
|
12281
|
+
normalizeOutputMimeType,
|
|
11949
12282
|
merge,
|
|
11950
12283
|
mcpTransportTypes,
|
|
11951
12284
|
mcpToolCall,
|
|
@@ -11963,6 +12296,7 @@ export {
|
|
|
11963
12296
|
json,
|
|
11964
12297
|
javaScript,
|
|
11965
12298
|
isDataUriImage,
|
|
12299
|
+
getSafeFetchImpl,
|
|
11966
12300
|
getMcpTaskDeps,
|
|
11967
12301
|
getMcpServerConfig,
|
|
11968
12302
|
getMcpServer,
|
|
@@ -11972,11 +12306,17 @@ export {
|
|
|
11972
12306
|
formatImageOutput,
|
|
11973
12307
|
fileLoader,
|
|
11974
12308
|
fetchUrl,
|
|
12309
|
+
extractDataUriMimeType,
|
|
11975
12310
|
delay,
|
|
11976
12311
|
debugLog,
|
|
11977
12312
|
createMcpClient,
|
|
11978
12313
|
createAuthProvider,
|
|
12314
|
+
classifyUrl,
|
|
12315
|
+
classifyIpLiteral,
|
|
11979
12316
|
buildAuthConfig,
|
|
12317
|
+
assertWithinPixelBudget,
|
|
12318
|
+
assertWithinByteBudget,
|
|
12319
|
+
assertIsDataUri,
|
|
11980
12320
|
VectorSumTask,
|
|
11981
12321
|
VectorSubtractTask,
|
|
11982
12322
|
VectorScaleTask,
|
|
@@ -12011,7 +12351,9 @@ export {
|
|
|
12011
12351
|
ScalarCeilTask,
|
|
12012
12352
|
ScalarAddTask,
|
|
12013
12353
|
ScalarAbsTask,
|
|
12354
|
+
SUPPORTED_OUTPUT_MIME_TYPES,
|
|
12014
12355
|
RegexTask,
|
|
12356
|
+
REJECTED_DECODE_MIME_TYPES,
|
|
12015
12357
|
OutputTask,
|
|
12016
12358
|
MergeTask,
|
|
12017
12359
|
McpToolCallTask,
|
|
@@ -12026,6 +12368,9 @@ export {
|
|
|
12026
12368
|
MCP_TASK_DEPS,
|
|
12027
12369
|
MCP_SERVER_REPOSITORY,
|
|
12028
12370
|
MCP_SERVERS,
|
|
12371
|
+
MAX_INPUT_BYTES_NODE,
|
|
12372
|
+
MAX_INPUT_BYTES_BROWSER,
|
|
12373
|
+
MAX_DECODED_PIXELS,
|
|
12029
12374
|
LambdaTask,
|
|
12030
12375
|
JsonTask,
|
|
12031
12376
|
JsonPathTask,
|
|
@@ -12066,4 +12411,4 @@ export {
|
|
|
12066
12411
|
ArrayTask
|
|
12067
12412
|
};
|
|
12068
12413
|
|
|
12069
|
-
//# debugId=
|
|
12414
|
+
//# debugId=D1A8BE957A811A1A64756E2164756E21
|