anyvali 0.3.3 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.5](https://github.com/BetterCorp/AnyVali/compare/js-v0.3.4...js-v0.3.5) (2026-06-17)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * infer default coercion source ([19577b3](https://github.com/BetterCorp/AnyVali/commit/19577b3d59e00e246fe2021c0b7e30e4196a5fa3))
9
+
10
+ ## [0.3.4](https://github.com/BetterCorp/AnyVali/compare/js-v0.3.3...js-v0.3.4) (2026-06-14)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **validation:** close cross-language coercion + regex-anchor bypasses ([5693efe](https://github.com/BetterCorp/AnyVali/commit/5693efe897c8289424c5e9ae897660a8e42f80bc))
16
+
3
17
  ## [0.3.3](https://github.com/BetterCorp/AnyVali/compare/js-v0.3.2...js-v0.3.3) (2026-06-03)
4
18
 
5
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../../src/parse/coerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACjC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,GACX,cAAc,CA4BhB;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,MAAM,GACjB,cAAc,CAqFhB"}
1
+ {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../../src/parse/coerce.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,MAAM,cAAc,GACtB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACjC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,GACX,cAAc,CA4BhB;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,MAAM,GACjB,cAAc,CAuGhB"}
@@ -1,3 +1,7 @@
1
+ // Decimal floating-point grammar: optional sign, digits with optional
2
+ // fraction (or bare fraction), optional decimal exponent. Excludes hex/octal/
3
+ // binary literals, Infinity and NaN that JS `Number()` would otherwise accept.
4
+ const DECIMAL_FLOAT_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
1
5
  /**
2
6
  * Normalize coercion config from the corpus/interchange format.
3
7
  * The corpus uses strings like "string->int", "trim", "lower", "upper"
@@ -43,8 +47,15 @@ export function applyCoercion(input, config, targetType) {
43
47
  value = value.toUpperCase();
44
48
  }
45
49
  }
46
- // Type coercion from string to target
47
- if (config.from === "string" && typeof value === "string") {
50
+ // Type coercion from string to target.
51
+ // The only portable coercion source is "string" (spec 5.1), so enabling
52
+ // coercion on a non-string target (e.g. `number().coerce()`) implies a
53
+ // string source even when `from` is omitted. Without this, a bare
54
+ // `.coerce()` on a numeric/bool schema would silently no-op and the raw
55
+ // string would fail validation with invalid_type.
56
+ const isTypeTarget = targetType !== "string" && targetType !== "unknown";
57
+ const fromString = config.from === "string" || (config.from === undefined && isTypeTarget);
58
+ if (fromString && typeof value === "string") {
48
59
  switch (targetType) {
49
60
  case "int":
50
61
  case "int8":
@@ -82,6 +93,16 @@ export function applyCoercion(input, config, targetType) {
82
93
  message: `Cannot coerce empty string to ${targetType}`,
83
94
  };
84
95
  }
96
+ // Spec 5.1: parse as DECIMAL floating-point. JS `Number()` also accepts
97
+ // hex (0x), octal (0o) and binary (0b) literals, which would let
98
+ // "0x10" slip through as 16 and bypass the decimal-only contract.
99
+ // Restrict to a decimal float grammar before parsing.
100
+ if (!DECIMAL_FLOAT_RE.test(trimmed)) {
101
+ return {
102
+ success: false,
103
+ message: `Cannot coerce "${value}" to ${targetType}`,
104
+ };
105
+ }
85
106
  const num = Number(trimmed);
86
107
  if (!Number.isFinite(num)) {
87
108
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"coerce.js","sourceRoot":"","sources":["../../src/parse/coerce.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"coerce.js","sourceRoot":"","sources":["../../src/parse/coerce.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"number.d.ts","sourceRoot":"","sources":["../../src/schemas/number.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAIvC,qBAAa,YAAa,SAAQ,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC;IAC1D,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC;IAC5B,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAEnB,IAAI,GAAE,UAAqB;IAKvC,kBAAkB,IAAI,MAAM;IAI5B,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAMpB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAMpB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM7B,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM7B,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM3B,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;IAgBrD,SAAS,CAAC,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI;IA0DpE,OAAO,IAAI,UAAU;CAYtB;AAED,qBAAa,aAAc,SAAQ,YAAY;;CAI9C;AAED,qBAAa,aAAc,SAAQ,YAAY;;CAI9C"}
1
+ {"version":3,"file":"number.d.ts","sourceRoot":"","sources":["../../src/schemas/number.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAOvC,qBAAa,YAAa,SAAQ,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC;IAC1D,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC;IAC5B,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAEnB,IAAI,GAAE,UAAqB;IAKvC,kBAAkB,IAAI,MAAM;IAI5B,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAMpB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAMpB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM7B,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM7B,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAM3B,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;IA8BrD,SAAS,CAAC,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI;IA0DpE,OAAO,IAAI,UAAU;CAYtB;AAED,qBAAa,aAAc,SAAQ,YAAY;;CAI9C;AAED,qBAAa,aAAc,SAAQ,YAAY;;CAI9C"}
@@ -1,6 +1,8 @@
1
1
  import { BaseSchema } from "./base.js";
2
2
  import { ISSUE_CODES } from "../issue-codes.js";
3
3
  import { describeType } from "../util.js";
4
+ /** Largest finite magnitude representable in IEEE 754 binary32. */
5
+ const FLOAT32_MAX = 3.4028234663852886e38;
4
6
  export class NumberSchema extends BaseSchema {
5
7
  _kind;
6
8
  _min;
@@ -51,6 +53,19 @@ export class NumberSchema extends BaseSchema {
51
53
  });
52
54
  return undefined;
53
55
  }
56
+ // float32 MUST reject values outside the binary32 representable range
57
+ // (spec 1.4). Without this, float32 silently accepts any float64 value,
58
+ // defeating the narrowing guarantee.
59
+ if (this._kind === "float32" && input !== 0 && Math.abs(input) > FLOAT32_MAX) {
60
+ ctx.issues.push({
61
+ code: ISSUE_CODES.TOO_LARGE,
62
+ message: `Value ${input} is outside the float32 range`,
63
+ path: [...ctx.path],
64
+ expected: "float32",
65
+ received: String(input),
66
+ });
67
+ return undefined;
68
+ }
54
69
  this._validateConstraints(input, ctx);
55
70
  return input;
56
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/schemas/number.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/schemas/number.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyvali",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Native validation with portable schema interchange",
5
5
  "homepage": "https://anyvali.com",
6
6
  "repository": {
@@ -1,5 +1,10 @@
1
1
  import type { CoercionConfig } from "../types.js";
2
2
 
3
+ // Decimal floating-point grammar: optional sign, digits with optional
4
+ // fraction (or bare fraction), optional decimal exponent. Excludes hex/octal/
5
+ // binary literals, Infinity and NaN that JS `Number()` would otherwise accept.
6
+ const DECIMAL_FLOAT_RE = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
7
+
3
8
  export type CoercionResult =
4
9
  | { success: true; value: unknown }
5
10
  | { success: false; message: string };
@@ -61,8 +66,16 @@ export function applyCoercion(
61
66
  }
62
67
  }
63
68
 
64
- // Type coercion from string to target
65
- if (config.from === "string" && typeof value === "string") {
69
+ // Type coercion from string to target.
70
+ // The only portable coercion source is "string" (spec 5.1), so enabling
71
+ // coercion on a non-string target (e.g. `number().coerce()`) implies a
72
+ // string source even when `from` is omitted. Without this, a bare
73
+ // `.coerce()` on a numeric/bool schema would silently no-op and the raw
74
+ // string would fail validation with invalid_type.
75
+ const isTypeTarget = targetType !== "string" && targetType !== "unknown";
76
+ const fromString =
77
+ config.from === "string" || (config.from === undefined && isTypeTarget);
78
+ if (fromString && typeof value === "string") {
66
79
  switch (targetType) {
67
80
  case "int":
68
81
  case "int8":
@@ -101,6 +114,16 @@ export function applyCoercion(
101
114
  message: `Cannot coerce empty string to ${targetType}`,
102
115
  };
103
116
  }
117
+ // Spec 5.1: parse as DECIMAL floating-point. JS `Number()` also accepts
118
+ // hex (0x), octal (0o) and binary (0b) literals, which would let
119
+ // "0x10" slip through as 16 and bypass the decimal-only contract.
120
+ // Restrict to a decimal float grammar before parsing.
121
+ if (!DECIMAL_FLOAT_RE.test(trimmed)) {
122
+ return {
123
+ success: false,
124
+ message: `Cannot coerce "${value}" to ${targetType}`,
125
+ };
126
+ }
104
127
  const num = Number(trimmed);
105
128
  if (!Number.isFinite(num)) {
106
129
  return {
@@ -3,6 +3,9 @@ import { BaseSchema } from "./base.js";
3
3
  import { ISSUE_CODES } from "../issue-codes.js";
4
4
  import { describeType } from "../util.js";
5
5
 
6
+ /** Largest finite magnitude representable in IEEE 754 binary32. */
7
+ const FLOAT32_MAX = 3.4028234663852886e38;
8
+
6
9
  export class NumberSchema extends BaseSchema<number, number> {
7
10
  protected _kind: SchemaKind;
8
11
  protected _min?: number;
@@ -62,6 +65,20 @@ export class NumberSchema extends BaseSchema<number, number> {
62
65
  return undefined;
63
66
  }
64
67
 
68
+ // float32 MUST reject values outside the binary32 representable range
69
+ // (spec 1.4). Without this, float32 silently accepts any float64 value,
70
+ // defeating the narrowing guarantee.
71
+ if (this._kind === "float32" && input !== 0 && Math.abs(input) > FLOAT32_MAX) {
72
+ ctx.issues.push({
73
+ code: ISSUE_CODES.TOO_LARGE,
74
+ message: `Value ${input} is outside the float32 range`,
75
+ path: [...ctx.path],
76
+ expected: "float32",
77
+ received: String(input),
78
+ });
79
+ return undefined;
80
+ }
81
+
65
82
  this._validateConstraints(input, ctx);
66
83
  return input;
67
84
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { number, float32, float64, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64 } from "../../src/index.js";
2
+ import { number, float32, float64, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64, object } from "../../src/index.js";
3
3
 
4
4
  describe("NumberSchema", () => {
5
5
  it("accepts valid numbers", () => {
@@ -61,6 +61,43 @@ describe("NumberSchema", () => {
61
61
  expect(result.success).toBe(false);
62
62
  if (!result.success) expect(result.issues[0].code).toBe("coercion_failed");
63
63
  });
64
+
65
+ // Bare `.coerce()` (no args) must imply string source on a numeric target.
66
+ // Regression: previously no-op'd, so a string input failed with invalid_type.
67
+ it("coerces string to number with no-arg coerce()", () => {
68
+ const s = number().coerce();
69
+ expect(s.parse("3.14")).toBe(3.14);
70
+ });
71
+
72
+ // Full string->number coercion matrix (no-arg form). ASCII decimal float
73
+ // incl. exponent, trimmed. No hex/Infinity/NaN/underscores.
74
+ describe("string->number matrix via no-arg coerce()", () => {
75
+ const s = number().coerce();
76
+
77
+ it.each([
78
+ ["3.14", 3.14],
79
+ ["-1.5e3", -1500],
80
+ [" 2 ", 2],
81
+ ["0", 0],
82
+ ])("accepts %j -> %j", (input, expected) => {
83
+ expect(s.parse(input as string)).toBe(expected);
84
+ });
85
+
86
+ it.each([
87
+ "0x10",
88
+ "Infinity",
89
+ "NaN",
90
+ "",
91
+ "1_000",
92
+ "abc",
93
+ ])("rejects %j with coercion_failed", (input) => {
94
+ const result = s.safeParse(input);
95
+ expect(result.success).toBe(false);
96
+ if (!result.success) {
97
+ expect(result.issues[0].code).toBe("coercion_failed");
98
+ }
99
+ });
100
+ });
64
101
  });
65
102
 
66
103
  describe("Float32Schema", () => {
@@ -102,6 +139,66 @@ describe("IntSchema", () => {
102
139
  const result = s.safeParse("3.14");
103
140
  expect(result.success).toBe(false);
104
141
  });
142
+
143
+ it("coerces string to int with no-arg coerce()", () => {
144
+ const s = int().coerce();
145
+ expect(s.parse("42")).toBe(42);
146
+ });
147
+
148
+ // Full string->int coercion matrix (no-arg form). ASCII `^-?\d+$`, trimmed.
149
+ describe("string->int matrix via no-arg coerce()", () => {
150
+ const s = int().coerce();
151
+
152
+ it.each([
153
+ ["42", 42],
154
+ [" 42 ", 42],
155
+ ["-7", -7],
156
+ ])("accepts %j -> %j", (input, expected) => {
157
+ expect(s.parse(input as string)).toBe(expected);
158
+ });
159
+
160
+ it.each([
161
+ "3.14",
162
+ "0x10",
163
+ "1_000",
164
+ "+5",
165
+ "Infinity",
166
+ "",
167
+ "abc",
168
+ ])("rejects %j with coercion_failed", (input) => {
169
+ const result = s.safeParse(input);
170
+ expect(result.success).toBe(false);
171
+ if (!result.success) {
172
+ expect(result.issues[0].code).toBe("coercion_failed");
173
+ }
174
+ });
175
+ });
176
+ });
177
+
178
+ // Reproduces the reported field-level coercion failure: an object whose
179
+ // numeric fields use bare `.coerce()` must coerce string inputs, not reject
180
+ // them with invalid_type.
181
+ describe("object with no-arg coerce() on numeric fields", () => {
182
+ it("coerces all string fields to numbers", () => {
183
+ const schema = object({
184
+ lumpSum: number().coerce().min(0),
185
+ monthlyContributions: number().coerce().min(0),
186
+ investmentTerm: number().coerce().min(1),
187
+ });
188
+ const result = schema.safeParse({
189
+ lumpSum: "1000000",
190
+ monthlyContributions: "1000",
191
+ investmentTerm: "20",
192
+ });
193
+ expect(result.success).toBe(true);
194
+ if (result.success) {
195
+ expect(result.data).toEqual({
196
+ lumpSum: 1000000,
197
+ monthlyContributions: 1000,
198
+ investmentTerm: 20,
199
+ });
200
+ }
201
+ });
105
202
  });
106
203
 
107
204
  describe("Int8Schema", () => {
@@ -35,6 +35,47 @@ describe("BoolSchema", () => {
35
35
  expect(result.success).toBe(false);
36
36
  if (!result.success) expect(result.issues[0].code).toBe("coercion_failed");
37
37
  });
38
+
39
+ it("coerces string to bool with no-arg coerce()", () => {
40
+ const s = bool().coerce();
41
+ expect(s.parse("true")).toBe(true);
42
+ expect(s.parse("false")).toBe(false);
43
+ });
44
+
45
+ // Full string->bool coercion matrix (no-arg form). Trim + case-insensitive.
46
+ // true <- "true"/"TRUE"/"1"; false <- "false"/"0". Nothing else.
47
+ describe("string->bool matrix via no-arg coerce()", () => {
48
+ const s = bool().coerce();
49
+
50
+ it.each([
51
+ ["true", true],
52
+ ["TRUE", true],
53
+ ["1", true],
54
+ [" true ", true],
55
+ ["false", false],
56
+ ["0", false],
57
+ [" FALSE ", false],
58
+ ])("accepts %j -> %j", (input, expected) => {
59
+ expect(s.parse(input as string)).toBe(expected);
60
+ });
61
+
62
+ it.each([
63
+ "yes",
64
+ "no",
65
+ "on",
66
+ "off",
67
+ "t",
68
+ "f",
69
+ "2",
70
+ "",
71
+ ])("rejects %j with coercion_failed", (input) => {
72
+ const result = s.safeParse(input);
73
+ expect(result.success).toBe(false);
74
+ if (!result.success) {
75
+ expect(result.issues[0].code).toBe("coercion_failed");
76
+ }
77
+ });
78
+ });
38
79
  });
39
80
 
40
81
  describe("NullSchema", () => {
@@ -61,6 +61,41 @@ describe("CVE-2016-4055 / CVE-2022-25883 - ReDoS catastrophic backtracking", ()
61
61
  });
62
62
  });
63
63
 
64
+ // ---------------------------------------------------------------------------
65
+ // 1b. Regex anchor newline bypass - CWE-20 / spec 3.1
66
+ // ---------------------------------------------------------------------------
67
+ // ECMA-262 is the portable baseline: "^"/"$" without the multiline flag match
68
+ // only the start/end of the whole string. JS already enforces this; these tests
69
+ // lock the invariant so it stays consistent with the other SDKs (which rewrite
70
+ // "^"/"$" to absolute anchors). A trailing-newline match would be a whitelist
71
+ // (newline/CRLF/log-injection) bypass.
72
+ describe("CWE-20 - regex anchor newline bypass", () => {
73
+ it("$ anchor does not match before a trailing newline", () => {
74
+ const s = string().pattern("^[a-z]+$");
75
+ expect(s.safeParse("abc").success).toBe(true);
76
+ expect(s.safeParse("abc\n").success).toBe(false);
77
+ expect(s.safeParse("abc\nEVIL").success).toBe(false);
78
+ });
79
+
80
+ it("^ anchor is string-start, not line-start", () => {
81
+ const s = string().pattern("^admin$");
82
+ expect(s.safeParse("admin").success).toBe(true);
83
+ expect(s.safeParse("x\nadmin").success).toBe(false);
84
+ expect(s.safeParse("admin\n").success).toBe(false);
85
+ });
86
+
87
+ it("applies the same anchoring to imported patterns", () => {
88
+ const schema = importSchema({
89
+ anyvaliVersion: "1.0",
90
+ schemaVersion: "1.1",
91
+ root: { kind: "string", pattern: "^[a-z]+$" },
92
+ definitions: {},
93
+ extensions: {},
94
+ });
95
+ expect(schema.safeParse("abc\n").success).toBe(false);
96
+ });
97
+ });
98
+
64
99
  // ---------------------------------------------------------------------------
65
100
  // 2. Prototype Pollution - CVE-2019-10744 / CVE-2020-8203
66
101
  // ---------------------------------------------------------------------------
@@ -416,6 +451,93 @@ describe("CWE-190 - Integer overflow and boundary checks", () => {
416
451
  });
417
452
  });
418
453
 
454
+ // ---------------------------------------------------------------------------
455
+ // 4b. float32 range bypass - CWE-20 / spec 1.4
456
+ // ---------------------------------------------------------------------------
457
+ // float32 MUST reject values outside the binary32 representable range. If it
458
+ // silently accepts any float64, a schema using float32 as a narrowing guard is
459
+ // bypassed: a value that cannot survive a round-trip through a real 32-bit
460
+ // float passes validation and is later truncated to Infinity downstream.
461
+ describe("CWE-20 - float32 out-of-range bypass", () => {
462
+ it("rejects a value just above the float32 maximum", () => {
463
+ // 3.5e38 > FLT_MAX (~3.4028e38)
464
+ const result = float32().safeParse(3.5e38);
465
+ expect(result.success).toBe(false);
466
+ if (!result.success) {
467
+ expect(result.issues[0].code).toBe("too_large");
468
+ }
469
+ });
470
+
471
+ it("rejects a value far above the float32 range", () => {
472
+ expect(float32().safeParse(1e300).success).toBe(false);
473
+ });
474
+
475
+ it("rejects a large negative value below the float32 range", () => {
476
+ expect(float32().safeParse(-1e300).success).toBe(false);
477
+ });
478
+
479
+ it("accepts values inside the float32 range and zero", () => {
480
+ expect(float32().safeParse(1.5).success).toBe(true);
481
+ expect(float32().safeParse(0).success).toBe(true);
482
+ expect(float32().safeParse(3.4e38).success).toBe(true);
483
+ });
484
+
485
+ it("rejects out-of-range float32 imported from interchange", () => {
486
+ const schema = importSchema({
487
+ anyvaliVersion: "1.0",
488
+ schemaVersion: "1.1",
489
+ root: { kind: "float32" },
490
+ definitions: {},
491
+ extensions: {},
492
+ });
493
+ expect(schema.safeParse(3.5e38).success).toBe(false);
494
+ });
495
+ });
496
+
497
+ // ---------------------------------------------------------------------------
498
+ // 4c. string->number coercion bypass - CWE-20 / spec 5.1
499
+ // ---------------------------------------------------------------------------
500
+ // string->number coercion MUST parse decimal floating-point only. JS Number()
501
+ // also accepts hex/octal/binary literals, so "0x10" would coerce to 16 and slip
502
+ // past a decimal-only contract (and diverge from every other SDK).
503
+ describe("CWE-20 - non-decimal string->number coercion bypass", () => {
504
+ const c = number().coerce({ from: "string" });
505
+
506
+ it("rejects hexadecimal literal strings", () => {
507
+ expect(c.safeParse("0x10").success).toBe(false);
508
+ });
509
+
510
+ it("rejects octal literal strings", () => {
511
+ expect(c.safeParse("0o17").success).toBe(false);
512
+ });
513
+
514
+ it("rejects binary literal strings", () => {
515
+ expect(c.safeParse("0b101").success).toBe(false);
516
+ });
517
+
518
+ it("rejects Infinity literal strings", () => {
519
+ expect(c.safeParse("Infinity").success).toBe(false);
520
+ expect(c.safeParse("-Infinity").success).toBe(false);
521
+ });
522
+
523
+ it("still accepts ordinary decimal and exponential strings", () => {
524
+ expect(c.parse("3.14")).toBe(3.14);
525
+ expect(c.parse(" -42 ")).toBe(-42);
526
+ expect(c.parse("1e3")).toBe(1000);
527
+ });
528
+
529
+ it("rejects hex coercion imported from interchange", () => {
530
+ const schema = importSchema({
531
+ anyvaliVersion: "1.0",
532
+ schemaVersion: "1.1",
533
+ root: { kind: "number", coerce: "string->number" },
534
+ definitions: {},
535
+ extensions: {},
536
+ });
537
+ expect(schema.safeParse("0x10").success).toBe(false);
538
+ });
539
+ });
540
+
419
541
  // ---------------------------------------------------------------------------
420
542
  // 5. NaN/Infinity injection - CWE-20
421
543
  // ---------------------------------------------------------------------------