Skip to content

Commit f577408

Browse files
committed
fix(deps): stop later checks from reading an oversized container's unparsed elements
1 parent 87b761d commit f577408

2 files changed

Lines changed: 62 additions & 28 deletions

File tree

‎apps/sim/lib/knowledge/transfer/bundle.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ describe('knowledgeBundleManifestSchema', () => {
7171
).toBe(false)
7272
})
7373

74+
/** The tag cross-checks read each entry, so they must never see an oversized list unparsed. */
75+
it('refuses an oversized tag list on its size before the tag cross-checks read it', () => {
76+
const result = knowledgeBundleManifestSchema.safeParse(manifest({ tags: Array(100).fill({}) }))
77+
78+
expect(result.error?.issues).toEqual([
79+
expect.objectContaining({ code: 'too_big', path: ['tags'] }),
80+
])
81+
})
82+
7483
it('refuses duplicate tag slots and case-insensitively duplicate tag names', () => {
7584
expect(
7685
knowledgeBundleManifestSchema.safeParse(

‎patches/zod@4.3.6.patch‎

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
diff --git a/node_modules/zod/.bun-tag-82bf292cf1bc8e88 b/.bun-tag-82bf292cf1bc8e88
2+
new file mode 100644
3+
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
14
diff --git a/v4/core/schemas.cjs b/v4/core/schemas.cjs
2-
index 8444c1fa122b0502f56b141ffa2939906b49baf9..9a085e4beacdbe1ef84c4c6a30b471b4aa9f9625 100644
5+
index 8444c1fa122b0502f56b141ffa2939906b49baf9..b1dc056dfe24e7e17927b5b61cbf528fc3830eb8 100644
36
--- a/v4/core/schemas.cjs
47
+++ b/v4/core/schemas.cjs
5-
@@ -685,6 +685,37 @@ function handleArrayResult(result, final, index) {
8+
@@ -685,6 +685,48 @@ function handleArrayResult(result, final, index) {
69
}
710
final.value[index] = result.value;
811
}
912
+// Backport of colinhacks/zod#6652 (zod 4.6.6, GHSA-mrhx-c7xc-m3w5): a container larger than
10-
+// its leading upper size bound skips its elements, so that bound's check (which has a `when`
11-
+// guard and always runs) reports it without parsing and collecting an issue per element first.
13+
+// its leading upper size bound fails on that bound before any element is parsed, instead of
14+
+// collecting an issue per element first.
1215
+function sizeCaps(defChecks) {
1316
+ const caps = [];
1417
+ for (const ch of defChecks ?? []) {
@@ -18,20 +21,31 @@ index 8444c1fa122b0502f56b141ffa2939906b49baf9..9a085e4beacdbe1ef84c4c6a30b471b4
1821
+ break;
1922
+ const max = d.maximum ?? d.length ?? d.size;
2023
+ if (max >= 0)
21-
+ caps.push(max);
24+
+ caps.push([max, ch]);
2225
+ }
2326
+ return caps;
2427
+}
2528
+function capSize(inst, size) {
2629
+ const caps = sizeCaps(inst._zod.def.checks);
2730
+ if (!caps.length)
2831
+ return;
29-
+ const cap = Math.min(...caps);
30-
+ const parse = inst._zod.parse;
31-
+ inst._zod.parse = (payload, ctx) => {
32-
+ if (size(payload.value) > cap && !ctx?.skipChecks && ctx?.direction !== "backward")
33-
+ return payload;
34-
+ return parse(payload, ctx);
32+
+ const cap = Math.min(...caps.map((c) => c[0]));
33+
+ const run = inst._zod.run;
34+
+ // gates `run`, not `parse`: 4.3.6 runs `when`-guarded checks even after an abort, so the
35+
+ // failing bound reports once here and no later check reads the unparsed elements
36+
+ inst._zod.run = (payload, ctx) => {
37+
+ const n = size(payload.value);
38+
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
39+
+ for (const [max, ch] of caps) {
40+
+ if (n > max && ch._zod.def.when(payload)) {
41+
+ const start = payload.issues.length;
42+
+ ch._zod.check(payload);
43+
+ payload.issues[start].continue = false;
44+
+ return payload;
45+
+ }
46+
+ }
47+
+ }
48+
+ return run(payload, ctx);
3549
+ };
3650
+}
3751
+const arraySize = (input) => (Array.isArray(input) ? input.length : -1);
@@ -40,23 +54,23 @@ index 8444c1fa122b0502f56b141ffa2939906b49baf9..9a085e4beacdbe1ef84c4c6a30b471b4
4054
exports.$ZodArray = core.$constructor("$ZodArray", (inst, def) => {
4155
exports.$ZodType.init(inst, def);
4256
inst._zod.parse = (payload, ctx) => {
43-
@@ -718,6 +749,7 @@ exports.$ZodArray = core.$constructor("$ZodArray", (inst, def) => {
57+
@@ -718,6 +760,7 @@ exports.$ZodArray = core.$constructor("$ZodArray", (inst, def) => {
4458
}
4559
return payload; //handleArrayResultsAsync(parseResults, final);
4660
};
4761
+ capSize(inst, arraySize);
4862
});
4963
function handlePropertyResult(result, final, key, input, isOptionalOut) {
5064
if (result.issues.length) {
51-
@@ -1480,6 +1512,7 @@ exports.$ZodMap = core.$constructor("$ZodMap", (inst, def) => {
65+
@@ -1480,6 +1523,7 @@ exports.$ZodMap = core.$constructor("$ZodMap", (inst, def) => {
5266
return Promise.all(proms).then(() => payload);
5367
return payload;
5468
};
5569
+ capSize(inst, mapSize);
5670
});
5771
function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
5872
if (keyResult.issues.length) {
59-
@@ -1540,6 +1573,7 @@ exports.$ZodSet = core.$constructor("$ZodSet", (inst, def) => {
73+
@@ -1540,6 +1584,7 @@ exports.$ZodSet = core.$constructor("$ZodSet", (inst, def) => {
6074
return Promise.all(proms).then(() => payload);
6175
return payload;
6276
};
@@ -65,16 +79,16 @@ index 8444c1fa122b0502f56b141ffa2939906b49baf9..9a085e4beacdbe1ef84c4c6a30b471b4
6579
function handleSetResult(result, final) {
6680
if (result.issues.length) {
6781
diff --git a/v4/core/schemas.js b/v4/core/schemas.js
68-
index 29c61bc5fcf2ca90c2bb0121b7f02e16fdf19f11..f87884eea6e301f1c9c46be45953226e850e4795 100644
82+
index 29c61bc5fcf2ca90c2bb0121b7f02e16fdf19f11..1123f503df65ff9993a0e04a4c78dfb3e216b961 100644
6983
--- a/v4/core/schemas.js
7084
+++ b/v4/core/schemas.js
71-
@@ -654,6 +654,37 @@ function handleArrayResult(result, final, index) {
85+
@@ -654,6 +654,48 @@ function handleArrayResult(result, final, index) {
7286
}
7387
final.value[index] = result.value;
7488
}
7589
+// Backport of colinhacks/zod#6652 (zod 4.6.6, GHSA-mrhx-c7xc-m3w5): a container larger than
76-
+// its leading upper size bound skips its elements, so that bound's check (which has a `when`
77-
+// guard and always runs) reports it without parsing and collecting an issue per element first.
90+
+// its leading upper size bound fails on that bound before any element is parsed, instead of
91+
+// collecting an issue per element first.
7892
+function sizeCaps(defChecks) {
7993
+ const caps = [];
8094
+ for (const ch of defChecks ?? []) {
@@ -84,20 +98,31 @@ index 29c61bc5fcf2ca90c2bb0121b7f02e16fdf19f11..f87884eea6e301f1c9c46be45953226e
8498
+ break;
8599
+ const max = d.maximum ?? d.length ?? d.size;
86100
+ if (max >= 0)
87-
+ caps.push(max);
101+
+ caps.push([max, ch]);
88102
+ }
89103
+ return caps;
90104
+}
91105
+function capSize(inst, size) {
92106
+ const caps = sizeCaps(inst._zod.def.checks);
93107
+ if (!caps.length)
94108
+ return;
95-
+ const cap = Math.min(...caps);
96-
+ const parse = inst._zod.parse;
97-
+ inst._zod.parse = (payload, ctx) => {
98-
+ if (size(payload.value) > cap && !ctx?.skipChecks && ctx?.direction !== "backward")
99-
+ return payload;
100-
+ return parse(payload, ctx);
109+
+ const cap = Math.min(...caps.map((c) => c[0]));
110+
+ const run = inst._zod.run;
111+
+ // gates `run`, not `parse`: 4.3.6 runs `when`-guarded checks even after an abort, so the
112+
+ // failing bound reports once here and no later check reads the unparsed elements
113+
+ inst._zod.run = (payload, ctx) => {
114+
+ const n = size(payload.value);
115+
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
116+
+ for (const [max, ch] of caps) {
117+
+ if (n > max && ch._zod.def.when(payload)) {
118+
+ const start = payload.issues.length;
119+
+ ch._zod.check(payload);
120+
+ payload.issues[start].continue = false;
121+
+ return payload;
122+
+ }
123+
+ }
124+
+ }
125+
+ return run(payload, ctx);
101126
+ };
102127
+}
103128
+const arraySize = (input) => (Array.isArray(input) ? input.length : -1);
@@ -106,23 +131,23 @@ index 29c61bc5fcf2ca90c2bb0121b7f02e16fdf19f11..f87884eea6e301f1c9c46be45953226e
106131
export const $ZodArray = /*@__PURE__*/ core.$constructor("$ZodArray", (inst, def) => {
107132
$ZodType.init(inst, def);
108133
inst._zod.parse = (payload, ctx) => {
109-
@@ -687,6 +718,7 @@ export const $ZodArray = /*@__PURE__*/ core.$constructor("$ZodArray", (inst, def
134+
@@ -687,6 +729,7 @@ export const $ZodArray = /*@__PURE__*/ core.$constructor("$ZodArray", (inst, def
110135
}
111136
return payload; //handleArrayResultsAsync(parseResults, final);
112137
};
113138
+ capSize(inst, arraySize);
114139
});
115140
function handlePropertyResult(result, final, key, input, isOptionalOut) {
116141
if (result.issues.length) {
117-
@@ -1449,6 +1481,7 @@ export const $ZodMap = /*@__PURE__*/ core.$constructor("$ZodMap", (inst, def) =>
142+
@@ -1449,6 +1492,7 @@ export const $ZodMap = /*@__PURE__*/ core.$constructor("$ZodMap", (inst, def) =>
118143
return Promise.all(proms).then(() => payload);
119144
return payload;
120145
};
121146
+ capSize(inst, mapSize);
122147
});
123148
function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
124149
if (keyResult.issues.length) {
125-
@@ -1509,6 +1542,7 @@ export const $ZodSet = /*@__PURE__*/ core.$constructor("$ZodSet", (inst, def) =>
150+
@@ -1509,6 +1553,7 @@ export const $ZodSet = /*@__PURE__*/ core.$constructor("$ZodSet", (inst, def) =>
126151
return Promise.all(proms).then(() => payload);
127152
return payload;
128153
};

0 commit comments

Comments
 (0)