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 +7 -0
- package/index.js +39 -7
- package/lib/aot-impl.js +21 -4
- package/lib/version.js +1 -1
- package/package.json +9 -9
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
|
|
43
|
-
data
|
|
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
|
|
83
|
+
!Object.hasOwn(target, key)
|
|
61
84
|
) {
|
|
62
|
-
target
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.27.
|
|
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.
|
|
126
|
-
"@ata-validator/native-darwin-x64": "1.27.
|
|
127
|
-
"@ata-validator/native-linux-arm64-gnu": "1.27.
|
|
128
|
-
"@ata-validator/native-linux-arm64-musl": "1.27.
|
|
129
|
-
"@ata-validator/native-linux-x64-gnu": "1.27.
|
|
130
|
-
"@ata-validator/native-linux-x64-musl": "1.27.
|
|
131
|
-
"@ata-validator/native-win32-x64": "1.27.
|
|
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"
|