Skip to content

Commit 4e35e5e

Browse files
committed
CDS extractor support for CODEQL_CONFIG_PATH env
1 parent 83d3680 commit 4e35e5e

File tree

5 files changed

+140
-11
lines changed

5 files changed

+140
-11
lines changed

extractors/cds/tools/dist/cds-extractor.bundle.js

Lines changed: 24 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extractors/cds/tools/dist/cds-extractor.bundle.js.map

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extractors/cds/tools/dist/compile-test-cds-lib.cjs.map

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

extractors/cds/tools/src/paths-ignore.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { existsSync, readFileSync } from 'fs';
2-
import { join } from 'path';
2+
import { join, relative, resolve } from 'path';
33

44
import { load as yamlLoad } from 'js-yaml';
55
import { minimatch } from 'minimatch';
@@ -10,7 +10,7 @@ import { cdsExtractorLog } from './logging';
1010
* Well-known paths where a CodeQL configuration file may be located,
1111
* relative to the source root directory. Checked in order of priority.
1212
*/
13-
const CODEQL_CONFIG_PATHS = [
13+
const DEFAULT_CONFIG_RELATIVE_PATHS = [
1414
'.github/codeql/codeql-config.yml',
1515
'.github/codeql/codeql-config.yaml',
1616
];
@@ -29,14 +29,44 @@ interface CodeqlConfig {
2929
}
3030

3131
/**
32-
* Finds the CodeQL configuration file in the source root directory by
33-
* checking the well-known paths in order.
32+
* Finds the CodeQL configuration file in the source root directory.
33+
*
34+
* When the `CODEQL_CONFIG_PATH` environment variable is set, its value
35+
* is treated as a path (relative to `sourceRoot`) to the config file.
36+
* The resolved path must reside under `sourceRoot`; otherwise it is
37+
* rejected to prevent path-traversal issues.
38+
*
39+
* When the environment variable is not set, the well-known default
40+
* paths are checked in order.
3441
*
3542
* @param sourceRoot - The source root directory
3643
* @returns The absolute path to the config file, or undefined if not found
3744
*/
3845
export function findCodeqlConfigFile(sourceRoot: string): string | undefined {
39-
for (const configPath of CODEQL_CONFIG_PATHS) {
46+
const envConfigPath = process.env.CODEQL_CONFIG_PATH;
47+
if (envConfigPath) {
48+
const resolvedRoot = resolve(sourceRoot);
49+
const fullPath = resolve(resolvedRoot, envConfigPath);
50+
const rel = relative(resolvedRoot, fullPath);
51+
if (rel.startsWith('..') || resolve(resolvedRoot, rel) !== fullPath) {
52+
cdsExtractorLog(
53+
'warn',
54+
`CODEQL_CONFIG_PATH '${envConfigPath}' resolves outside the source root. Ignoring.`,
55+
);
56+
return undefined;
57+
}
58+
if (existsSync(fullPath)) {
59+
cdsExtractorLog('info', `Using CodeQL config file from CODEQL_CONFIG_PATH: ${fullPath}`);
60+
return fullPath;
61+
}
62+
cdsExtractorLog(
63+
'warn',
64+
`CODEQL_CONFIG_PATH is set to '${envConfigPath}', but no file exists at '${fullPath}'.`,
65+
);
66+
return undefined;
67+
}
68+
69+
for (const configPath of DEFAULT_CONFIG_RELATIVE_PATHS) {
4070
const fullPath = join(sourceRoot, configPath);
4171
if (existsSync(fullPath)) {
4272
return fullPath;

extractors/cds/tools/test/src/paths-ignore.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@ jest.mock('fs', () => ({
1818
jest.mock('js-yaml');
1919

2020
describe('paths-ignore', () => {
21+
const savedCodeqlConfigPath = process.env.CODEQL_CONFIG_PATH;
22+
2123
beforeEach(() => {
2224
jest.resetAllMocks();
2325
clearPathsIgnoreCache();
26+
delete process.env.CODEQL_CONFIG_PATH;
2427
jest.spyOn(console, 'log').mockImplementation(() => {});
2528
jest.spyOn(console, 'warn').mockImplementation(() => {});
2629
jest.spyOn(console, 'error').mockImplementation(() => {});
2730
});
2831

2932
afterEach(() => {
3033
jest.restoreAllMocks();
34+
if (savedCodeqlConfigPath !== undefined) {
35+
process.env.CODEQL_CONFIG_PATH = savedCodeqlConfigPath;
36+
} else {
37+
delete process.env.CODEQL_CONFIG_PATH;
38+
}
3139
});
3240

3341
describe('findCodeqlConfigFile', () => {
@@ -62,6 +70,61 @@ describe('paths-ignore', () => {
6270
const result = findCodeqlConfigFile('/source');
6371
expect(result).toBeUndefined();
6472
});
73+
74+
it('should use CODEQL_CONFIG_PATH when set and file exists', () => {
75+
process.env.CODEQL_CONFIG_PATH = 'default-codeql-config.yml';
76+
(existsSync as jest.Mock).mockImplementation(
77+
(p: string) => p === '/source/default-codeql-config.yml',
78+
);
79+
80+
const result = findCodeqlConfigFile('/source');
81+
expect(result).toBe('/source/default-codeql-config.yml');
82+
});
83+
84+
it('should use CODEQL_CONFIG_PATH for nested paths under source root', () => {
85+
process.env.CODEQL_CONFIG_PATH = 'config/my-codeql-config.yml';
86+
(existsSync as jest.Mock).mockImplementation(
87+
(p: string) => p === '/source/config/my-codeql-config.yml',
88+
);
89+
90+
const result = findCodeqlConfigFile('/source');
91+
expect(result).toBe('/source/config/my-codeql-config.yml');
92+
});
93+
94+
it('should return undefined when CODEQL_CONFIG_PATH file does not exist', () => {
95+
process.env.CODEQL_CONFIG_PATH = 'nonexistent-config.yml';
96+
(existsSync as jest.Mock).mockReturnValue(false);
97+
98+
const result = findCodeqlConfigFile('/source');
99+
expect(result).toBeUndefined();
100+
});
101+
102+
it('should reject CODEQL_CONFIG_PATH that resolves outside source root', () => {
103+
process.env.CODEQL_CONFIG_PATH = '../../etc/passwd';
104+
(existsSync as jest.Mock).mockReturnValue(true);
105+
106+
const result = findCodeqlConfigFile('/source');
107+
expect(result).toBeUndefined();
108+
});
109+
110+
it('should not fall back to default paths when CODEQL_CONFIG_PATH is set but missing', () => {
111+
process.env.CODEQL_CONFIG_PATH = 'missing-config.yml';
112+
(existsSync as jest.Mock).mockImplementation(
113+
(p: string) => p === '/source/.github/codeql/codeql-config.yml',
114+
);
115+
116+
const result = findCodeqlConfigFile('/source');
117+
// Should NOT find the default config when CODEQL_CONFIG_PATH is explicitly set
118+
expect(result).toBeUndefined();
119+
});
120+
121+
it('should take precedence over default paths when CODEQL_CONFIG_PATH is set', () => {
122+
process.env.CODEQL_CONFIG_PATH = 'custom-config.yml';
123+
(existsSync as jest.Mock).mockReturnValue(true);
124+
125+
const result = findCodeqlConfigFile('/source');
126+
expect(result).toBe('/source/custom-config.yml');
127+
});
65128
});
66129

67130
describe('getPathsIgnorePatterns', () => {
@@ -192,6 +255,20 @@ describe('paths-ignore', () => {
192255
const result = getPathsIgnorePatterns('/source');
193256
expect(result).toEqual([]);
194257
});
258+
259+
it('should read patterns from custom config via CODEQL_CONFIG_PATH', () => {
260+
process.env.CODEQL_CONFIG_PATH = 'default-codeql-config.yml';
261+
(existsSync as jest.Mock).mockImplementation(
262+
(p: string) => p === '/source/default-codeql-config.yml',
263+
);
264+
(readFileSync as jest.Mock).mockReturnValue('yaml-content');
265+
(yamlLoad as jest.Mock).mockReturnValue({
266+
'paths-ignore': ['third_party', 'generated/**'],
267+
});
268+
269+
const result = getPathsIgnorePatterns('/source');
270+
expect(result).toEqual(['third_party', 'generated/**']);
271+
});
195272
});
196273

197274
describe('shouldIgnorePath', () => {

0 commit comments

Comments
 (0)