mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-12 04:33:29 +08:00
test: add 3 tests for appendFile new-file creation, getExecCommand traversal, getAllSessions non-session skip
Round 109: - appendFile creating new file in non-existent directory (ensureDir + appendFileSync) - getExecCommand with ../ path traversal in binary (SAFE_NAME_REGEX allows ../) - getAllSessions skips .tmp files that don't match session filename format
This commit is contained in:
@@ -1489,6 +1489,29 @@ function runTests() {
|
|||||||
'Same string as explicit string arg is correctly rejected by SAFE_ARGS_REGEX');
|
'Same string as explicit string arg is correctly rejected by SAFE_ARGS_REGEX');
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
// ── Round 109: getExecCommand with ../ path traversal in binary — SAFE_NAME_REGEX allows it ──
|
||||||
|
console.log('\nRound 109: getExecCommand (path traversal in binary — SAFE_NAME_REGEX permits ../ in binary name):');
|
||||||
|
if (test('getExecCommand accepts ../../../etc/passwd as binary because SAFE_NAME_REGEX allows ../', () => {
|
||||||
|
const originalEnv = process.env.CLAUDE_PACKAGE_MANAGER;
|
||||||
|
try {
|
||||||
|
process.env.CLAUDE_PACKAGE_MANAGER = 'npm';
|
||||||
|
// SAFE_NAME_REGEX = /^[@a-zA-Z0-9_.\/-]+$/ individually allows . and /
|
||||||
|
const cmd = pm.getExecCommand('../../../etc/passwd');
|
||||||
|
assert.strictEqual(cmd, 'npx ../../../etc/passwd',
|
||||||
|
'Path traversal in binary passes SAFE_NAME_REGEX because . and / are individually allowed');
|
||||||
|
// Also verify scoped path traversal
|
||||||
|
const cmd2 = pm.getExecCommand('@scope/../../evil');
|
||||||
|
assert.strictEqual(cmd2, 'npx @scope/../../evil',
|
||||||
|
'Scoped path traversal also passes the regex');
|
||||||
|
} finally {
|
||||||
|
if (originalEnv !== undefined) {
|
||||||
|
process.env.CLAUDE_PACKAGE_MANAGER = originalEnv;
|
||||||
|
} else {
|
||||||
|
delete process.env.CLAUDE_PACKAGE_MANAGER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// ── Round 108: getRunCommand with path traversal — SAFE_NAME_REGEX allows ../ sequences ──
|
// ── Round 108: getRunCommand with path traversal — SAFE_NAME_REGEX allows ../ sequences ──
|
||||||
console.log('\nRound 108: getRunCommand (path traversal — SAFE_NAME_REGEX permits ../ via allowed / and . chars):');
|
console.log('\nRound 108: getRunCommand (path traversal — SAFE_NAME_REGEX permits ../ via allowed / and . chars):');
|
||||||
if (test('getRunCommand accepts @scope/../../evil because SAFE_NAME_REGEX allows ../', () => {
|
if (test('getRunCommand accepts @scope/../../evil because SAFE_NAME_REGEX allows ../', () => {
|
||||||
|
|||||||
@@ -1785,6 +1785,41 @@ file.ts
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
// ── Round 109: getAllSessions skips .tmp files that don't match session filename format ──
|
||||||
|
console.log('\nRound 109: getAllSessions (non-session .tmp files — parseSessionFilename returns null → skip):');
|
||||||
|
if (test('getAllSessions ignores .tmp files with non-matching filenames', () => {
|
||||||
|
const isoHome = path.join(os.tmpdir(), `ecc-r109-nonsession-${Date.now()}`);
|
||||||
|
const isoSessionsDir = path.join(isoHome, '.claude', 'sessions');
|
||||||
|
fs.mkdirSync(isoSessionsDir, { recursive: true });
|
||||||
|
// Create one valid session file
|
||||||
|
const validName = '2026-03-01-abcd1234-session.tmp';
|
||||||
|
fs.writeFileSync(path.join(isoSessionsDir, validName), '# Valid Session');
|
||||||
|
// Create non-session .tmp files that don't match the expected pattern
|
||||||
|
fs.writeFileSync(path.join(isoSessionsDir, 'notes.tmp'), 'personal notes');
|
||||||
|
fs.writeFileSync(path.join(isoSessionsDir, 'scratch.tmp'), 'scratch data');
|
||||||
|
fs.writeFileSync(path.join(isoSessionsDir, 'backup-2026.tmp'), 'backup');
|
||||||
|
const origHome = process.env.HOME;
|
||||||
|
const origUserProfile = process.env.USERPROFILE;
|
||||||
|
process.env.HOME = isoHome;
|
||||||
|
process.env.USERPROFILE = isoHome;
|
||||||
|
try {
|
||||||
|
delete require.cache[require.resolve('../../scripts/lib/session-manager')];
|
||||||
|
delete require.cache[require.resolve('../../scripts/lib/utils')];
|
||||||
|
const freshManager = require('../../scripts/lib/session-manager');
|
||||||
|
const result = freshManager.getAllSessions({ limit: 100 });
|
||||||
|
assert.strictEqual(result.total, 1,
|
||||||
|
'Should find only 1 valid session (non-matching .tmp files skipped via !metadata continue)');
|
||||||
|
assert.strictEqual(result.sessions[0].shortId, 'abcd1234',
|
||||||
|
'The one valid session should have correct shortId');
|
||||||
|
} finally {
|
||||||
|
process.env.HOME = origHome;
|
||||||
|
process.env.USERPROFILE = origUserProfile;
|
||||||
|
delete require.cache[require.resolve('../../scripts/lib/session-manager')];
|
||||||
|
delete require.cache[require.resolve('../../scripts/lib/utils')];
|
||||||
|
fs.rmSync(isoHome, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// ── Round 108: getSessionSize exact boundary at 1024 bytes — B→KB transition ──
|
// ── Round 108: getSessionSize exact boundary at 1024 bytes — B→KB transition ──
|
||||||
console.log('\nRound 108: getSessionSize (exact 1024-byte boundary — < means 1024 is KB, 1023 is B):');
|
console.log('\nRound 108: getSessionSize (exact 1024-byte boundary — < means 1024 is KB, 1023 is B):');
|
||||||
if (test('getSessionSize returns KB at exactly 1024 bytes and B at 1023', () => {
|
if (test('getSessionSize returns KB at exactly 1024 bytes and B at 1023', () => {
|
||||||
|
|||||||
@@ -1629,6 +1629,29 @@ function runTests() {
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
// ── Round 109: appendFile creating new file in non-existent directory (ensureDir + appendFileSync) ──
|
||||||
|
console.log('\nRound 109: appendFile (new file creation — ensureDir creates parent, appendFileSync creates file):');
|
||||||
|
if (test('appendFile creates parent directory and new file when neither exist', () => {
|
||||||
|
const tmpDir = fs.mkdtempSync(path.join(utils.getTempDir(), 'r109-append-new-'));
|
||||||
|
const nestedPath = path.join(tmpDir, 'deep', 'nested', 'dir', 'newfile.txt');
|
||||||
|
try {
|
||||||
|
// Parent directory 'deep/nested/dir' does not exist yet
|
||||||
|
assert.ok(!fs.existsSync(path.join(tmpDir, 'deep')),
|
||||||
|
'Parent "deep" should not exist before appendFile');
|
||||||
|
utils.appendFile(nestedPath, 'first line\n');
|
||||||
|
assert.ok(fs.existsSync(nestedPath),
|
||||||
|
'File should be created by appendFile');
|
||||||
|
assert.strictEqual(utils.readFile(nestedPath), 'first line\n',
|
||||||
|
'Content should match what was appended');
|
||||||
|
// Append again to verify it adds to existing file
|
||||||
|
utils.appendFile(nestedPath, 'second line\n');
|
||||||
|
assert.strictEqual(utils.readFile(nestedPath), 'first line\nsecond line\n',
|
||||||
|
'Second append should add to existing file');
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// ── Round 108: grepFile with Unicode/emoji content — UTF-16 string matching on split lines ──
|
// ── Round 108: grepFile with Unicode/emoji content — UTF-16 string matching on split lines ──
|
||||||
console.log('\nRound 108: grepFile (Unicode/emoji — regex matching on UTF-16 split lines):');
|
console.log('\nRound 108: grepFile (Unicode/emoji — regex matching on UTF-16 split lines):');
|
||||||
if (test('grepFile finds Unicode emoji patterns across lines', () => {
|
if (test('grepFile finds Unicode emoji patterns across lines', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user