ata-validator 1.27.0 → 1.27.1

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
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to ata-validator are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/), and this project adheres to semantic versioning.
4
4
 
5
+ ## 1.27.1 - 2026-09-17
6
+
7
+ ### Fixed
8
+
9
+ - A default fills a property the instance does not carry, and both engines asked `key in data` to find out, which also answers for what the instance inherits: a property named `constructor` never received its default, and one named `__proto__` sent its nested defaults to `Object.prototype`, since the interpreter's applier walked `data.__proto__` as the parent. Both now ask `Object.hasOwn`. `tests/test_defaults_own_keys.js` holds it for either engine. Reported and fixed by @d4tocchini in #43.
10
+ - Completing that report, every remaining prototype-unsafe write and walk in the defaults machinery is closed. The nested-defaults applier now refuses to walk a parent the instance does not carry as an own key, which was the reachable pollution: a schema declaring a property named `__proto__` with nested defaults, no matter what the input held, resolved `data.__proto__` to `Object.prototype` and wrote the child defaults onto it, for every object in the realm, on both engines. Fills of a property named `__proto__` go through `Object.defineProperty`, because plain assignment of that name rewrites the object's prototype instead of creating a property; type coercion skips that name for the same reason, and the raw value still goes through validation. The AOT `parse()` emitter gets the same treatment defensively (computed keys in literals, `Object.hasOwn` presence checks, `defineProperty` for the one dangerous name), and its record loop, whose keys come from the input, now keeps a JSON-parsed own `__proto__` key as an own key in the output instead of silently rewriting the output's prototype.
11
+
5
12
  ## 1.27.0 - 2026-09-17
6
13
 
7
14
  ### Added
package/index.js CHANGED
@@ -30,6 +30,23 @@ function buildDefaultsApplier(schema) {
30
30
  };
31
31
  }
32
32
 
33
+ // Write an own property. Plain assignment of a key named `__proto__` does
34
+ // not create a property at all: it hits the Object.prototype setter and
35
+ // rewrites the object's prototype, which is how a schema could reach
36
+ // Object.prototype itself. defineProperty has no such special case.
37
+ function setOwn(obj, key, val) {
38
+ if (key === "__proto__") {
39
+ Object.defineProperty(obj, key, {
40
+ value: val,
41
+ writable: true,
42
+ enumerable: true,
43
+ configurable: true,
44
+ });
45
+ } else {
46
+ obj[key] = val;
47
+ }
48
+ }
49
+
33
50
  function collectDefaults(schema, actions, path) {
34
51
  if (typeof schema !== "object" || schema === null) return;
35
52
  const props = schema.properties;
@@ -39,11 +56,12 @@ function collectDefaults(schema, actions, path) {
39
56
  const defaultVal = prop.default;
40
57
  if (!path) {
41
58
  actions.push((data) => {
42
- if (typeof data === "object" && data !== null && !(key in data)) {
43
- data[key] =
59
+ if (typeof data === "object" && data !== null && !Object.hasOwn(data, key)) {
60
+ setOwn(data,
61
+ key,
44
62
  typeof defaultVal === "object" && defaultVal !== null
45
63
  ? JSON.parse(JSON.stringify(defaultVal))
46
- : defaultVal;
64
+ : defaultVal);
47
65
  }
48
66
  });
49
67
  } else {
@@ -52,17 +70,23 @@ function collectDefaults(schema, actions, path) {
52
70
  let target = data;
53
71
  for (let j = 0; j < parentPath.length; j++) {
54
72
  if (typeof target !== "object" || target === null) return;
73
+ // Own keys only. `target[key]` for an inherited name walks the
74
+ // prototype chain: a parent named `__proto__` that the instance
75
+ // does not carry resolved to Object.prototype, and the child
76
+ // defaults were written onto it, for every object in the realm.
77
+ if (!Object.hasOwn(target, parentPath[j])) return;
55
78
  target = target[parentPath[j]];
56
79
  }
57
80
  if (
58
81
  typeof target === "object" &&
59
82
  target !== null &&
60
- !(key in target)
83
+ !Object.hasOwn(target, key)
61
84
  ) {
62
- target[key] =
85
+ setOwn(target,
86
+ key,
63
87
  typeof defaultVal === "object" && defaultVal !== null
64
88
  ? JSON.parse(JSON.stringify(defaultVal))
65
- : defaultVal;
89
+ : defaultVal);
66
90
  }
67
91
  });
68
92
  }
@@ -263,6 +287,10 @@ function buildPreprocessCodegen(schema, options) {
263
287
  if (options.coerceTypes) {
264
288
  for (const [key, prop] of Object.entries(props)) {
265
289
  if (!prop || typeof prop !== 'object' || !prop.type) continue;
290
+ // Coercion writes with plain assignment, which for a key named
291
+ // __proto__ rewrites the prototype instead. The raw value still goes
292
+ // through validation, so skipping is a refusal to coerce, not a hole.
293
+ if (key === '__proto__') continue;
266
294
  const t = Array.isArray(prop.type) ? null : prop.type;
267
295
  if (!t) continue;
268
296
  const k = JSON.stringify(key);
@@ -289,7 +317,11 @@ function buildPreprocessCodegen(schema, options) {
289
317
  if (prop && typeof prop === 'object' && prop.default !== undefined) {
290
318
  const k = JSON.stringify(key);
291
319
  const def = JSON.stringify(prop.default);
292
- lines.push(`if(!(${k} in d))d[${k}]=${def}`);
320
+ // Assignment to a key named __proto__ hits the prototype setter
321
+ // instead of creating a property; defineProperty writes an own key.
322
+ lines.push(key === '__proto__'
323
+ ? `if(!Object.hasOwn(d,${k}))Object.defineProperty(d,${k},{value:${def},writable:true,enumerable:true,configurable:true})`
324
+ : `if(!Object.hasOwn(d,${k}))d[${k}]=${def}`);
293
325
  }
294
326
  }
295
327
  }
package/lib/aot-impl.js CHANGED
@@ -351,7 +351,9 @@ function emitClone(node, access, depth) {
351
351
  const read = `${access}[${JSON.stringify(key)}]`;
352
352
  const value = emitValueExpr(prop, read, depth);
353
353
  if (value === null) return null;
354
- if (required.has(key)) fixed.push(`${JSON.stringify(key)}: ${value}`);
354
+ // Computed key syntax: a plain `"__proto__":` in an object literal is
355
+ // the prototype slot, not a property; `["__proto__"]:` is a property.
356
+ if (required.has(key)) fixed.push(`[${JSON.stringify(key)}]: ${value}`);
355
357
  else conditional.push({ key, value, dflt: prop.default });
356
358
  }
357
359
  if (fixed.length === 0 && conditional.length === 0 && !apSchema) return null;
@@ -377,7 +379,10 @@ function emitClone(node, access, depth) {
377
379
  } else if (keys.length > 0) {
378
380
  skip = `if (${keys.map((k) => `${kVar} === ${JSON.stringify(k)}`).join(' || ')}) continue; `;
379
381
  }
380
- recordLoop = ` ${declSet}for (const ${kVar} of Object.keys(${access})) { ${skip}const ${vVar} = ${access}[${kVar}]; ${tmp}[${kVar}] = ${valueExpr}; }`;
382
+ // The key comes from the input, so it can be the string "__proto__" (an
383
+ // own key only JSON.parse writes); assignment would hit the prototype
384
+ // setter, so that one key goes through defineProperty.
385
+ recordLoop = ` ${declSet}for (const ${kVar} of Object.keys(${access})) { ${skip}const ${vVar} = ${access}[${kVar}]; if (${kVar} === "__proto__") Object.defineProperty(${tmp}, ${kVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); else ${tmp}[${kVar}] = ${valueExpr}; }`;
381
386
  }
382
387
 
383
388
  const literal = `{ ${fixed.join(', ')} }`;
@@ -386,10 +391,22 @@ function emitClone(node, access, depth) {
386
391
  // gains a key the input did not have.
387
392
  const adds = conditional
388
393
  .map(({ key, value, dflt }) => {
389
- const set = `if (${JSON.stringify(key)} in ${access}) ${tmp}[${JSON.stringify(key)}] = ${value};`;
394
+ // Object.hasOwn, not `in`: `in` also answers for what the instance
395
+ // inherits, so a property named `constructor` would copy the inherited
396
+ // function into the output that validate().data does not carry. And a
397
+ // key named `__proto__` cannot be written by assignment, which hits
398
+ // the prototype setter; defineProperty writes an own key.
399
+ const assign = key === '__proto__'
400
+ ? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${value}, writable: true, enumerable: true, configurable: true });`
401
+ : `${tmp}[${JSON.stringify(key)}] = ${value};`;
402
+ const set = `if (Object.hasOwn(${access}, ${JSON.stringify(key)})) ${assign}`;
390
403
  // The default is emitted as a literal inside the call, so an object or
391
404
  // array default is a fresh value on every parse, never shared state.
392
- return dflt === undefined ? set : `${set} else ${tmp}[${JSON.stringify(key)}] = ${JSON.stringify(dflt)};`;
405
+ if (dflt === undefined) return set;
406
+ const setDflt = key === '__proto__'
407
+ ? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${JSON.stringify(dflt)}, writable: true, enumerable: true, configurable: true });`
408
+ : `${tmp}[${JSON.stringify(key)}] = ${JSON.stringify(dflt)};`;
409
+ return `${set} else ${setDflt}`;
393
410
  })
394
411
  .join(' ');
395
412
  return `(function(){ const ${tmp} = ${literal}; ${adds}${recordLoop} return ${tmp}; })()`;
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  //
8
8
  // Kept in lockstep with package.json by `tests/test_version_sync.js`.
9
9
 
10
- module.exports = '1.27.0';
10
+ module.exports = '1.27.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.27.0",
3
+ "version": "1.27.1",
4
4
  "description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -51,7 +51,7 @@
51
51
  "release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
52
52
  "build": "cmake-js build --target ata",
53
53
  "rebuild": "cmake-js rebuild --target ata",
54
- "test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_uri_helper_parity.js && node tests/test_formats_single_pass.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_file_positions.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
54
+ "test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_uri_helper_parity.js && node tests/test_formats_single_pass.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_file_positions.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
55
55
  "bench:size": "node benchmark/bench_aot_size.mjs",
56
56
  "test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
57
57
  "test:compat": "node tests/test_compat.js",
@@ -122,13 +122,13 @@
122
122
  "LICENSE"
123
123
  ],
124
124
  "optionalDependencies": {
125
- "@ata-validator/native-darwin-arm64": "1.27.0",
126
- "@ata-validator/native-darwin-x64": "1.27.0",
127
- "@ata-validator/native-linux-arm64-gnu": "1.27.0",
128
- "@ata-validator/native-linux-arm64-musl": "1.27.0",
129
- "@ata-validator/native-linux-x64-gnu": "1.27.0",
130
- "@ata-validator/native-linux-x64-musl": "1.27.0",
131
- "@ata-validator/native-win32-x64": "1.27.0"
125
+ "@ata-validator/native-darwin-arm64": "1.27.1",
126
+ "@ata-validator/native-darwin-x64": "1.27.1",
127
+ "@ata-validator/native-linux-arm64-gnu": "1.27.1",
128
+ "@ata-validator/native-linux-arm64-musl": "1.27.1",
129
+ "@ata-validator/native-linux-x64-gnu": "1.27.1",
130
+ "@ata-validator/native-linux-x64-musl": "1.27.1",
131
+ "@ata-validator/native-win32-x64": "1.27.1"
132
132
  },
133
133
  "peerDependencies": {
134
134
  "yaml": "^2.0.0"