Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions tests/kit/provider-cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// real machine's kit.json or ~/.claude — src/lib/paths.mjs's configBase()
// reads APPDATA (not XDG_CONFIG_HOME) on win32, so both must be set or the
// sandbox is silently bypassed there.
import { test } from 'node:test';
import { test, after } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
Expand All @@ -14,6 +14,22 @@ import os from 'node:os';
import path from 'node:path';
import { DUAL_ROLE_TIP, JUDGE_BIAS_TIP } from '../../src/lib/providers.mjs';

// Tripwire (#137): a spawned `ak x host pick` whose cwd falls back to the test
// process's cwd writes PROJECT-scoped config (.claude/settings.local.json,
// .agentic-qe/llm-config.json) into the REAL repository the suite runs from —
// HOME sandboxing cannot catch that class of leak. Record the real cwd's state
// at module load and prove it byte-identical when the suite ends.
const REAL_PROJECT_FILES = ['.claude/settings.local.json', '.agentic-qe/llm-config.json']
.map((rel) => path.resolve(process.cwd(), rel));
const readOrNull = (f) => { try { return fs.readFileSync(f, 'utf8'); } catch { return null; } };
const realProjectBefore = REAL_PROJECT_FILES.map(readOrNull);
after(() => {
REAL_PROJECT_FILES.forEach((f, i) => {
assert.equal(readOrNull(f), realProjectBefore[i],
`${f} was modified by this suite — a spawned ak command leaked out of the sandbox (cwd not anchored to the sandbox project?)`);
});
});

const BIN = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../bin/agentic-kit.mjs');

function sandbox({ hosts }) {
Expand Down Expand Up @@ -163,10 +179,13 @@ function pickSandbox({
return { home, project, binDir, catalog };
}

function akPick(args, { cwd, home, binDir, catalog }, { input, env = {} } = {}) {
function akPick(args, { cwd, project, home, binDir, catalog }, { input, env = {} } = {}) {
return spawnSync(process.execPath, [BIN, ...args], {
encoding: 'utf8',
cwd,
// Anchor at the sandbox project (#137): an undefined cwd inherits the test
// process's cwd — the real repository — and pick's PROJECT-scoped writes
// (settings.local.json, llm-config.json) would land there.
cwd: cwd ?? project,
input,
env: {
...process.env,
Expand Down
Loading