Skip to content

Commit 99bc4e8

Browse files
fix: drop unmatched properties when additionalProperties is false (#879)
1 parent de5dd5b commit 99bc4e8

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,12 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma, objV
384384
const additionalPropertiesLocation = location.getPropertyLocation('additionalProperties')
385385
const additionalPropertiesSchema = additionalPropertiesLocation.schema
386386

387-
if (additionalPropertiesSchema !== undefined) {
387+
// `additionalProperties: false` means every property that is not declared in
388+
// `properties` nor matched by `patternProperties` is dropped, so no branch is
389+
// emitted for it. Without this guard the `false` schema reaches buildValue,
390+
// which serializes any boolean schema with `JSON.stringify(value)` and lets
391+
// the property through.
392+
if (additionalPropertiesSchema !== undefined && additionalPropertiesSchema !== false) {
388393
if (additionalPropertiesSchema === true) {
389394
code += `
390395
${addComma}

test/additionalProperties.test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,43 @@ test('required key not in properties + additionalProperties produces valid JSON'
377377
t.assert.equal(out, '{"str":"x"}')
378378
t.assert.deepStrictEqual(JSON.parse(out), { str: 'x' })
379379
})
380+
381+
test('additionalProperties set to false ignores properties not matched by patternProperties', (t) => {
382+
// Regression: `additionalProperties: false` is documented to drop every
383+
// property that is not listed in `properties` or matched by
384+
// `patternProperties`. Combined with `patternProperties`, the generated
385+
// code used to fall through to the additionalProperties branch with a
386+
// boolean `false` schema, which serialized unmatched properties with
387+
// `JSON.stringify(value)` and leaked them into the output:
388+
// {"nickname":"nick","matchnum":3,"leaked":"secret"}
389+
t.plan(2)
390+
const stringify = build({
391+
type: 'object',
392+
properties: {
393+
nickname: { type: 'string' }
394+
},
395+
patternProperties: {
396+
num: { type: 'number' }
397+
},
398+
additionalProperties: false
399+
})
400+
401+
const out = stringify({ nickname: 'nick', matchnum: 3, leaked: 'secret' })
402+
t.assert.equal(out, '{"nickname":"nick","matchnum":3}')
403+
t.assert.deepStrictEqual(JSON.parse(out), { nickname: 'nick', matchnum: 3 })
404+
})
405+
406+
test('additionalProperties set to false without declared properties ignores unmatched properties', (t) => {
407+
t.plan(2)
408+
const stringify = build({
409+
type: 'object',
410+
patternProperties: {
411+
'^str': { type: 'string' }
412+
},
413+
additionalProperties: false
414+
})
415+
416+
const out = stringify({ str1: 'a', leaked: 'secret' })
417+
t.assert.equal(out, '{"str1":"a"}')
418+
t.assert.deepStrictEqual(JSON.parse(out), { str1: 'a' })
419+
})

0 commit comments

Comments
 (0)