This commit is contained in:
Oleg Maslov
2026-09-02 10:14:22 +02:00
parent 0c3e2ead3b
commit b20b138fe4
771 changed files with 161561 additions and 9027 deletions

View File

@@ -26,12 +26,18 @@ describe('codex session-start handler', () => {
expect(bridge.recallMemory).toHaveBeenCalledWith('', { limit: 1, scope: 'personal', workspace: null });
expect(cap.stdout).toHaveLength(1);
const parsed = JSON.parse(cap.stdout[0]) as {
hookSpecificOutput: { source: string; additionalContext: string };
hookSpecificOutput: { hookEventName: string; additionalContext: string };
};
// Codex has no custom formatInject ⇒ the default CC hookSpecificOutput shape,
// stamped with source 'codex'.
expect(parsed.hookSpecificOutput.source).toBe('codex');
expect(parsed.hookSpecificOutput.additionalContext).toContain('past observation');
expect(parsed).toEqual({
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: expect.stringContaining('past observation'),
},
});
expect(Object.keys(parsed.hookSpecificOutput).sort()).toEqual([
'additionalContext',
'hookEventName',
]);
expect(cap.exits).toEqual([0]);
});

View File

@@ -11,6 +11,11 @@ import { HIVE_MIND_MARKER } from '../src/adapter.js';
const execFileAsync = promisify(execFile);
function decodedHookCommand(command: string): string {
const match = /^[A-Za-z]:\\[A-Za-z0-9._\\-]+\\System32\\WindowsPowerShell\\v1\.0\\powershell\.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand ([A-Za-z0-9+/=]+)$/.exec(command);
return match ? Buffer.from(match[1], 'base64').toString('utf16le') : command;
}
// This file's last test spawns the COMPILED CLI (dist/bin/codex-hooks.js).
// dist/ is gitignored and a clean CI checkout runs no build step, so the
// artifact is absent there — skip (don't fail) when it's missing. The other
@@ -153,8 +158,18 @@ describe('install (codex)', () => {
const after = JSON.parse(await readFile(env.configPath, 'utf-8')) as {
hooks: Record<string, Array<{ hooks: Array<{ command: string }> }>>;
};
expect(after.hooks.SessionStart[0].hooks[0].command).toContain(`--cli-path "${cliPath}"`);
expect(after.hooks.Stop[0].hooks[0].command).toContain(`--cli-path "${cliPath}"`);
for (const command of [
after.hooks.SessionStart[0].hooks[0].command,
after.hooks.Stop[0].hooks[0].command,
]) {
if (process.platform === 'win32') {
expect(command).toMatch(/^[A-Za-z]:\\[A-Za-z0-9._\\-]+\\System32\\WindowsPowerShell\\v1\.0\\powershell\.exe .* -EncodedCommand [A-Za-z0-9+/=]+$/);
expect(command).not.toContain('"');
}
const payload = decodedHookCommand(command);
expect(payload).toContain('--cli-path');
expect(payload).toContain(cliPath);
}
const pointer = JSON.parse(await readFile(result.pointerPath, 'utf-8')) as Record<string, unknown>;
expect(pointer['cli_path']).toBe(cliPath);

View File

@@ -1,3 +1,6 @@
import { spawnSync } from 'node:child_process';
import { copyFile, mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { describe, expect, it } from 'vitest';
import { join, resolve } from 'node:path';
import {
@@ -33,26 +36,193 @@ describe('resolvePaths (codex)', () => {
});
});
describe('hookCommandFor (codex, 2-arg shared helper)', () => {
describe('hookCommandFor (codex)', () => {
it('produces a quoted node invocation around the absolute script path', () => {
const cmd = hookCommandFor(resolve('/abs/dist/hooks/session-start.js'));
const cmd = hookCommandFor(resolve('/abs/dist/hooks/session-start.js'), undefined, { platform: 'linux' });
expect(cmd).toMatch(/^node "[^"]+session-start\.js"$/);
});
it('appends --cli-path when supplied', () => {
const cmd = hookCommandFor(resolve('/abs/dist/hooks/session-start.js'), '/abs/cli/dist/index.js');
const cmd = hookCommandFor(
resolve('/abs/dist/hooks/session-start.js'),
'/abs/cli/dist/index.js',
{ platform: 'linux' },
);
expect(cmd).toMatch(/--cli-path "\/abs\/cli\/dist\/index\.js"$/);
});
it('omits --cli-path when empty string is passed', () => {
const cmd = hookCommandFor(resolve('/abs/dist/hooks/session-start.js'), '');
const cmd = hookCommandFor(resolve('/abs/dist/hooks/session-start.js'), '', { platform: 'linux' });
expect(cmd).not.toContain('--cli-path');
});
it('preserves Windows-style paths (with spaces) inside the quotes', () => {
const cmd = hookCommandFor('/abs/dist/hooks/stop.js', 'C:\\Program Files\\hive-mind\\dist\\index.js');
expect(cmd).toContain('--cli-path "C:\\Program Files\\hive-mind\\dist\\index.js"');
it('rejects a Windows root unsafe to embed in either host shell', () => {
expect(() => hookCommandFor('C:\\hooks\\session-start.js', undefined, {
platform: 'win32',
nodePath: 'C:\\runtime\\node.exe',
systemRoot: 'C:\\Windows & attacker',
})).toThrow(/SystemRoot/);
});
it.runIf(process.platform === 'win32')(
'survives Codex Rust cmd.exe /C dispatch with spaces and apostrophes',
async () => {
const root = await mkdtemp(join(tmpdir(), "codex hook O'Brien "));
try {
const stdinMarker = 'CODEX_STDIN_čćžšđ_漢_🐝';
const stdoutMarker = 'CODEX_STDOUT_čćžšđ_漢_🐝';
const stderrMarker = 'CODEX_STDERR_čćžšđ_漢_🐝';
const fixtureDir = join(root, "fixture dir O'Brien");
const scriptPath = join(fixtureDir, "hook O'Brien.mjs");
const cliPath = join(root, "cli dir O'Brien", 'index.js');
const nodePath = join(root, "node runtime O'Brien.exe");
const decoyPowerShell = join(root, 'powershell.exe');
const rustSource = join(root, 'codex-runner.rs');
const rustExe = join(root, 'codex-runner.exe');
await mkdir(fixtureDir, { recursive: true });
await copyFile(process.execPath, nodePath);
await writeFile(decoyPowerShell, 'DECOY_WORKSPACE_POWERSHELL', 'utf8');
await writeFile(scriptPath, [
"let stdin = '';",
`const stdoutMarker = ${JSON.stringify(stdoutMarker)};`,
"process.stdin.setEncoding('utf8');",
"for await (const chunk of process.stdin) stdin += chunk;",
"process.stdout.write(JSON.stringify({ stdin, argv: process.argv.slice(2), stdoutMarker }));",
`process.stderr.write(${JSON.stringify(stderrMarker)});`,
'process.exitCode = 23;',
'',
].join('\n'), 'utf8');
await writeFile(rustSource, String.raw`
use std::env;
use std::io::{self, Read, Write};
use std::process::{Command, Stdio};
fn main() {
let command = env::var("CODEX_TEST_COMMAND").expect("CODEX_TEST_COMMAND");
let comspec = env::var("ComSpec").unwrap_or_else(|_| "cmd.exe".to_string());
let mut input = Vec::new();
io::stdin().read_to_end(&mut input).expect("read stdin");
let mut child = Command::new(comspec)
.arg("/C")
.arg(command)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn cmd.exe");
child.stdin.take().expect("child stdin").write_all(&input).expect("forward stdin");
let output = child.wait_with_output().expect("wait for cmd.exe");
io::stdout().write_all(&output.stdout).expect("forward stdout");
io::stderr().write_all(&output.stderr).expect("forward stderr");
std::process::exit(output.status.code().unwrap_or(1));
}
`, 'utf8');
const compile = spawnSync('rustc', [rustSource, '-o', rustExe], {
encoding: 'utf8',
windowsHide: true,
});
expect(compile.status, compile.stderr).toBe(0);
const command = hookCommandFor(scriptPath, cliPath, {
platform: 'win32',
nodePath,
systemRoot: 'C:\\Windows',
});
expect(command).toMatch(/^C:\\Windows\\System32\\WindowsPowerShell\\v1\.0\\powershell\.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand [A-Za-z0-9+/=]+$/);
expect(command).not.toContain('"');
const payload = Buffer.from(command.split(' ').at(-1) as string, 'base64').toString('utf16le');
expect(payload).toContain(`& '${nodePath.replaceAll("'", "''")}'`);
const run = spawnSync(rustExe, [], {
cwd: root,
env: { ...process.env, CODEX_TEST_COMMAND: command },
input: stdinMarker,
encoding: 'utf8',
windowsHide: true,
});
expect(run.status).toBe(23);
expect(JSON.parse(run.stdout)).toEqual({
stdin: stdinMarker,
argv: ['--cli-path', cliPath],
stdoutMarker,
});
expect(run.stderr).toBe(stderrMarker);
const missingCommand = hookCommandFor(scriptPath, cliPath, {
platform: 'win32',
nodePath: join(root, 'missing-node.exe'),
systemRoot: 'C:\\Windows',
});
const missingRun = spawnSync(rustExe, [], {
cwd: root,
env: { ...process.env, CODEX_TEST_COMMAND: missingCommand },
encoding: 'utf8',
windowsHide: true,
});
expect(missingRun.status, missingRun.stderr).not.toBe(0);
} finally {
await rm(root, { recursive: true, force: true });
}
},
30_000,
);
it.runIf(process.platform === 'win32')(
'survives Codex PowerShell host dispatch',
async () => {
const root = await mkdtemp(join(tmpdir(), 'codex-powershell-host-'));
try {
const scriptPath = join(root, 'session-start.mjs');
const decoyPowerShell = join(root, 'powershell.exe');
await writeFile(scriptPath, 'process.stdin.pipe(process.stdout);\n', 'utf8');
await writeFile(decoyPowerShell, 'DECOY_WORKSPACE_POWERSHELL', 'utf8');
const command = hookCommandFor(scriptPath, undefined, {
platform: 'win32',
nodePath: process.execPath,
});
const hostPowerShell = join(
process.env.SystemRoot ?? 'C:\\Windows',
'System32',
'WindowsPowerShell',
'v1.0',
'powershell.exe',
);
const run = spawnSync(
hostPowerShell,
['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', command],
{
cwd: root,
input: 'POWERSHELL_HOST_OK',
encoding: 'utf8',
windowsHide: true,
},
);
expect(run.status, run.stderr).toBe(0);
expect(run.stdout).toBe('POWERSHELL_HOST_OK');
const missingCommand = hookCommandFor(scriptPath, undefined, {
platform: 'win32',
nodePath: join(root, 'missing-node.exe'),
});
const missingRun = spawnSync(
hostPowerShell,
['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', missingCommand],
{
cwd: root,
encoding: 'utf8',
windowsHide: true,
},
);
expect(missingRun.status, missingRun.stderr).not.toBe(0);
} finally {
await rm(root, { recursive: true, force: true });
}
},
15_000,
);
});
describe('backupPathFor (codex)', () => {

View File

@@ -21,7 +21,9 @@ function entry(
): JsonRegisterEntry {
return {
lifecycle,
command: hookCommandFor(`${HOOKS_DIR}/${basename}.js`),
// Registration-shape tests are platform-neutral; command dispatch itself
// is covered by paths.test.ts against Codex's exact Windows Rust runner.
command: hookCommandFor(`${HOOKS_DIR}/${basename}.js`, undefined, { platform: 'linux' }),
timeout,
};
}