Skip to content

Conversation

@stevespringett
Copy link
Member

No description provided.

Signed-off-by: Steve Springett <[email protected]>
@stevespringett stevespringett added this to the 2.0 milestone Jun 15, 2025
@stevespringett stevespringett self-assigned this Jun 15, 2025
@stevespringett stevespringett added the CDX 2.0 related to release v2.0 label Jun 15, 2025
@stevespringett stevespringett linked an issue Jun 15, 2025 that may be closed by this pull request
@jkowalleck jkowalleck changed the title CycloneDX v2.0 Specification [WIP] CycloneDX v2.0 Specification Jun 16, 2025
const absoluteRootPath = path.resolve(rootSchemaPath);

// Verify paths exist
await fs.access(absoluteModelsDir);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

The best way to fix this problem is to validate user input (the CLI arguments modelsDirectory and rootSchemaPath) before using them in filesystem operations. The most common solution is to designate a "safe root" directory and ensure that any operation only accesses descendants of this root.

Since the arguments tell the script what to operate on, it may be reasonable to define a project-level root (for instance, the current working directory, or a user-chosen root), and ensure both modelsDirectory and rootSchemaPath are contained within it. This is achieved by resolving the paths and checking that the resolved/real paths start with the root path.

To implement this:

  • Before using modelsDirectory and rootSchemaPath, resolve them to absolute paths.
  • Define a safe root (for instance, process.cwd()), and then ensure both paths are within the root.
  • If not, log an error and exit.
  • You will need to add this validation in the CLI entry (lines 226–245).
  • You may need to use synchronous path resolution (e.g., fs.realpathSync) in CLI entry before passing to the async function, or just use path.resolve if you don't want to follow symbolic links.

No dependency (like sanitize-filename) is strictly required here because we are validating file/directory containment with path checks.

Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -240,6 +240,15 @@
         process.exit(1);
     }
 
+    // Validate user-provided paths are under project root
+    const SAFE_ROOT = process.cwd();
+    const absModelsDir = path.resolve(modelsDirectory);
+    const absRootSchemaPath = path.resolve(rootSchemaPath);
+    if (!absModelsDir.startsWith(SAFE_ROOT) || !absRootSchemaPath.startsWith(SAFE_ROOT)) {
+        console.error('Error: Provided paths must be under project root directory:\n  ' + SAFE_ROOT);
+        process.exit(1);
+    }
+
     bundleSchemas(modelsDirectory, rootSchemaPath, { validate: true })
         .catch(err => process.exit(1));
 }
EOF
@@ -240,6 +240,15 @@
process.exit(1);
}

// Validate user-provided paths are under project root
const SAFE_ROOT = process.cwd();
const absModelsDir = path.resolve(modelsDirectory);
const absRootSchemaPath = path.resolve(rootSchemaPath);
if (!absModelsDir.startsWith(SAFE_ROOT) || !absRootSchemaPath.startsWith(SAFE_ROOT)) {
console.error('Error: Provided paths must be under project root directory:\n ' + SAFE_ROOT);
process.exit(1);
}

bundleSchemas(modelsDirectory, rootSchemaPath, { validate: true })
.catch(err => process.exit(1));
}
Copilot is powered by AI and may make mistakes. Always verify output.

// Verify paths exist
await fs.access(absoluteModelsDir);
await fs.access(absoluteRootPath);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix this issue, you must ensure that any user-provided path is restricted so that the resulting path after resolution is contained within an expected safe root directory. For this script, both the modelsDirectory and rootSchemaPath should probably be contained within a certain schema directory (for example, maybe the project base or a specified schemas directory at runtime), but at minimum, the code should verify that after resolving and normalizing the path, it does not escape an expected root.

The best-practice way is:

  • Define a safe root directory for schema files (for example, process.cwd(), or an assigned base directory: up to context).
  • After resolving the user input paths (with path.resolve), use fs.realpathSync or await fs.realpath to fully resolve symbolic links.
  • Check that the resulting absolute path starts with the root directory (using string startsWith). If not, reject the request and exit.

Implement these changes near where the paths are resolved (after line 80, before line 83), so that all subsequent file accesses use only validated paths. This requires access to the chosen safe root directory and a check after resolving the paths.

You may need to also update CLI usage/help text if input restrictions are relevant.

No new dependencies are required; only the standard library (path, fs) is needed.


Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -76,18 +76,27 @@
 
 async function bundleSchemas(modelsDirectory, rootSchemaPath, options = {}) {
     try {
+        const SAFE_ROOT = process.cwd(); // Define safe root as current working directory
         const absoluteModelsDir = path.resolve(modelsDirectory);
         const absoluteRootPath = path.resolve(rootSchemaPath);
 
+        // Verify the resolved paths are contained within SAFE_ROOT
+        const realModelsDir = await fs.realpath(absoluteModelsDir);
+        const realRootPath = await fs.realpath(absoluteRootPath);
+        if (!realModelsDir.startsWith(SAFE_ROOT) || !realRootPath.startsWith(SAFE_ROOT)) {
+            throw new Error(`Refusing to access paths outside of safe root directory: ${SAFE_ROOT}`);
+        }
+
         // Verify paths exist
-        await fs.access(absoluteModelsDir);
-        await fs.access(absoluteRootPath);
+        await fs.access(realModelsDir);
+        await fs.access(realRootPath);
 
-        const rootSchemaFilename = path.basename(absoluteRootPath);
-        const rootSchemaDir = path.dirname(absoluteRootPath);
+        const rootSchemaFilename = path.basename(realRootPath);
+        const rootSchemaDir = path.dirname(realRootPath);
 
-        console.log(`Models directory: ${absoluteModelsDir}`);
-        console.log(`Root schema: ${absoluteRootPath}`);
+        console.log(`Models directory: ${realModelsDir}`);
+        console.log(`Root schema: ${realRootPath}`);
+        console.log(`Root schema: ${realRootPath}`);
 
         // Generate output filenames
         const baseFilename = rootSchemaFilename.replace('.schema.json', '');
EOF
@@ -76,18 +76,27 @@

async function bundleSchemas(modelsDirectory, rootSchemaPath, options = {}) {
try {
const SAFE_ROOT = process.cwd(); // Define safe root as current working directory
const absoluteModelsDir = path.resolve(modelsDirectory);
const absoluteRootPath = path.resolve(rootSchemaPath);

// Verify the resolved paths are contained within SAFE_ROOT
const realModelsDir = await fs.realpath(absoluteModelsDir);
const realRootPath = await fs.realpath(absoluteRootPath);
if (!realModelsDir.startsWith(SAFE_ROOT) || !realRootPath.startsWith(SAFE_ROOT)) {
throw new Error(`Refusing to access paths outside of safe root directory: ${SAFE_ROOT}`);
}

// Verify paths exist
await fs.access(absoluteModelsDir);
await fs.access(absoluteRootPath);
await fs.access(realModelsDir);
await fs.access(realRootPath);

const rootSchemaFilename = path.basename(absoluteRootPath);
const rootSchemaDir = path.dirname(absoluteRootPath);
const rootSchemaFilename = path.basename(realRootPath);
const rootSchemaDir = path.dirname(realRootPath);

console.log(`Models directory: ${absoluteModelsDir}`);
console.log(`Root schema: ${absoluteRootPath}`);
console.log(`Models directory: ${realModelsDir}`);
console.log(`Root schema: ${realRootPath}`);
console.log(`Root schema: ${realRootPath}`);

// Generate output filenames
const baseFilename = rootSchemaFilename.replace('.schema.json', '');
Copilot is powered by AI and may make mistakes. Always verify output.
console.log(`Output: ${outputPath}\n`);

// Read all schema files in the models directory
const files = await fs.readdir(absoluteModelsDir);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix the problem, the code should validate that the directory specified by modelsDirectory is within a designated safe root folder, before using it in any file system operations.

The best way to safely handle user input for directory paths is to:

  • Define a safe root folder (e.g., project-specific schema directory root, such as the directory the script is launched from, or a hard-coded directory).
  • Normalize user-provided paths using path.resolve.
  • After normalization, ensure that the resolved directory is contained within the safe root folder (e.g., resolved path startsWith safe root path).
  • If the check fails, log an error and exit.

For this code, assuming the schemas should reside in or under the current working directory, define SAFE_ROOT = process.cwd() near the CLI entrypoint (line 226). In the bundleSchemas function, after resolving modelsDirectory, check (after path.resolve) that the directory remains under SAFE_ROOT. If not, throw an error.

Needed changes:

  • Import nothing new, as only path and fs are needed.
  • Insert containment checks.
  • Add an error message and hard exit if validation fails.

All changes should be made in tools/src/main/js/bundle-schemas.js, within the entrypoint and bundleSchemas function as shown.

Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -79,6 +79,12 @@
         const absoluteModelsDir = path.resolve(modelsDirectory);
         const absoluteRootPath = path.resolve(rootSchemaPath);
 
+        // Ensure models directory is under SAFE_ROOT
+        const SAFE_ROOT = options.SAFE_ROOT || process.cwd();
+        if (!absoluteModelsDir.startsWith(SAFE_ROOT)) {
+            throw new Error(`Models directory must be inside the safe root: ${SAFE_ROOT}\nGiven: ${absoluteModelsDir}`);
+        }
+
         // Verify paths exist
         await fs.access(absoluteModelsDir);
         await fs.access(absoluteRootPath);
@@ -225,6 +231,7 @@
 // CLI usage
 if (require.main === module) {
     const [,, modelsDirectory, rootSchemaPath] = process.argv;
+    const SAFE_ROOT = process.cwd();
 
     if (!modelsDirectory || !rootSchemaPath) {
         console.log('Usage: node bundle-schemas.js <models-directory> <root-schema-path>');
@@ -240,7 +247,7 @@
         process.exit(1);
     }
 
-    bundleSchemas(modelsDirectory, rootSchemaPath, { validate: true })
+    bundleSchemas(modelsDirectory, rootSchemaPath, { validate: true, SAFE_ROOT })
         .catch(err => process.exit(1));
 }
 
EOF
@@ -79,6 +79,12 @@
const absoluteModelsDir = path.resolve(modelsDirectory);
const absoluteRootPath = path.resolve(rootSchemaPath);

// Ensure models directory is under SAFE_ROOT
const SAFE_ROOT = options.SAFE_ROOT || process.cwd();
if (!absoluteModelsDir.startsWith(SAFE_ROOT)) {
throw new Error(`Models directory must be inside the safe root: ${SAFE_ROOT}\nGiven: ${absoluteModelsDir}`);
}

// Verify paths exist
await fs.access(absoluteModelsDir);
await fs.access(absoluteRootPath);
@@ -225,6 +231,7 @@
// CLI usage
if (require.main === module) {
const [,, modelsDirectory, rootSchemaPath] = process.argv;
const SAFE_ROOT = process.cwd();

if (!modelsDirectory || !rootSchemaPath) {
console.log('Usage: node bundle-schemas.js <models-directory> <root-schema-path>');
@@ -240,7 +247,7 @@
process.exit(1);
}

bundleSchemas(modelsDirectory, rootSchemaPath, { validate: true })
bundleSchemas(modelsDirectory, rootSchemaPath, { validate: true, SAFE_ROOT })
.catch(err => process.exit(1));
}

Copilot is powered by AI and may make mistakes. Always verify output.
const schemaPath = path.join(absoluteModelsDir, file);
console.log(` Reading ${file}...`);

const content = await fs.readFile(schemaPath, 'utf8');

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix the problem, user input used to construct file and directory paths should be validated before being used. Specifically, the directory path given as modelsDirectory should always be within a predefined root directory (for example, the current working directory or a dedicated schemas root), and not allow traversal to arbitrary locations. The best fix is to resolve and normalize the user-provided path (modelsDirectory), and then verify that the resulting absolute path is indeed a subdirectory (or is equal to) the intended safe root. If the path is outside the allowed root, report an error and exit.

To achieve this, introduce a trusted root directory, normalize both the root and the user-provided directory, and check that the normalized user directory path starts with the normalized root directory. Add imports if necessary, but as path is already imported, no additional dependencies are required.

The recommended location for this validation is just after resolving the modelsDirectory (after line 79), before using it further. If the check fails, print an error and abort. For clarity, make the root something like process.cwd() (the working directory), which is as restrictive as possible without breaking existing usage; but make the root definition easy to adjust.


Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -79,6 +79,14 @@
         const absoluteModelsDir = path.resolve(modelsDirectory);
         const absoluteRootPath = path.resolve(rootSchemaPath);
 
+        // Validate models directory is under an allowed root directory
+        const safeRoot = process.cwd();
+        const normalizedRoot = path.resolve(safeRoot) + path.sep;
+        const normalizedModelsDir = absoluteModelsDir + path.sep;
+        if (!normalizedModelsDir.startsWith(normalizedRoot)) {
+            throw new Error(`Models directory ${absoluteModelsDir} is outside the allowed root (${safeRoot}).`);
+        }
+
         // Verify paths exist
         await fs.access(absoluteModelsDir);
         await fs.access(absoluteRootPath);
EOF
@@ -79,6 +79,14 @@
const absoluteModelsDir = path.resolve(modelsDirectory);
const absoluteRootPath = path.resolve(rootSchemaPath);

// Validate models directory is under an allowed root directory
const safeRoot = process.cwd();
const normalizedRoot = path.resolve(safeRoot) + path.sep;
const normalizedModelsDir = absoluteModelsDir + path.sep;
if (!normalizedModelsDir.startsWith(normalizedRoot)) {
throw new Error(`Models directory ${absoluteModelsDir} is outside the allowed root (${safeRoot}).`);
}

// Verify paths exist
await fs.access(absoluteModelsDir);
await fs.access(absoluteRootPath);
Copilot is powered by AI and may make mistakes. Always verify output.

// Read the root schema
console.log(`\nReading root schema...`);
const rootContent = await fs.readFile(absoluteRootPath, 'utf8');

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix the vulnerability, we need to make sure that the file path specified by the user (rootSchemaPath) is within a trusted root directory that we control, such as modelsDirectory or a designated schema directory. The recommended pattern is:

  1. Normalize the file path with path.resolve().
  2. Optionally, use fs.realpathSync (or its async promise version) to resolve symlinks.
  3. Check that the (normalized, symlink-resolved) resulting path starts with the trusted root directory.
  4. Reject any path that does not meet the restriction, ideally with a clear error message and a non-zero exit code.

Apply this pattern for both inputs: modelsDirectory and rootSchemaPath. All further file reads should only use the validated, normalized absolute paths.

For this codebase and the scenario, it's best to:

  • Define a trusted root directory for schemas and require both arguments stay within it.
  • After path.resolve, use fs.realpath (async) to resolve actual path.
  • Use a simple startsWith test to ensure the resulting path is under the root.

Required changes:

  • After resolving the paths for modelsDirectory and rootSchemaPath, run each through await fs.realpath(...).
  • Add a check ensuring both are subpaths of a defined trusted root directory (e.g., the process's working directory or some fixed schema path).
  • If the check fails, output an error and abort execution.

Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -76,9 +76,25 @@
 
 async function bundleSchemas(modelsDirectory, rootSchemaPath, options = {}) {
     try {
-        const absoluteModelsDir = path.resolve(modelsDirectory);
-        const absoluteRootPath = path.resolve(rootSchemaPath);
+        // Trusted root: process current working directory
+        const trustedRoot = path.resolve(process.cwd());
 
+        // Resolve and normalize all paths
+        const absoluteModelsDir = await fs.realpath(path.resolve(modelsDirectory));
+        const absoluteRootPath = await fs.realpath(path.resolve(rootSchemaPath));
+
+        // Verify containment in trusted root
+        if (
+            !absoluteModelsDir.startsWith(trustedRoot) ||
+            !absoluteRootPath.startsWith(trustedRoot)
+        ) {
+            throw new Error(
+                `Invalid input: Paths must be contained within the trusted root directory (${trustedRoot})\n` +
+                `  modelsDirectory resolved: ${absoluteModelsDir}\n` +
+                `  rootSchemaPath resolved: ${absoluteRootPath}`
+            );
+        }
+
         // Verify paths exist
         await fs.access(absoluteModelsDir);
         await fs.access(absoluteRootPath);
EOF
@@ -76,9 +76,25 @@

async function bundleSchemas(modelsDirectory, rootSchemaPath, options = {}) {
try {
const absoluteModelsDir = path.resolve(modelsDirectory);
const absoluteRootPath = path.resolve(rootSchemaPath);
// Trusted root: process current working directory
const trustedRoot = path.resolve(process.cwd());

// Resolve and normalize all paths
const absoluteModelsDir = await fs.realpath(path.resolve(modelsDirectory));
const absoluteRootPath = await fs.realpath(path.resolve(rootSchemaPath));

// Verify containment in trusted root
if (
!absoluteModelsDir.startsWith(trustedRoot) ||
!absoluteRootPath.startsWith(trustedRoot)
) {
throw new Error(
`Invalid input: Paths must be contained within the trusted root directory (${trustedRoot})\n` +
` modelsDirectory resolved: ${absoluteModelsDir}\n` +
` rootSchemaPath resolved: ${absoluteRootPath}`
);
}

// Verify paths exist
await fs.access(absoluteModelsDir);
await fs.access(absoluteRootPath);
Copilot is powered by AI and may make mistakes. Always verify output.
// Write bundled (pretty) version
console.log('\nWriting bundled schema...');
await fs.writeFile(bundledPath, JSON.stringify(finalSchema, null, 2));
const bundledStats = await fs.stat(bundledPath);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix the problem, we must ensure that the output file paths (specifically, bundledPath and minifiedPath) are only located inside a safe directory, such as the specified models directory, or another explicitly defined output directory. This can be achieved by normalizing both the models directory and the resolved output path using path.resolve and fs.realpathSync, and then verifying that the output file path starts with the models directory's path. If the check fails, we must report an error and terminate execution.

The changes are needed in the bundleSchemas function, specifically after establishing what the output file paths are (lines 97-98), but before writing any files. We need to perform the containment check (output path must start with safe directory), and abort if the check fails. This requires a synchronous realpath operation (or an async one via fs.promises) and a startsWith check. Ideally, we should place the check as soon as the output file paths are calculated, so before first write (line 195).

We do not need new external dependencies, as all relevant functionality can be imported from the built-in fs and path modules. If the models directory is deemed not suitable (e.g., schema directory is the intended base), you may choose as the root the parent directory of the schema file, but you must ensure a safe base is used.

Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -97,6 +97,19 @@
         const bundledPath = path.join(rootSchemaDir, bundledFilename);
         const minifiedPath = path.join(rootSchemaDir, minifiedFilename);
 
+
+        // Validate that output files are written within the models directory
+        const resolvedModelsDir = await fs.realpath(absoluteModelsDir);
+        const resolvedBundledPath = path.resolve(bundledPath);
+        const resolvedMinifiedPath = path.resolve(minifiedPath);
+
+        if (
+            !resolvedBundledPath.startsWith(resolvedModelsDir) ||
+            !resolvedMinifiedPath.startsWith(resolvedModelsDir)
+        ) {
+            throw new Error(`Output file paths must be within models directory (${resolvedModelsDir}). Refusing to write files.`);
+        }
+
         console.log(`Output (bundled): ${bundledPath}`);
         console.log(`Output (minified): ${minifiedPath}\n`);
 
EOF
@@ -97,6 +97,19 @@
const bundledPath = path.join(rootSchemaDir, bundledFilename);
const minifiedPath = path.join(rootSchemaDir, minifiedFilename);


// Validate that output files are written within the models directory
const resolvedModelsDir = await fs.realpath(absoluteModelsDir);
const resolvedBundledPath = path.resolve(bundledPath);
const resolvedMinifiedPath = path.resolve(minifiedPath);

if (
!resolvedBundledPath.startsWith(resolvedModelsDir) ||
!resolvedMinifiedPath.startsWith(resolvedModelsDir)
) {
throw new Error(`Output file paths must be within models directory (${resolvedModelsDir}). Refusing to write files.`);
}

console.log(`Output (bundled): ${bundledPath}`);
console.log(`Output (minified): ${minifiedPath}\n`);

Copilot is powered by AI and may make mistakes. Always verify output.
// Write minified version
console.log('Writing minified schema...');
await fs.writeFile(minifiedPath, JSON.stringify(finalSchema));
const minifiedStats = await fs.stat(minifiedPath);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix the uncontrolled path expression, we should ensure that all output file paths derived from user input (modelsDirectory, rootSchemaPath) are verified to be within an approved directory (safe root). The best general approach is to define a safe root directory (e.g., the absoluteModelsDir), resolve the intended output paths (with path.resolve), and then check that output files' real paths stay within the safe root.
Specifically, after computing (for example) minifiedPath, we should resolve it and use fs.realpath (or fs.realpathSync) and ensure the result starts with the safe root directory. If not, throw an error and exit.
You will need to synchronously or asynchronously check the real output path(s) before any write operation.

Required changes:

  • After computing each output file path (bundledPath, minifiedPath), resolve their real path, and ensure they start with the safe root (absoluteModelsDir or another intended directory).
  • If not, throw an error or exit with a clear message.
  • Ensure fs.realpath is imported (the code already imports fs.promises as fs, so you can use fs.realpath / fs.access) and path.

Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -97,6 +97,22 @@
         const bundledPath = path.join(rootSchemaDir, bundledFilename);
         const minifiedPath = path.join(rootSchemaDir, minifiedFilename);
 
+        // Sanitize output paths: ensure outputs reside within the models directory
+        const resolvedBundledPath = path.resolve(bundledPath);
+        const resolvedMinifiedPath = path.resolve(minifiedPath);
+        if (
+            !resolvedBundledPath.startsWith(absoluteModelsDir + path.sep) &&
+            resolvedBundledPath !== absoluteModelsDir
+        ) {
+            throw new Error(`Output file path (${resolvedBundledPath}) is outside the models directory (${absoluteModelsDir})`);
+        }
+        if (
+            !resolvedMinifiedPath.startsWith(absoluteModelsDir + path.sep) &&
+            resolvedMinifiedPath !== absoluteModelsDir
+        ) {
+            throw new Error(`Output file path (${resolvedMinifiedPath}) is outside the models directory (${absoluteModelsDir})`);
+        }
+
         console.log(`Output (bundled): ${bundledPath}`);
         console.log(`Output (minified): ${minifiedPath}\n`);
 
EOF
@@ -97,6 +97,22 @@
const bundledPath = path.join(rootSchemaDir, bundledFilename);
const minifiedPath = path.join(rootSchemaDir, minifiedFilename);

// Sanitize output paths: ensure outputs reside within the models directory
const resolvedBundledPath = path.resolve(bundledPath);
const resolvedMinifiedPath = path.resolve(minifiedPath);
if (
!resolvedBundledPath.startsWith(absoluteModelsDir + path.sep) &&
resolvedBundledPath !== absoluteModelsDir
) {
throw new Error(`Output file path (${resolvedBundledPath}) is outside the models directory (${absoluteModelsDir})`);
}
if (
!resolvedMinifiedPath.startsWith(absoluteModelsDir + path.sep) &&
resolvedMinifiedPath !== absoluteModelsDir
) {
throw new Error(`Output file path (${resolvedMinifiedPath}) is outside the models directory (${absoluteModelsDir})`);
}

console.log(`Output (bundled): ${bundledPath}`);
console.log(`Output (minified): ${minifiedPath}\n`);

Copilot is powered by AI and may make mistakes. Always verify output.
// Write bundled (pretty) version
console.log('\nWriting bundled schema...');
const prettyJson = JSON.stringify(finalSchema, null, 2);
await fs.writeFile(bundledPath, prettyJson);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
const lineCount = minifiedJson.split('\n').length;
console.log(` Minified JSON is on ${lineCount} line(s)`);

await fs.writeFile(minifiedPath, minifiedJson);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 5 days ago

To fix the problem, we should ensure that any output file path calculated from untrusted user input (specifically, rootSchemaPath and its derived directory) is contained within a safe, intended directory. A robust fix would involve designating a "safe root" directory and, after resolving all input paths, verifying that any generated output file path (such as minifiedPath and bundledPath) is contained within this directory.

Specifically:

  • Use a safe "root" directory for output (either the models directory or another pre-chosen directory).
  • Normalize paths using path.resolve and fs.realpathSync (or async fs.realpath) to handle symbolic links.
  • After path normalization, ensure the output paths start with the safe root directory.
  • Refuse to proceed if these checks fail, printing a message and exiting.

The suggested location for enforcing these checks is after resolving paths and before calling any file-modifying APIs. The necessary imports are already present; no need to add extra libraries.


Suggested changeset 1
tools/src/main/js/bundle-schemas.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tools/src/main/js/bundle-schemas.js b/tools/src/main/js/bundle-schemas.js
--- a/tools/src/main/js/bundle-schemas.js
+++ b/tools/src/main/js/bundle-schemas.js
@@ -97,6 +97,14 @@
         const bundledPath = path.join(rootSchemaDir, bundledFilename);
         const minifiedPath = path.join(rootSchemaDir, minifiedFilename);
 
+        // Validate output paths are within the trusted models directory
+        const realModelsDir = await fs.realpath(absoluteModelsDir);
+        const realBundledPath = await fs.realpath(path.resolve(bundledPath)).catch(() => path.resolve(bundledPath));
+        const realMinifiedPath = await fs.realpath(path.resolve(minifiedPath)).catch(() => path.resolve(minifiedPath));
+        if (!realBundledPath.startsWith(realModelsDir) || !realMinifiedPath.startsWith(realModelsDir)) {
+            throw new Error("Output paths must be inside the models directory.");
+        }
+
         console.log(`Output (bundled): ${bundledPath}`);
         console.log(`Output (minified): ${minifiedPath}\n`);
 
EOF
@@ -97,6 +97,14 @@
const bundledPath = path.join(rootSchemaDir, bundledFilename);
const minifiedPath = path.join(rootSchemaDir, minifiedFilename);

// Validate output paths are within the trusted models directory
const realModelsDir = await fs.realpath(absoluteModelsDir);
const realBundledPath = await fs.realpath(path.resolve(bundledPath)).catch(() => path.resolve(bundledPath));
const realMinifiedPath = await fs.realpath(path.resolve(minifiedPath)).catch(() => path.resolve(minifiedPath));
if (!realBundledPath.startsWith(realModelsDir) || !realMinifiedPath.startsWith(realModelsDir)) {
throw new Error("Output paths must be inside the models directory.");
}

console.log(`Output (bundled): ${bundledPath}`);
console.log(`Output (minified): ${minifiedPath}\n`);

Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CDX 2.0 related to release v2.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CycloneDX 2.0

2 participants