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 ---