Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { RunnerOptions } from '../api';
* @param zoneTestingStrategy How zone.js should be loaded during initialization.
* @returns The string content of the virtual initialization file.
*/
function createTestBedInitVirtualFile(
export function createTestBedInitVirtualFile(
providersFile: string | undefined,
projectSourceRoot: string,
teardown: boolean,
Expand All @@ -41,7 +41,12 @@ function createTestBedInitVirtualFile(
const relativePath = path.relative(projectSourceRoot, providersFile);
const { dir, name } = path.parse(relativePath);
const importPath = toPosixPath(path.join(dir, name));
providersImport = `import providers from './${importPath}';`;
// The import path is derived from the `providersFile` value in the project's
// configuration and must be embedded as a single JavaScript string literal.
// Building the specifier with `JSON.stringify` escapes any quotes, backslashes,
// or newlines it contains, so a crafted value cannot terminate the string early
// and inject executable code into this generated file.
providersImport = `import providers from ${JSON.stringify('./' + importPath)};`;
}

let zoneTestingSnippet = '';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import { createTestBedInitVirtualFile } from './build-options';

describe('createTestBedInitVirtualFile', () => {
const projectSourceRoot = '/project/src';

it('generates a providers import for a normal providersFile', () => {
const content = createTestBedInitVirtualFile(
'/project/src/my.providers.ts',
projectSourceRoot,
true,
'none',
false,
);

expect(content).toContain('import providers from "./my.providers";');
});

it('embeds the providersFile specifier as a single escaped string literal', () => {
// A providersFile whose value carries a quote followed by extra source. With raw
// string interpolation this would terminate the import specifier early and inject
// the trailing text as executable code into the generated file.
const malicious = `/project/src/x';globalThis['__pwned']=true;'`;

const content = createTestBedInitVirtualFile(
malicious,
projectSourceRoot,
true,
'none',
false,
);

// The entire value stays inside one string literal, so no statement escapes.
expect(content).toContain(`import providers from "./x';globalThis['__pwned']=true;'";`);
// The payload must never appear as standalone code.
expect(content).not.toContain(`import providers from './x';globalThis`);
});

it('escapes newlines in the providersFile specifier', () => {
const withNewline = '/project/src/x\';\nglobalThis["__pwned"]=true;//';

const content = createTestBedInitVirtualFile(
withNewline,
projectSourceRoot,
true,
'none',
false,
);

// A raw newline cannot appear inside the generated string literal; it is escaped.
expect(content).toContain('\\n');
expect(content).not.toMatch(/import providers from '\.\/x';\n/);
});
});