From 635eb108abd97c2c6b05d51222d0e1a315d4aab4 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 17:41:58 -0800 Subject: [PATCH] test: add 3 tests for nested backtick context truncation, newline args injection, alias 128-char boundary Round 111: Tests for parseSessionMetadata context regex truncation at nested triple backticks (lazy [\s\S]*? stops early), getExecCommand accepting newline/tab/CR in args via \s in SAFE_ARGS_REGEX, and setAlias accepting exactly 128-character alias (off-by-one boundary). 893 tests total. --- tests/lib/package-manager.test.js | 27 +++++++++++++++++++++ tests/lib/session-aliases.test.js | 23 ++++++++++++++++++ tests/lib/session-manager.test.js | 39 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/tests/lib/package-manager.test.js b/tests/lib/package-manager.test.js index e2066f0f..a9b32f7b 100644 --- a/tests/lib/package-manager.test.js +++ b/tests/lib/package-manager.test.js @@ -1536,6 +1536,33 @@ function runTests() { } })) passed++; else failed++; + // ── Round 111: getExecCommand with newline in args — SAFE_ARGS_REGEX \s includes \n ── + console.log('\nRound 111: getExecCommand (newline in args — SAFE_ARGS_REGEX \\s matches \\n):'); + if (test('getExecCommand accepts newline in args because SAFE_ARGS_REGEX \\s includes \\n', () => { + // SAFE_ARGS_REGEX = /^[@a-zA-Z0-9\s_.\/:=,'"*+-]+$/ + // \s matches [\t\n\v\f\r ] — includes newline! + // This means "file.js\nmalicious" passes the regex. + const originalEnv = process.env.CLAUDE_PACKAGE_MANAGER; + try { + process.env.CLAUDE_PACKAGE_MANAGER = 'npm'; + // Newline in args should pass SAFE_ARGS_REGEX because \s matches \n + const cmd = pm.getExecCommand('prettier', 'file.js\necho injected'); + assert.strictEqual(cmd, 'npx prettier file.js\necho injected', + 'Newline passes SAFE_ARGS_REGEX (\\s includes \\n) — potential command injection vector'); + // Tab also passes + const cmd2 = pm.getExecCommand('eslint', 'file.js\t--fix'); + assert.strictEqual(cmd2, 'npx eslint file.js\t--fix', + 'Tab also passes SAFE_ARGS_REGEX via \\s'); + // Carriage return also passes + const cmd3 = pm.getExecCommand('tsc', 'src\r--strict'); + assert.strictEqual(cmd3, 'npx tsc src\r--strict', + 'Carriage return passes via \\s'); + } finally { + if (originalEnv !== undefined) process.env.CLAUDE_PACKAGE_MANAGER = originalEnv; + else delete process.env.CLAUDE_PACKAGE_MANAGER; + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); diff --git a/tests/lib/session-aliases.test.js b/tests/lib/session-aliases.test.js index d6c9eefb..3ae74345 100644 --- a/tests/lib/session-aliases.test.js +++ b/tests/lib/session-aliases.test.js @@ -1395,6 +1395,29 @@ function runTests() { 'Whitespace title persists in JSON as-is'); })) passed++; else failed++; + // ── Round 111: setAlias with exactly 128-character alias — off-by-one boundary ── + console.log('\nRound 111: setAlias (128-char alias — exact boundary of > 128 check):'); + if (test('setAlias accepts alias of exactly 128 characters (128 is NOT > 128)', () => { + // session-aliases.js line 199: if (alias.length > 128) + // 128 is NOT > 128, so exactly 128 chars is ACCEPTED. + // Existing test only checks 129 (rejected). + resetAliases(); + const alias128 = 'a'.repeat(128); + const result = aliases.setAlias(alias128, '/path/to/session'); + assert.strictEqual(result.success, true, + '128-char alias should be accepted (128 is NOT > 128)'); + assert.strictEqual(result.isNew, true); + // Verify it can be resolved + const resolved = aliases.resolveAlias(alias128); + assert.notStrictEqual(resolved, null, '128-char alias should be resolvable'); + assert.strictEqual(resolved.sessionPath, '/path/to/session'); + // Confirm 129 is rejected (boundary) + const result129 = aliases.setAlias('b'.repeat(129), '/path'); + assert.strictEqual(result129.success, false, '129-char alias should be rejected'); + assert.ok(result129.error.includes('128'), + 'Error message should mention 128-char limit'); + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); diff --git a/tests/lib/session-manager.test.js b/tests/lib/session-manager.test.js index 366397a3..7717e6c2 100644 --- a/tests/lib/session-manager.test.js +++ b/tests/lib/session-manager.test.js @@ -1891,6 +1891,45 @@ file.ts assert.strictEqual(lowerResult.shortId, 'abcd1234'); })) passed++; else failed++; + // ── Round 111: parseSessionMetadata context with nested triple backticks — lazy regex truncation ── + console.log('\nRound 111: parseSessionMetadata (nested ``` in context — lazy \\S*? stops at first ```):");'); + if (test('parseSessionMetadata context capture truncated by nested triple backticks', () => { + // The regex: /### Context to Load\s*\n```\n([\s\S]*?)```/ + // The lazy [\s\S]*? matches as few chars as possible, so it stops at the + // FIRST ``` it encounters — even if that's inside the code block content. + const content = [ + '# Session', + '', + '### Context to Load', + '```', + 'const x = 1;', + '```nested code block```', // Inner ``` causes premature match end + 'const y = 2;', + '```' + ].join('\n'); + const meta = sessionManager.parseSessionMetadata(content); + // Lazy regex stops at the inner ```, so context only captures "const x = 1;\n" + assert.ok(meta.context.includes('const x = 1'), + 'Context should contain text before the inner backticks'); + assert.ok(!meta.context.includes('const y = 2'), + 'Context should NOT contain text after inner ``` (lazy regex stops early)'); + // Without nested backticks, full content is captured + const cleanContent = [ + '# Session', + '', + '### Context to Load', + '```', + 'const x = 1;', + 'const y = 2;', + '```' + ].join('\n'); + const cleanMeta = sessionManager.parseSessionMetadata(cleanContent); + assert.ok(cleanMeta.context.includes('const x = 1'), + 'Clean context should have first line'); + assert.ok(cleanMeta.context.includes('const y = 2'), + 'Clean context should have second line'); + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0);