Skip to content

Commit efe3db5

Browse files
authored
fix(security): sanitize fields and tables when using nestTables (#2702)
* fix(security): sanitize fields and tables when using nestTables * chore: undone `nestTables` validation for `rowsAsArray`
1 parent 2e03694 commit efe3db5

File tree

5 files changed

+98
-33
lines changed

5 files changed

+98
-33
lines changed

lib/helpers.js

+12
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,15 @@ const privateObjectProps = new Set([
7373
]);
7474

7575
exports.privateObjectProps = privateObjectProps;
76+
77+
const fieldEscape = (field) => {
78+
if (privateObjectProps.has(field)) {
79+
throw new Error(
80+
`The field name (${field}) can't be the same as an object's private property.`,
81+
);
82+
}
83+
84+
return srcEscape(field);
85+
};
86+
87+
exports.fieldEscape = fieldEscape;

lib/parsers/binary_parser.js

+5-14
Original file line numberDiff line numberDiff line change
@@ -145,22 +145,14 @@ function compile(fields, options, config) {
145145
let tableName = '';
146146

147147
for (let i = 0; i < fields.length; i++) {
148-
fieldName = helpers.srcEscape(fields[i].name);
149-
150-
if (helpers.privateObjectProps.has(fields[i].name)) {
151-
throw new Error(
152-
`The field name (${fieldName}) can't be the same as an object's private property.`,
153-
);
154-
}
155-
156-
parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
148+
fieldName = helpers.fieldEscape(fields[i].name);
149+
// parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
157150

158151
if (typeof options.nestTables === 'string') {
159-
lvalue = `result[${helpers.srcEscape(
160-
fields[i].table + options.nestTables + fields[i].name,
161-
)}]`;
152+
lvalue = `result[${helpers.fieldEscape(fields[i].table + options.nestTables + fields[i].name)}]`;
162153
} else if (options.nestTables === true) {
163-
tableName = helpers.srcEscape(fields[i].table);
154+
tableName = helpers.fieldEscape(fields[i].table);
155+
164156
parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
165157
lvalue = `result[${tableName}][${fieldName}]`;
166158
} else if (options.rowsAsArray) {
@@ -190,7 +182,6 @@ function compile(fields, options, config) {
190182
}
191183
parserFn('}');
192184

193-
194185
currentFieldNullBit *= 2;
195186
if (currentFieldNullBit === 0x100) {
196187
currentFieldNullBit = 1;

lib/parsers/text_parser.js

+9-13
Original file line numberDiff line numberDiff line change
@@ -143,28 +143,24 @@ function compile(fields, options, config) {
143143
}
144144
resultTablesArray = Object.keys(resultTables);
145145
for (let i = 0; i < resultTablesArray.length; i++) {
146-
parserFn(`result[${helpers.srcEscape(resultTablesArray[i])}] = {};`);
146+
parserFn(`result[${helpers.fieldEscape(resultTablesArray[i])}] = {};`);
147147
}
148148
}
149149

150150
let lvalue = '';
151151
let fieldName = '';
152+
let tableName = '';
152153
for (let i = 0; i < fields.length; i++) {
153-
fieldName = helpers.srcEscape(fields[i].name);
154+
fieldName = helpers.fieldEscape(fields[i].name);
155+
// parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
154156

155-
if (helpers.privateObjectProps.has(fields[i].name)) {
156-
throw new Error(
157-
`The field name (${fieldName}) can't be the same as an object's private property.`,
158-
);
159-
}
160-
161-
parserFn(`// ${fieldName}: ${typeNames[fields[i].columnType]}`);
162157
if (typeof options.nestTables === 'string') {
163-
lvalue = `result[${helpers.srcEscape(
164-
fields[i].table + options.nestTables + fields[i].name,
165-
)}]`;
158+
lvalue = `result[${helpers.fieldEscape(fields[i].table + options.nestTables + fields[i].name)}]`;
166159
} else if (options.nestTables === true) {
167-
lvalue = `result[${helpers.srcEscape(fields[i].table)}][${fieldName}]`;
160+
tableName = helpers.fieldEscape(fields[i].table);
161+
162+
parserFn(`if (!result[${tableName}]) result[${tableName}] = {};`);
163+
lvalue = `result[${tableName}][${fieldName}]`;
168164
} else if (options.rowsAsArray) {
169165
lvalue = `result[${i.toString(10)}]`;
170166
} else {
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { describe, assert } from 'poku';
22
import { describeOptions } from '../../../common.test.cjs';
33
import getBinaryParser from '../../../../lib/parsers/binary_parser.js';
4-
import { srcEscape } from '../../../../lib/helpers.js';
54
import { privateObjectProps } from '../../../../lib/helpers.js';
65

76
describe('Binary Parser: Block Native Object Props', describeOptions);
87

98
const blockedFields = Array.from(privateObjectProps).map((prop) => [
10-
{ name: prop },
9+
{ name: prop, table: '' },
1110
]);
1211

1312
blockedFields.forEach((fields) => {
@@ -17,8 +16,42 @@ blockedFields.forEach((fields) => {
1716
} catch (error) {
1817
assert.strictEqual(
1918
error.message,
20-
`The field name (${srcEscape(fields[0].name)}) can't be the same as an object's private property.`,
19+
`The field name (${fields[0].name}) can't be the same as an object's private property.`,
2120
`Ensure safe ${fields[0].name}`,
2221
);
2322
}
2423
});
24+
25+
blockedFields
26+
.map((fields) =>
27+
fields.map((field) => ({ ...field, name: field.name.slice(1) })),
28+
)
29+
.forEach((fields) => {
30+
try {
31+
getBinaryParser(fields, { nestTables: '_' }, {});
32+
assert.fail('An error was expected');
33+
} catch (error) {
34+
assert.strictEqual(
35+
error.message,
36+
`The field name (_${fields[0].name}) can't be the same as an object's private property.`,
37+
`Ensure safe _${fields[0].name} for nestTables as string`,
38+
);
39+
}
40+
});
41+
42+
blockedFields
43+
.map((fields) =>
44+
fields.map((field) => ({ ...field, name: '', table: field.name })),
45+
)
46+
.forEach((fields) => {
47+
try {
48+
getBinaryParser(fields, { nestTables: true }, {});
49+
assert.fail('An error was expected');
50+
} catch (error) {
51+
assert.strictEqual(
52+
error.message,
53+
`The field name (${fields[0].table}) can't be the same as an object's private property.`,
54+
`Ensure safe ${fields[0].table} for nestTables as true`,
55+
);
56+
}
57+
});
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { describe, assert } from 'poku';
22
import { describeOptions } from '../../../common.test.cjs';
33
import TextRowParser from '../../../../lib/parsers/text_parser.js';
4-
import { srcEscape } from '../../../../lib/helpers.js';
54
import { privateObjectProps } from '../../../../lib/helpers.js';
65

76
describe('Text Parser: Block Native Object Props', describeOptions);
87

98
const blockedFields = Array.from(privateObjectProps).map((prop) => [
10-
{ name: prop },
9+
{ name: prop, table: '' },
1110
]);
1211

1312
blockedFields.forEach((fields) => {
@@ -17,8 +16,42 @@ blockedFields.forEach((fields) => {
1716
} catch (error) {
1817
assert.strictEqual(
1918
error.message,
20-
`The field name (${srcEscape(fields[0].name)}) can't be the same as an object's private property.`,
19+
`The field name (${fields[0].name}) can't be the same as an object's private property.`,
2120
`Ensure safe ${fields[0].name}`,
2221
);
2322
}
2423
});
24+
25+
blockedFields
26+
.map((fields) =>
27+
fields.map((field) => ({ ...field, name: field.name.slice(1) })),
28+
)
29+
.forEach((fields) => {
30+
try {
31+
TextRowParser(fields, { nestTables: '_' }, {});
32+
assert.fail('An error was expected');
33+
} catch (error) {
34+
assert.strictEqual(
35+
error.message,
36+
`The field name (_${fields[0].name}) can't be the same as an object's private property.`,
37+
`Ensure safe _${fields[0].name} for nestTables as string`,
38+
);
39+
}
40+
});
41+
42+
blockedFields
43+
.map((fields) =>
44+
fields.map((field) => ({ ...field, name: '', table: field.name })),
45+
)
46+
.forEach((fields) => {
47+
try {
48+
TextRowParser(fields, { nestTables: true }, {});
49+
assert.fail('An error was expected');
50+
} catch (error) {
51+
assert.strictEqual(
52+
error.message,
53+
`The field name (${fields[0].table}) can't be the same as an object's private property.`,
54+
`Ensure safe ${fields[0].table} for nestTables as true`,
55+
);
56+
}
57+
});

0 commit comments

Comments
 (0)