Skip to content
Open
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
16 changes: 14 additions & 2 deletions examples/scripts/build-metadata.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import path from 'path';
import { toKebabCase } from '../src/app/strings.mjs';
import { parseConfig } from '../utils/utils.mjs';

/**
* Sanitizes a file or directory name to prevent path traversal attacks
* @param {string} name - The file or directory name to sanitize
* @returns {string} - The sanitized name
*/
const sanitizeName = (name) => {
// Remove any path traversal sequences and null bytes
return name.replace(/\.\./g, '').replace(/\0/g, '').replace(/[/\\]/g, '');
};

/**
* @type {{
* path: string,
Expand Down Expand Up @@ -42,15 +52,17 @@ const main = () => {
const categories = getDirFiles(rootPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const categories = getDirFiles(rootPath);
const categories = getDirFiles(rootPath).reduce((acc, file) => {
if (!/^[a-z-]+$/.test(file)) {
console.warn(`skipping invalid category folder: ${file}`);
return acc;
}
acc.push(file);
return acc;
}, /** @type {string[]} */ ([]));

Copy link
Contributor

Choose a reason for hiding this comment

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

Since we want to use this for example names to I would just move this reduce up to getDirFiles and call it getSantizedDirFiles


categories.forEach((category) => {
const categoryPath = path.resolve(`${rootPath}/${category}`);
const sanitizedCategory = sanitizeName(category);
const categoryPath = path.resolve(rootPath, sanitizedCategory);
const examplesFiles = getDirFiles(categoryPath);
const categoryKebab = toKebabCase(category);
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The sanitization is applied to category, but line 58 still uses the original unsanitized category variable to generate categoryKebab. This inconsistency means that if category contained path traversal sequences, they would be preserved in the kebab case conversion and potentially stored in the metadata output.

For consistency and security, use the sanitized version:

const categoryKebab = toKebabCase(sanitizedCategory);
Suggested change
const categoryKebab = toKebabCase(category);
const categoryKebab = toKebabCase(sanitizedCategory);

Copilot uses AI. Check for mistakes.

examplesFiles.forEach((exampleFile) => {
if (!/example.mjs$/.test(exampleFile)) {
return;
}
const examplePath = path.resolve(`${categoryPath}/${exampleFile}`);
const sanitizedExampleFile = sanitizeName(exampleFile);
const examplePath = path.resolve(categoryPath, sanitizedExampleFile);
const exampleName = exampleFile.split('.').shift() ?? '';
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

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

The sanitization is applied to exampleFile, but line 66 still uses the original unsanitized exampleFile variable to extract the example name. This inconsistency means that if exampleFile contained path traversal sequences, they would be preserved in exampleName and subsequently in exampleNameKebab at line 67, potentially storing malicious paths in the metadata output.

For consistency and security, use the sanitized version:

const exampleName = sanitizedExampleFile.split('.').shift() ?? '';
Suggested change
const exampleName = exampleFile.split('.').shift() ?? '';
const exampleName = sanitizedExampleFile.split('.').shift() ?? '';

Copilot uses AI. Check for mistakes.
const exampleNameKebab = toKebabCase(exampleName);

Expand Down
Loading