mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-27 00:31:11 +08:00
* fix(clv2): escape $HOME before pgrep -f in migrate-homunculus.sh pgrep -f treats its argument as an extended regular expression, but the running-observer guard interpolated $HOME unescaped. Paths containing regex metacharacters (e.g. /home/user.name, /home/c++dev, /home/user (work)) made the match over-broad or invalid, causing either a false negative (live observer missed, migration proceeds and risks registry corruption) or a false positive (migration blocked unnecessarily). Escape the ERE metacharacters in $HOME via sed before building the pattern so the home prefix is matched literally while the trailing .*observer-loop\.sh regex is preserved. Portable across BSD and GNU sed. Fixes #2301 * test(clv2): add regression test for migrate-homunculus.sh $HOME escaping Guards the #2301 fix: extracts the script's sed escaping command and asserts the resulting pgrep -f pattern matches the literal home path while no longer over-matching a regex-expanded decoy (HOME=/home/user.name must not match /home/userXname). Also pins that the guard uses escaped_home rather than $HOME directly. Follows the existing clv2 shell-test convention in tests/hooks/observe-entrypoint-allowlist.test.js. Refs #2301 * test(clv2): skip migrate-homunculus escaping test on Windows The test relies on POSIX bash/sed/grep -E semantics, which differ on the Windows CI runners. Guard with the same process.platform === 'win32' early exit used by tests/hooks/observe-subdirectory-detection.test.js so the bash-dependent assertions only run on POSIX platforms. Refs #2301
142 lines
4.7 KiB
JavaScript
142 lines
4.7 KiB
JavaScript
/**
|
|
* Regression test for migrate-homunculus.sh $HOME escaping (#2301).
|
|
*
|
|
* The running-observer guard in migrate-homunculus.sh builds a `pgrep -f`
|
|
* pattern from $HOME. `pgrep -f` treats its argument as an extended regular
|
|
* expression, so an unescaped $HOME containing regex metacharacters (e.g.
|
|
* /home/user.name, /home/c++dev, /home/user (work)) made the match over-broad
|
|
* or invalid. That caused either a false negative (a live observer-loop.sh is
|
|
* missed and the migration proceeds unsafely) or a false positive (an unrelated
|
|
* process matches and the migration is blocked).
|
|
*
|
|
* The fix escapes the ERE metacharacters in $HOME before interpolation. This
|
|
* test pins that behavior by extracting the exact `sed` escaping command from
|
|
* the script (so it tests the real implementation, not a copy), then asserting
|
|
* that, for HOME values containing metacharacters:
|
|
* (a) the escaped pattern matches the literal home path, and
|
|
* (b) the escaped pattern does NOT over-match a decoy path that the
|
|
* unescaped (regex-expanded) form would have matched.
|
|
*
|
|
* Run with: node tests/hooks/migrate-homunculus-home-escape.test.js
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
// migrate-homunculus.sh and this test's assertions rely on POSIX bash, sed, and
|
|
// grep -E semantics. Skip on Windows, matching the repo convention for
|
|
// bash-dependent clv2 tests (see tests/hooks/observe-subdirectory-detection.test.js).
|
|
if (process.platform === 'win32') {
|
|
console.log('Skipping bash-dependent migrate-homunculus tests on Windows');
|
|
process.exit(0);
|
|
}
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
const scriptPath = path.join(
|
|
repoRoot,
|
|
'skills',
|
|
'continuous-learning-v2',
|
|
'scripts',
|
|
'migrate-homunculus.sh'
|
|
);
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` ✓ ${name}`);
|
|
passed++;
|
|
} catch (err) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` Error: ${err.message}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
const scriptSource = fs.readFileSync(scriptPath, 'utf8');
|
|
|
|
// Extract the exact sed escaping command from the script so this test verifies
|
|
// the real implementation. Expected form:
|
|
// escaped_home="$(printf '%s' "$HOME" | sed 's/.../\\&/g')"
|
|
const sedMatch = scriptSource.match(
|
|
/escaped_home="\$\(printf '%s' "\$HOME" \| (sed '[^']*')\)"/
|
|
);
|
|
|
|
// Build the pgrep pattern exactly as the script does: ${escaped_home} followed
|
|
// by the literal observer-loop.sh regex tail.
|
|
function buildPattern(home) {
|
|
assert.ok(
|
|
sedMatch,
|
|
'could not locate the escaped_home sed command in migrate-homunculus.sh; ' +
|
|
'the fix for #2301 must escape $HOME before pgrep -f'
|
|
);
|
|
const sedCmd = sedMatch[1];
|
|
const res = spawnSync(
|
|
'bash',
|
|
['-c', `printf '%s' "$1" | ${sedCmd}`, 'bash', home],
|
|
{ encoding: 'utf8' }
|
|
);
|
|
assert.strictEqual(
|
|
res.status,
|
|
0,
|
|
`sed escaping failed for HOME=${home}: ${res.stderr}`
|
|
);
|
|
return `${res.stdout}.*observer-loop\\.sh`;
|
|
}
|
|
|
|
// grep -E uses the same ERE engine as pgrep -f. Return true if cmdline matches.
|
|
function ereMatches(pattern, cmdline) {
|
|
const res = spawnSync('grep', ['-E', pattern], {
|
|
input: cmdline,
|
|
encoding: 'utf8',
|
|
});
|
|
return res.status === 0;
|
|
}
|
|
|
|
console.log('\n=== migrate-homunculus.sh $HOME escaping (#2301) ===\n');
|
|
|
|
test('the running-observer guard no longer interpolates $HOME unescaped', () => {
|
|
assert.ok(
|
|
!/pgrep -f "\$\{HOME\}/.test(scriptSource),
|
|
'pgrep -f must not use ${HOME} directly; it must use the escaped value'
|
|
);
|
|
assert.ok(
|
|
/pgrep -f "\$\{escaped_home\}/.test(scriptSource),
|
|
'pgrep -f must use the escaped_home value built from $HOME'
|
|
);
|
|
});
|
|
|
|
const problemHomes = ['/home/user.name', '/home/c++dev', '/home/user (work)', '/tmp/h[x]'];
|
|
|
|
for (const home of problemHomes) {
|
|
test(`escaped pattern matches the literal home ${home}`, () => {
|
|
const pattern = buildPattern(home);
|
|
const cmdline = `/bin/bash ${home}/.local/share/ecc-homunculus/observer-loop.sh`;
|
|
assert.ok(
|
|
ereMatches(pattern, cmdline),
|
|
`expected escaped pattern to match the literal observer cmdline for HOME=${home}`
|
|
);
|
|
});
|
|
}
|
|
|
|
test('escaped "." does not over-match a different path (#2301 false positive)', () => {
|
|
const pattern = buildPattern('/home/user.name');
|
|
// The unescaped form ("." as any-char) would match /home/userXname; the
|
|
// escaped form must not.
|
|
const decoy = '/bin/bash /home/userXname/observer-loop.sh';
|
|
assert.ok(
|
|
!ereMatches(pattern, decoy),
|
|
'escaped pattern must not over-match /home/userXname when HOME=/home/user.name'
|
|
);
|
|
});
|
|
|
|
console.log(`\nPassed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|