Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions extractors/cds/tools/cds-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ try {
new Set([
...globSync(join(sourceRoot, '**/*.cds'), {
ignore: ['**/node_modules/**', '**/.git/**'],
windowsPathsNoEscape: true,
}),
]),
);
Expand Down
51 changes: 34 additions & 17 deletions extractors/cds/tools/dist/cds-extractor.bundle.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions extractors/cds/tools/dist/cds-extractor.bundle.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions extractors/cds/tools/dist/compile-test-cds-lib.cjs.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion extractors/cds/tools/index-files.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ REM - assumes it is run from the root of the project source directory;

where node >nul 2>nul
if %ERRORLEVEL% neq 0 (
echo node executable is required (in PATH) to run the 'cds-extractor.bundle.js' script. Please install Node.js and try again.
echo node executable is required ^(in PATH^) to run the 'cds-extractor.bundle.js' script. Please install Node.js and try again.
exit /b 2
)

Expand Down
2 changes: 1 addition & 1 deletion extractors/cds/tools/src/cds/compiler/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function createSpawnOptions(
): SpawnSyncOptions {
const spawnOptions: SpawnSyncOptions = {
cwd: projectBaseDir, // CRITICAL: Always use project base directory as cwd to ensure correct path generation
shell: false, // Use shell=false to ensure proper argument handling for paths with spaces
shell: true, // Required on Windows where npm/npx are .cmd files
stdio: 'pipe',
env: { ...process.env },
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shell was switched from false to true for all CDS compilation spawns. This changes argument handling semantics (especially for paths with spaces) and increases exposure to shell-quoting edge cases. Prefer keeping shell: false and using platform-specific executables (npx.cmd/cds.cmd) or only enabling shell on Windows when needed.

See below for a potential fix:

  // Check if we're using a direct binary path (contains node_modules/.bin/ or node_modules\.bin\) or npx-style command
  // Check both platform-native separator and forward slash for cross-platform compatibility
  const binPathNative = `node_modules${sep}.bin${sep}`;
  const binPathPosix = 'node_modules/.bin/';
  const isDirectBinary = cdsCommand.includes(binPathNative) || cdsCommand.includes(binPathPosix);
  const useShell = process.platform === 'win32' && !isDirectBinary;

  const spawnOptions: SpawnSyncOptions = {
    cwd: projectBaseDir, // CRITICAL: Always use project base directory as cwd to ensure correct path generation
    shell: useShell, // Only enable shell on Windows for command resolution such as npm/npx .cmd files
    stdio: 'pipe',
    env: { ...process.env },
  };

Copilot uses AI. Check for mistakes.
};
Expand Down
15 changes: 15 additions & 0 deletions extractors/cds/tools/src/cds/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { delimiter, join } from 'path';

import { addCdsIndexerDiagnostic } from '../diagnostics';
import { cdsExtractorLog } from '../logging';
import { projectInstallDependencies } from '../packageManager';
import type { CdsDependencyGraph, CdsProject } from './parser/types';

/** Maximum time (ms) allowed for a single cds-indexer invocation. */
Expand Down Expand Up @@ -114,6 +115,7 @@ export function runCdsIndexer(
env,
stdio: 'pipe',
timeout: CDS_INDEXER_TIMEOUT_MS,
shell: true,
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spawnSync is invoked with shell: true, which routes execution through a command shell. That’s usually unnecessary for npx and can introduce quoting/escaping differences across platforms. Prefer keeping shell: false and selecting the correct executable on Windows (e.g., npx.cmd), or make shell conditional on process.platform === 'win32'.

This issue also appears on line 206 of the same file.

See below for a potential fix:

    const npxExecutable = process.platform === 'win32' ? 'npx.cmd' : 'npx';
    const spawnResult = spawnSync(
      npxExecutable,
      ['--yes', CDS_INDEXER_PACKAGE],
      {
        cwd: projectAbsPath,
        env,
        stdio: 'pipe',
        timeout: CDS_INDEXER_TIMEOUT_MS,
        shell: false,
      },
    );

Copilot uses AI. Check for mistakes.
});

result.durationMs = Date.now() - startTime;
Expand Down Expand Up @@ -199,6 +201,19 @@ export function orchestrateCdsIndexer(

if (result.success) {
summary.successfulRuns++;

// Install the project's full dependencies so the CDS compiler can
// resolve all CDS model imports (e.g. `@sap/cds-shim`) during
// compilation. The cache directory only contains `@sap/cds`,
// `@sap/cds-dk`, and `@sap/cds-indexer`, which is not enough for
// projects that reference additional CDS packages.
const installResult = projectInstallDependencies(project, sourceRoot);
if (!installResult.success) {
cdsExtractorLog(
'warn',
`Full dependency installation failed for project '${projectDir}' after successful cds-indexer run: ${installResult.error ?? 'unknown error'}`,
);
}
} else {
summary.failedRuns++;

Expand Down
10 changes: 8 additions & 2 deletions extractors/cds/tools/src/cds/parser/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function determineCdsFilesForProjectDir(
const cdsFiles = sync(join(projectDir, '**/*.cds'), {
nodir: true,
ignore: ['**/node_modules/**', '**/*.testproj/**'],
windowsPathsNoEscape: true,
});

// Convert absolute paths to paths relative to sourceRootDir
Expand Down Expand Up @@ -91,11 +92,13 @@ export function determineCdsProjectsUnderSourceDir(sourceRootDir: string): strin
const packageJsonFiles = sync(join(sourceRootDir, '**/package.json'), {
nodir: true,
ignore: ['**/node_modules/**', '**/*.testproj/**'],
windowsPathsNoEscape: true,
});

const cdsFiles = sync(join(sourceRootDir, '**/*.cds'), {
nodir: true,
ignore: ['**/node_modules/**', '**/*.testproj/**'],
windowsPathsNoEscape: true,
});

// Collect all potential project directories
Expand Down Expand Up @@ -369,7 +372,10 @@ function hasStandardCdsContent(dir: string): boolean {
for (const location of standardLocations) {
if (existsSync(location) && statSync(location).isDirectory()) {
// Check for any .cds files at any level under these directories.
const cdsFiles = sync(join(location, '**/*.cds'), { nodir: true });
const cdsFiles = sync(join(location, '**/*.cds'), {
nodir: true,
windowsPathsNoEscape: true,
});
if (cdsFiles.length > 0) {
return true;
}
Expand All @@ -383,7 +389,7 @@ function hasStandardCdsContent(dir: string): boolean {
* Check if a directory has direct CDS files.
*/
function hasDirectCdsContent(dir: string): boolean {
const directCdsFiles = sync(join(dir, '*.cds'));
const directCdsFiles = sync(join(dir, '*.cds'), { windowsPathsNoEscape: true });
return directCdsFiles.length > 0;
}

Expand Down
16 changes: 10 additions & 6 deletions extractors/cds/tools/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ export function configureLgtmIndexFilters(): void {
`Found $LGTM_INDEX_FILTERS already set to:
${process.env.LGTM_INDEX_FILTERS}`,
);
const allowedExcludePatterns = [join('exclude:**', '*'), join('exclude:**', '*.*')];
// Use forward slashes explicitly — join() uses backslashes on Windows
// which causes 'Illegal use of **' errors in the JS extractor.
const allowedExcludePatterns = ['exclude:**/*', 'exclude:**/*.*'];

excludeFilters =
'\n' +
Expand All @@ -245,12 +247,14 @@ ${process.env.LGTM_INDEX_FILTERS}`,
// The cdsExtractorMarkerFileName file is auto-created by the CDS extractor in order
// to force the underlying JS extractor to see at least one .js file, which became a
// requirement starting with v2.23.5 of the CodeQL CLI.
// Use forward slashes explicitly — join() uses backslashes on Windows
// which causes 'Illegal use of **' errors in the JS extractor.
const lgtmIndexFiltersPatterns = [
join('exclude:**', '*.*'),
join('include:**', '*.cds.json'),
join('include:**', '*.cds'),
join('include:**', cdsExtractorMarkerFileName),
join('exclude:**', 'node_modules', '**', '*.*'),
'exclude:**/*.*',
'include:**/*.cds.json',
'include:**/*.cds',
`include:**/${cdsExtractorMarkerFileName}`,
'exclude:**/node_modules/**/*.*',
].join('\n');

process.env.LGTM_INDEX_FILTERS = lgtmIndexFiltersPatterns + excludeFilters;
Expand Down
1 change: 1 addition & 0 deletions extractors/cds/tools/src/packageManager/cacheInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ function installDependenciesInCache(
execFileSync('npm', ['install', '--quiet', '--no-audit', '--no-fund'], {
cwd: cacheDir,
stdio: 'inherit',
shell: true,
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execFileSync('npm', ...) is now run with shell: true. Prefer using npm.cmd on Windows (or otherwise making shell conditional) and keeping shell: false to avoid shell-specific quoting/escaping behavior and reduce security risk.

Suggested change
execFileSync('npm', ['install', '--quiet', '--no-audit', '--no-fund'], {
cwd: cacheDir,
stdio: 'inherit',
shell: true,
const npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';
execFileSync(npmExecutable, ['install', '--quiet', '--no-audit', '--no-fund'], {
cwd: cacheDir,
stdio: 'inherit',

Copilot uses AI. Check for mistakes.
});

// Add warning diagnostic if using fallback versions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function projectInstallDependencies(
cwd: projectPath,
stdio: 'inherit',
timeout: 120000, // 2-minute timeout
shell: true,
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running npm install with shell: true executes through a shell and is more error-prone for argument quoting; it also increases the risk surface compared to using the platform-specific executable (e.g., npm.cmd on Windows) with shell: false. Since this installs full project dependencies, also consider passing --ignore-scripts to avoid running arbitrary lifecycle scripts from untrusted dependencies during extraction.

See below for a potential fix:

    const npmExecutable = process.platform === 'win32' ? 'npm.cmd' : 'npm';

    try {
      execFileSync(
        npmExecutable,
        ['install', '--ignore-scripts', '--quiet', '--no-audit', '--no-fund'],
        {
          cwd: projectPath,
          stdio: 'inherit',
          timeout: 120000, // 2-minute timeout
          shell: false,
        },
      );

Copilot uses AI. Check for mistakes.
});

result.success = true;
Expand Down
Loading
Loading