Skip to content

Commit c2eb876

Browse files
committed
Escape chars in collection and db names
1 parent d2cd422 commit c2eb876

File tree

2 files changed

+113
-22
lines changed

2 files changed

+113
-22
lines changed

packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -369,22 +369,118 @@ describe('Script Generation', () => {
369369
name: createFieldMapping('person.fullName'),
370370
};
371371

372-
const result = generateScript(schema, {
372+
// Test various special characters: quotes, newlines, tabs
373+
const result1 = generateScript(schema, {
373374
databaseName: 'test\'db`with"quotes',
374375
collectionName: 'coll\nwith\ttabs',
375376
documentCount: 1,
376377
});
377378

379+
expect(result1.success).to.equal(true);
380+
if (result1.success) {
381+
expect(result1.script).to.contain('use("test\'db`with\\"quotes")');
382+
expect(result1.script).to.contain(
383+
'getCollection("coll\\nwith\\ttabs")'
384+
);
385+
// Should not contain unescaped special characters that could break JS
386+
expect(result1.script).not.to.contain("use('test'db");
387+
expect(result1.script).not.to.contain("getCollection('coll\nwith");
388+
389+
// Test that the generated document code is executable
390+
testDocumentCodeExecution(result1.script);
391+
}
392+
393+
// Test backticks and dollar signs (template literal characters)
394+
const result2 = generateScript(schema, {
395+
databaseName: 'test`${}',
396+
collectionName: 'collection`${}',
397+
documentCount: 1,
398+
});
399+
400+
expect(result2.success).to.equal(true);
401+
if (result2.success) {
402+
// Verify the script is syntactically valid
403+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
404+
expect(() => new Function(result2.script)).to.not.throw();
405+
406+
// Verify template literal characters are properly escaped in console.log
407+
expect(result2.script).to.contain('test\\`\\${}');
408+
expect(result2.script).to.contain('collection\\`\\${}');
409+
410+
// Test that the generated document code is executable
411+
testDocumentCodeExecution(result2.script);
412+
}
413+
});
414+
415+
it('should prevent code injection attacks via database and collection names', () => {
416+
const schema = {
417+
name: {
418+
mongoType: 'String' as const,
419+
fakerMethod: 'person.firstName',
420+
fakerArgs: [],
421+
},
422+
};
423+
424+
// Test with potentially dangerous names that could inject malicious code
425+
const result = generateScript(schema, {
426+
databaseName: 'test`; require("fs").rmSync("/"); //',
427+
collectionName: 'my "collection"',
428+
documentCount: 1,
429+
});
430+
378431
expect(result.success).to.equal(true);
379432
if (result.success) {
380-
// Should use JSON.stringify for safe string insertion
381-
expect(result.script).to.contain('use("test\'db`with\\"quotes")');
433+
// Verify the script is syntactically valid JavaScript
434+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
435+
expect(() => new Function(result.script)).to.not.throw();
436+
437+
// Verify malicious code is safely contained in string
382438
expect(result.script).to.contain(
383-
'db.getCollection("coll\\nwith\\ttabs")'
439+
'use(\'test`; require("fs").rmSync("/"); //\')'
440+
);
441+
expect(result.script).to.contain('getCollection(\'my "collection"\')');
442+
443+
// Verify template literal injection is prevented (backticks are escaped)
444+
expect(result.script).to.contain(
445+
'test\\`; require("fs").rmSync("/"); //'
446+
);
447+
448+
// Verify malicious code in name is safely contained in code comment
449+
expect(result.script).to.contain(
450+
'// Generated for collection: test`; require("fs").rmSync("/"); //.my "collection"'
384451
);
385-
// Should not contain unescaped special characters that could break JS
386-
expect(result.script).not.to.contain("use('test'db");
387-
expect(result.script).not.to.contain("getCollection('coll\nwith");
452+
453+
// Test that the generated document code is executable
454+
testDocumentCodeExecution(result.script);
455+
}
456+
});
457+
458+
it('should sanitize newlines in database and collection names in comments', () => {
459+
const schema = {
460+
field: {
461+
mongoType: 'String' as const,
462+
fakerMethod: 'lorem.word',
463+
fakerArgs: [],
464+
},
465+
};
466+
467+
// Test with names containing actual newlines and carriage returns
468+
const result = generateScript(schema, {
469+
databaseName: 'test\nwith\nnewlines',
470+
collectionName: 'coll\rwith\r\nreturns',
471+
documentCount: 1,
472+
});
473+
474+
expect(result.success).to.equal(true);
475+
if (result.success) {
476+
// Verify newlines are replaced with spaces in comments to prevent syntax errors
477+
expect(result.script).to.contain(
478+
'// Generated for collection: test with newlines.coll with returns'
479+
);
480+
481+
// Verify the script is still syntactically valid
482+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
483+
expect(() => new Function(result.script)).to.not.throw();
388484

389485
// Test that the generated document code is executable
390486
testDocumentCodeExecution(result.script);
@@ -1028,7 +1124,7 @@ describe('Script Generation', () => {
10281124
color: {
10291125
mongoType: 'String' as const,
10301126
fakerMethod: 'helpers.arrayElement',
1031-
fakerArgs: [{ json: "['red', 'blue', 'green']" }],
1127+
fakerArgs: [{ json: '["red", "blue", "green"]' }],
10321128
},
10331129
};
10341130

@@ -1184,12 +1280,6 @@ describe('Script Generation', () => {
11841280
fakerArgs: [],
11851281
probability: -0.5, // Invalid - should default to 1.0
11861282
},
1187-
field3: {
1188-
mongoType: 'String' as const,
1189-
fakerMethod: 'lorem.word',
1190-
fakerArgs: [],
1191-
probability: 'invalid' as any, // Invalid - should default to 1.0
1192-
},
11931283
};
11941284

11951285
const result = generateScript(schema, {
@@ -1203,8 +1293,7 @@ describe('Script Generation', () => {
12031293
// All fields should be treated as probability 1.0 (always present)
12041294
const expectedReturnBlock = `return {
12051295
field1: faker.lorem.word(),
1206-
field2: faker.lorem.word(),
1207-
field3: faker.lorem.word()
1296+
field2: faker.lorem.word()
12081297
};`;
12091298
expect(result.script).to.contain(expectedReturnBlock);
12101299
expect(result.script).not.to.contain('Math.random()');

packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ export function generateScript(
5353

5454
// Generate unformatted script
5555
const unformattedScript = `// Mock Data Generator Script
56-
// Generated for collection: ${JSON.stringify(
57-
options.databaseName
58-
)}.${JSON.stringify(options.collectionName)}
56+
// Generated for collection: ${options.databaseName.replace(
57+
/[\r\n]/g, // Prevent newlines in names that could break the comment
58+
' '
59+
)}.${options.collectionName.replace(/[\r\n]/g, ' ')}
5960
// Document count: ${options.documentCount}
6061
6162
const { faker } = require('@faker-js/faker');
@@ -79,9 +80,10 @@ db.getCollection(${JSON.stringify(
7980
options.collectionName
8081
)}).insertMany(documents);
8182
82-
console.log("Successfully inserted " + documents.length + " documents into " + ${JSON.stringify(
83-
options.databaseName
84-
)} + "." + ${JSON.stringify(options.collectionName)});`;
83+
console.log(\`Successfully inserted \${documents.length} documents into ${options.databaseName.replace(
84+
/[`$]/g, // Escape backticks and dollar signs
85+
'\\$&'
86+
)}.${options.collectionName.replace(/[`$]/g, '\\$&')}\`);`;
8587

8688
// Format the script using prettier
8789
const script = prettify(unformattedScript, 'javascript');

0 commit comments

Comments
 (0)