From eecb6dd5f8bc59d8a4782baf9a374bbd60c8946a Mon Sep 17 00:00:00 2001 From: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:21:38 +0200 Subject: [PATCH 1/3] fix(validate-refs): flag single-backslash Windows path leaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The absolute-path-leak check used `[A-Z]:\\\\`, which in a regex literal needs two backslashes — so a normal Windows path like C:\Users\... slipped straight through. Widened it to match either separator (C:\ or C:/). While here, exported checkAbsolutePathLeaks and added a small test (test/test-abs-path-leak.js, wired into npm test) covering the single-backslash case plus the existing Unix and code-block behaviour. --- package.json | 3 +- test/test-abs-path-leak.js | 84 +++++++++++++++++++++++++++++++++++++ tools/validate-file-refs.js | 6 ++- 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 test/test-abs-path-leak.js diff --git a/package.json b/package.json index 505c6e8e0..a5652cf4d 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,10 @@ "prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0", "quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills && npm run docs:validate-sidebar", "rebundle": "node tools/installer/bundlers/bundle-web.js rebundle", - "test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run lint && npm run lint:md && npm run format:check", + "test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run test:leaks && npm run lint && npm run lint:md && npm run format:check", "test:channels": "node test/test-installer-channels.js", "test:install": "node test/test-installation-components.js", + "test:leaks": "node test/test-abs-path-leak.js", "test:refs": "node test/test-file-refs-csv.js", "test:urls": "node test/test-parse-source-urls.js", "validate:refs": "node tools/validate-file-refs.js --strict", diff --git a/test/test-abs-path-leak.js b/test/test-abs-path-leak.js new file mode 100644 index 000000000..9301f2160 --- /dev/null +++ b/test/test-abs-path-leak.js @@ -0,0 +1,84 @@ +/** + * Absolute Path Leak Detection Test Runner + * + * Tests checkAbsolutePathLeaks() from validate-file-refs.js. Focused on Windows + * drive paths, which use a single separator (C:\Users) and were previously missed. + * + * Usage: node test/test-abs-path-leak.js + * Exit codes: 0 = all tests pass, 1 = test failures + */ + +const { checkAbsolutePathLeaks } = require('../tools/validate-file-refs.js'); + +// ANSI color codes +const colors = { + reset: '[0m', + green: '[32m', + red: '[31m', + cyan: '[36m', +}; + +let totalTests = 0; +let passedTests = 0; +const failures = []; + +function test(name, fn) { + totalTests++; + try { + fn(); + passedTests++; + console.log(` ${colors.green}OK${colors.reset} ${name}`); + } catch (error) { + console.log(` ${colors.red}XX${colors.reset} ${name} ${colors.red}${error.message}${colors.reset}`); + failures.push({ name, message: error.message }); + } +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +// Number of leak lines detected in a content string (filePath is only used for labelling). +function leakCount(content) { + return checkAbsolutePathLeaks('fixture.md', content).length; +} + +console.log(`\n${colors.cyan}Absolute Path Leak Detection${colors.reset}\n`); + +test('Windows single-backslash drive path is detected', () => { + assert(leakCount('See C:\\Users\\alex\\notes.md for details.') === 1, 'C:\\Users... not detected'); +}); + +test('Windows forward-slash drive path is detected', () => { + assert(leakCount('See C:/Users/alex/notes.md for details.') === 1, 'C:/Users... not detected'); +}); + +test('Unix /Users path is detected', () => { + assert(leakCount('open /Users/alex/secret.md') === 1, '/Users path not detected'); +}); + +test('Unix /home path is detected', () => { + assert(leakCount('open /home/alex/secret.md') === 1, '/home path not detected'); +}); + +test('relative paths are not flagged', () => { + assert(leakCount('load `./workflow.md` and ../shared/util.md') === 0, 'relative path falsely flagged'); +}); + +test('leaks inside fenced code blocks are ignored', () => { + const content = ['```bash', 'cat C:\\Users\\alex\\secret.md', '```'].join('\n'); + assert(leakCount(content) === 0, 'leak inside code block should be stripped'); +}); + +// --- Summary --- +console.log(`\n${colors.cyan}${'='.repeat(55)}${colors.reset}`); +console.log(`Total: ${totalTests} Passed: ${colors.green}${passedTests}${colors.reset} Failed: ${totalTests - passedTests}`); +console.log(`${colors.cyan}${'='.repeat(55)}${colors.reset}\n`); + +if (failures.length > 0) { + for (const failure of failures) console.log(`${colors.red}XX${colors.reset} ${failure.name}: ${failure.message}`); + process.exit(1); +} + +console.log(`${colors.green}All tests passed!${colors.reset}\n`); +process.exit(0); diff --git a/tools/validate-file-refs.js b/tools/validate-file-refs.js index 7e137763c..ad6365ad9 100644 --- a/tools/validate-file-refs.js +++ b/tools/validate-file-refs.js @@ -67,7 +67,9 @@ const STEP_META = /(?:thisStepFile|nextStepFile|continueStepFile|skipToStepFile| const LOAD_DIRECTIVE = /Load[:\s]+`(\.[^`]+)`/g; // Pattern: absolute path leaks -const ABS_PATH_LEAK = /(?:\/Users\/|\/home\/|[A-Z]:\\\\)/; +// Windows drive paths use a single separator (C:\Users or C:/Users). In a regex +// literal `\\` already matches one backslash, so the class matches either separator. +const ABS_PATH_LEAK = /(?:\/Users\/|\/home\/|[A-Z]:[\\/])/; // --- Output Escaping --- @@ -402,7 +404,7 @@ function checkAbsolutePathLeaks(filePath, content) { } // --- Exports (for testing) --- -module.exports = { extractCsvRefs }; +module.exports = { extractCsvRefs, checkAbsolutePathLeaks }; // --- Main --- From 9c4dde977da744adfc22694a5f9c6f282eefd62f Mon Sep 17 00:00:00 2001 From: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> Date: Sat, 20 Jun 2026 15:20:10 +0200 Subject: [PATCH 2/3] test(validate-refs): document test helpers with JSDoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added JSDoc to the test/assert/leakCount helpers so the new test file documents its own helpers. No behaviour change — 6/6 still pass. --- test/test-abs-path-leak.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/test-abs-path-leak.js b/test/test-abs-path-leak.js index 9301f2160..2c837677f 100644 --- a/test/test-abs-path-leak.js +++ b/test/test-abs-path-leak.js @@ -22,6 +22,11 @@ let totalTests = 0; let passedTests = 0; const failures = []; +/** + * Run a single named test case, recording the result and printing a status line. + * @param {string} name - Human-readable test description. + * @param {Function} fn - Test body; throw to signal failure. + */ function test(name, fn) { totalTests++; try { @@ -34,11 +39,20 @@ function test(name, fn) { } } +/** + * Throw an Error with `message` when `condition` is falsy. + * @param {boolean} condition - Expression that must hold. + * @param {string} message - Failure message. + */ function assert(condition, message) { if (!condition) throw new Error(message); } -// Number of leak lines detected in a content string (filePath is only used for labelling). +/** + * Count the leak lines detected in a content string. + * @param {string} content - File content to scan (filePath is only used for labelling). + * @returns {number} Number of lines flagged as absolute-path leaks. + */ function leakCount(content) { return checkAbsolutePathLeaks('fixture.md', content).length; } From 3df821d26e315bf962ae0ddf1f0993e7e8df72c5 Mon Sep 17 00:00:00 2001 From: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:27:17 +0200 Subject: [PATCH 3/3] fix(validate-refs): also catch lowercase drive letters in leak check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following review feedback, widen the Windows branch to [A-Za-z] so lowercase paths (c:\Users\...) are caught too. Kept a \b anchor so URL schemes like https:// (which also contain ":/") aren't flagged — a plain [A-Za-z] would have matched every URL in the docs. Added lowercase and URL-not-flagged cases to the test (now 8/8). --- test/test-abs-path-leak.js | 10 ++++++++++ tools/validate-file-refs.js | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/test/test-abs-path-leak.js b/test/test-abs-path-leak.js index 2c837677f..46a85e82c 100644 --- a/test/test-abs-path-leak.js +++ b/test/test-abs-path-leak.js @@ -67,6 +67,16 @@ test('Windows forward-slash drive path is detected', () => { assert(leakCount('See C:/Users/alex/notes.md for details.') === 1, 'C:/Users... not detected'); }); +test('lowercase Windows drive path is detected', () => { + assert(leakCount('see c:\\Users\\alex\\notes.md') === 1, 'c:\\Users... not detected'); + assert(leakCount('see c:/users/alex/notes.md') === 1, 'c:/users... not detected'); +}); + +test('URLs are not flagged as drive-letter leaks', () => { + // https:// also contains ":/"; the \b in the pattern must exclude it. + assert(leakCount('docs at https://github.com/org/repo and http://example.com') === 0, 'URL falsely flagged'); +}); + test('Unix /Users path is detected', () => { assert(leakCount('open /Users/alex/secret.md') === 1, '/Users path not detected'); }); diff --git a/tools/validate-file-refs.js b/tools/validate-file-refs.js index ad6365ad9..4358bfe3f 100644 --- a/tools/validate-file-refs.js +++ b/tools/validate-file-refs.js @@ -67,9 +67,10 @@ const STEP_META = /(?:thisStepFile|nextStepFile|continueStepFile|skipToStepFile| const LOAD_DIRECTIVE = /Load[:\s]+`(\.[^`]+)`/g; // Pattern: absolute path leaks -// Windows drive paths use a single separator (C:\Users or C:/Users). In a regex -// literal `\\` already matches one backslash, so the class matches either separator. -const ABS_PATH_LEAK = /(?:\/Users\/|\/home\/|[A-Z]:[\\/])/; +// Windows drive paths use a single separator (C:\Users or C:/Users) and the drive +// letter can be either case. The leading \b keeps URL schemes like https:// — which +// also contain ":/" — from matching. In a regex literal `\\` is one backslash. +const ABS_PATH_LEAK = /(?:\/Users\/|\/home\/|\b[A-Za-z]:[\\/])/; // --- Output Escaping ---