feat(mosaic): WI-2 mutator-class guard for directive-freshness (#837)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful

WI-2: mutator-class guard for the compaction directive-freshness mechanism — command-position parser (prefix x var-indirection unified) + primitive-anchored invariant backstop + all-tools-hook fail-close. Parser-complete on principle: realistic evasion matrix RED-regression-covered, residual exotic evasions proven backstop-caught (B-tests), lens-convergence reached.

closes #829
This commit was merged in pull request #837.
This commit is contained in:
2026-07-18 05:51:58 +00:00
parent 8ec67a1126
commit abd2791f59
27 changed files with 2579 additions and 46 deletions

View File

@@ -0,0 +1,667 @@
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { createConnection } from 'node:net';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
import { afterEach, describe, expect, test } from 'vitest';
import { launchClaudex, type ClaudexHarnessAdapter } from '../commands/claudex.js';
interface BrokerReply {
ok: boolean;
code?: string;
decision?: 'allow' | 'deny';
state?: 'UNVERIFIED' | 'PENDING_VERIFICATION' | 'VERIFIED';
session_id?: string;
promotion_token?: string;
}
interface BrokerPaths {
socket: string;
}
const frameworkRoot = new URL('../../framework/', import.meta.url).pathname;
const daemonPath = join(frameworkRoot, 'tools/lease-broker/daemon.py');
const gatePath = join(frameworkRoot, 'tools/lease-broker/mutator-gate.py');
const launchGuardPath = join(frameworkRoot, 'tools/lease-broker/check-runtime-launches.py');
const launcherPath = join(frameworkRoot, 'tools/lease-broker/launch-runtime.py');
const claudeSettingsPath = join(frameworkRoot, 'runtime/claude/settings.json');
const piExtensionPath = join(frameworkRoot, 'runtime/pi/mosaic-extension.ts');
const prdyInitPath = join(frameworkRoot, 'tools/prdy/prdy-init.sh');
const prdyUpdatePath = join(frameworkRoot, 'tools/prdy/prdy-update.sh');
const remediationHandlerPath = join(frameworkRoot, 'tools/qa/remediation-hook-handler.sh');
const children: ChildProcess[] = [];
const temporaryRoots: string[] = [];
const binding = (compaction_epoch = 1) => ({
compaction_epoch,
request_epoch: 0,
h_source: 'a'.repeat(64),
h_payload: 'b'.repeat(64),
schema_version: 1,
});
async function request(socketPath: string, requestValue: object): Promise<BrokerReply> {
return await new Promise<BrokerReply>((resolve, reject) => {
const socket = createConnection(socketPath);
let response = '';
socket.setEncoding('utf8');
socket.once('error', reject);
socket.on('data', (chunk: string) => {
response += chunk;
});
socket.once('end', () => resolve(JSON.parse(response) as BrokerReply));
socket.once('connect', () => socket.end(`${JSON.stringify(requestValue)}\n`));
});
}
async function startBroker(): Promise<BrokerPaths> {
const root = await mkdtemp(join(tmpdir(), 'mosaic-mutator-gate-'));
await chmod(root, 0o700);
const socket = join(root, 'broker.sock');
const child = spawn(
'python3',
[daemonPath, '--socket', socket, '--state', join(root, 'state.json')],
{
stdio: ['ignore', 'pipe', 'pipe'],
},
);
children.push(child);
await new Promise<void>((resolve, reject) => {
let stderr = '';
child.stderr?.setEncoding('utf8');
child.stderr?.on('data', (chunk: string) => (stderr += chunk));
child.once('error', reject);
child.once('exit', (code: number | null) =>
reject(new Error(`broker exited ${code}: ${stderr}`)),
);
child.stdout?.once('data', () => resolve());
});
return { socket };
}
interface RuntimeLaunchEntry {
name: string;
script: string;
prepare(root: string): Promise<string[]>;
}
const runtimeLaunchEntries: RuntimeLaunchEntry[] = [
{
name: 'prdy-init',
script: prdyInitPath,
prepare: async (root) => ['--project', root, '--name', 'Gate Test'],
},
{
name: 'prdy-update',
script: prdyUpdatePath,
prepare: async (root) => {
await mkdir(join(root, 'docs'), { recursive: true });
await writeFile(join(root, 'docs/PRD.md'), '# Existing PRD\n');
return ['--project', root];
},
},
{
name: 'qa-remediation',
script: remediationHandlerPath,
prepare: async (root) => {
const pending = join(root, 'reports/pending');
await mkdir(pending, { recursive: true });
const report = join(pending, 'gate_remediation_needed.md');
await writeFile(report, '# remediation\n');
return [report];
},
},
];
async function runRuntimeLaunchEntry(entry: RuntimeLaunchEntry, socket: string) {
const root = await mkdtemp(join(tmpdir(), `mosaic-${entry.name}-gate-`));
temporaryRoots.push(root);
const binDir = join(root, 'bin');
await mkdir(binDir, { recursive: true });
const fakeClaude = join(binDir, 'claude');
await writeFile(
fakeClaude,
`#!/usr/bin/env python3
import json
import os
import subprocess
session_id = os.environ.get("MOSAIC_LEASE_SESSION_ID", "")
denied = subprocess.run(
["python3", ${JSON.stringify(gatePath)}, "--runtime", "claude"],
input=json.dumps({"tool_name": "Bash"}) + "\\n",
text=True,
capture_output=True,
env=os.environ,
).returncode == 2
print("RUNTIME_PROBE=" + json.dumps({"session_id": session_id, "denied": denied}))
raise SystemExit(0 if len(session_id) == 64 and denied else 1)
`,
{ mode: 0o700 },
);
await chmod(fakeClaude, 0o700);
const args = await entry.prepare(root);
return spawnSync('bash', [entry.script, ...args], {
cwd: root,
encoding: 'utf8',
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH ?? ''}`,
MOSAIC_HOME: frameworkRoot,
MOSAIC_PRDY_RUNTIME: 'claude',
MOSAIC_LEASE_BROKER_SOCKET: socket,
MOSAIC_RUNTIME_GENERATION: '1',
},
});
}
async function register(socket: string, runtime_generation = 1): Promise<string> {
const reply = await request(socket, { action: 'register_anchor', runtime_generation });
expect(reply.ok).toBe(true);
expect(reply.session_id).toMatch(/^[a-f0-9]{64}$/);
return reply.session_id!;
}
async function beginVerification(
socket: string,
session_id: string,
runtime: 'claude' | 'pi',
runtime_generation = 1,
ttl_seconds = 300,
compactionEpoch = 1,
): Promise<BrokerReply> {
return await request(socket, {
action: 'begin_verification',
session_id,
runtime_generation,
runtime,
ttl_seconds,
binding: binding(compactionEpoch),
});
}
async function promote(
socket: string,
session_id: string,
promotion_token: string,
runtime_generation = 1,
): Promise<BrokerReply> {
return await request(socket, {
action: 'promote_lease',
session_id,
runtime_generation,
promotion_token,
});
}
async function authorize(
socket: string,
session_id: string,
runtime: 'claude' | 'pi',
tool_name: string,
runtime_generation = 1,
): Promise<BrokerReply> {
return await request(socket, {
action: 'authorize_tool',
session_id,
runtime_generation,
runtime,
tool_name,
});
}
function runRuntimeGate(
socket: string,
sessionId: string,
runtime: 'claude' | 'pi',
toolName: string,
generation = 1,
) {
return spawnSync('python3', [gatePath, '--runtime', runtime], {
input: `${JSON.stringify({ tool_name: toolName })}\n`,
encoding: 'utf8',
env: {
...process.env,
MOSAIC_LEASE_BROKER_SOCKET: socket,
MOSAIC_LEASE_SESSION_ID: sessionId,
MOSAIC_RUNTIME_GENERATION: String(generation),
},
});
}
afterEach(async () => {
for (const child of children.splice(0)) child.kill('SIGTERM');
await Promise.all(
temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
);
});
describe('whole mutator-class lease gate', () => {
test('revoke-first and promote-last structurally bracket mutator authority', async () => {
const { socket } = await startBroker();
const sessionId = await register(socket);
expect(await promote(socket, sessionId, 'c'.repeat(64))).toMatchObject({
ok: false,
code: 'INVALID_LEASE_TRANSITION',
});
expect(await authorize(socket, sessionId, 'claude', 'Bash')).toMatchObject({
ok: false,
code: 'MUTATOR_UNVERIFIED',
decision: 'deny',
});
const pending = await beginVerification(socket, sessionId, 'claude');
expect(pending).toMatchObject({ ok: true, state: 'PENDING_VERIFICATION' });
expect(pending.promotion_token).toMatch(/^[a-f0-9]{64}$/);
expect(await authorize(socket, sessionId, 'claude', 'Write')).toMatchObject({
ok: false,
decision: 'deny',
});
expect(await promote(socket, sessionId, pending.promotion_token!)).toMatchObject({
ok: true,
state: 'VERIFIED',
});
expect(await authorize(socket, sessionId, 'claude', 'Bash')).toMatchObject({
ok: true,
decision: 'allow',
state: 'VERIFIED',
});
const nextCycle = await beginVerification(socket, sessionId, 'claude', 1, 300, 2);
expect(nextCycle).toMatchObject({ ok: true, state: 'PENDING_VERIFICATION' });
expect(await authorize(socket, sessionId, 'claude', 'Edit')).toMatchObject({
ok: false,
decision: 'deny',
});
expect(await promote(socket, sessionId, pending.promotion_token!)).toMatchObject({
ok: false,
code: 'PROMOTION_TOKEN_MISMATCH',
});
});
test('non-dangerous parser residual is denied by the global all-tools hook without a lease', async () => {
const root = await mkdtemp(join(tmpdir(), 'mosaic-parser-residual-'));
temporaryRoots.push(root);
const source = join(root, 'packages/probe/launch.sh');
await mkdir(join(root, 'packages/probe'), { recursive: true });
await writeFile(source, 'alias hidden_runtime=claude\nhidden_runtime -p x\n');
const parserResult = spawnSync('python3', [launchGuardPath, '--root', root, '--json'], {
encoding: 'utf8',
});
expect(parserResult.status).toBe(0);
expect(JSON.parse(parserResult.stdout)).toMatchObject({ gated: 0, total: 0 });
const settings = JSON.parse(await readFile(claudeSettingsPath, 'utf8')) as {
hooks: { PreToolUse: Array<{ matcher: string; hooks: Array<{ command: string }> }> };
};
const allToolsHook = settings.hooks.PreToolUse.find((hook) => hook.matcher === '.*');
expect(allToolsHook?.hooks[0]?.command).toContain('mutator-gate.py --runtime claude');
const environment = { ...process.env };
delete environment['MOSAIC_LEASE_SESSION_ID'];
delete environment['MOSAIC_LEASE_BROKER_SOCKET'];
for (const toolName of ['Bash', 'Read', 'mcp__provider__custom']) {
const gateResult = spawnSync('python3', [gatePath, '--runtime', 'claude'], {
input: `${JSON.stringify({ tool_name: toolName })}\n`,
encoding: 'utf8',
env: environment,
});
expect(gateResult.status, toolName).toBe(2);
expect(gateResult.stderr, toolName).toContain('GATE_UNAVAILABLE');
}
});
test('T-B raw and custom mutator tools are default-denied without shell parsing', async () => {
const { socket } = await startBroker();
const sessionId = await register(socket);
const mutators: Array<['claude' | 'pi', string]> = [
['claude', 'Bash'],
['claude', 'Edit'],
['claude', 'Write'],
['claude', 'NotebookEdit'],
['claude', 'mcp__provider__close_issue'],
['pi', 'bash'],
['pi', 'edit'],
['pi', 'write'],
['pi', 'deploy'],
['pi', 'unknown_custom_tool'],
];
for (const [runtime, toolName] of mutators) {
expect(
await authorize(socket, sessionId, runtime, toolName),
`${runtime}:${toolName}`,
).toMatchObject({
ok: false,
code: 'MUTATOR_UNVERIFIED',
decision: 'deny',
});
}
for (const [runtime, toolName] of [
['claude', 'Read'],
['claude', 'Grep'],
['pi', 'read'],
['pi', 'grep'],
['pi', 'mosaic_context_recover'],
] as const) {
expect(await authorize(socket, sessionId, runtime, toolName)).toMatchObject({
ok: true,
decision: 'allow',
});
}
});
test('lease and tool validation failures remain fail-closed at the broker boundary', async () => {
const { socket } = await startBroker();
const sessionId = await register(socket);
const baseRequest = {
action: 'begin_verification',
session_id: sessionId,
runtime_generation: 1,
runtime: 'claude',
ttl_seconds: 300,
binding: binding(),
};
expect(await request(socket, { ...baseRequest, runtime: 'codex' })).toMatchObject({
ok: false,
code: 'INVALID_RUNTIME',
});
expect(
await request(socket, { ...baseRequest, binding: { ...binding(), h_source: 'bad' } }),
).toMatchObject({
ok: false,
code: 'INVALID_BINDING',
});
expect(await request(socket, { ...baseRequest, ttl_seconds: 0 })).toMatchObject({
ok: false,
code: 'INVALID_LEASE_TTL',
});
expect(
await request(socket, {
action: 'authorize_tool',
session_id: sessionId,
runtime_generation: 1,
runtime: 'codex',
tool_name: 'Read',
}),
).toMatchObject({ ok: false, code: 'INVALID_RUNTIME' });
expect(
await request(socket, {
action: 'authorize_tool',
session_id: sessionId,
runtime_generation: 1,
runtime: 'claude',
tool_name: 'x'.repeat(257),
}),
).toMatchObject({ ok: false, code: 'INVALID_TOOL' });
});
test('observer revocation and monotonic TTL expiry deny the next mutator', async () => {
const { socket } = await startBroker();
const sessionId = await register(socket);
const pending = await beginVerification(socket, sessionId, 'claude', 1, 1);
await promote(socket, sessionId, pending.promotion_token!);
expect(await authorize(socket, sessionId, 'claude', 'Bash')).toMatchObject({
ok: true,
decision: 'allow',
});
await new Promise((resolve) => setTimeout(resolve, 1_100));
expect(await authorize(socket, sessionId, 'claude', 'Bash')).toMatchObject({
ok: false,
code: 'LEASE_EXPIRED',
decision: 'deny',
});
const refreshed = await beginVerification(socket, sessionId, 'claude', 1, 300, 2);
await promote(socket, sessionId, refreshed.promotion_token!);
expect(
await request(socket, {
action: 'revoke_lease',
session_id: sessionId,
runtime_generation: 1,
reason: 'compaction_observer',
}),
).toMatchObject({ ok: true, state: 'UNVERIFIED' });
expect(await authorize(socket, sessionId, 'claude', 'Write')).toMatchObject({
ok: false,
code: 'MUTATOR_UNVERIFIED',
decision: 'deny',
});
});
test('runtime-generation replacement cannot inherit a verified lease', async () => {
const { socket } = await startBroker();
const sessionId = await register(socket);
const pending = await beginVerification(socket, sessionId, 'pi');
await promote(socket, sessionId, pending.promotion_token!);
expect(await authorize(socket, sessionId, 'pi', 'bash', 2)).toMatchObject({
ok: false,
code: 'MUTATOR_UNVERIFIED',
decision: 'deny',
});
expect(await authorize(socket, sessionId, 'pi', 'bash', 1)).toMatchObject({
ok: false,
code: 'STALE_GENERATION',
});
});
test('runtime launcher anchors broker identity before exec and fails closed without broker', async () => {
const { socket } = await startBroker();
const probe = [
'import json,os,socket',
's=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)',
"s.connect(os.environ['MOSAIC_LEASE_BROKER_SOCKET'])",
"request={'action':'authorize_tool','session_id':os.environ['MOSAIC_LEASE_SESSION_ID'],'runtime_generation':int(os.environ['MOSAIC_RUNTIME_GENERATION']),'runtime':'claude','tool_name':'Read'}",
"s.sendall((json.dumps(request)+'\\n').encode())",
's.shutdown(socket.SHUT_WR)',
"print(json.dumps({'session_id':os.environ['MOSAIC_LEASE_SESSION_ID'],'reply':json.loads(s.recv(65536))}))",
].join(';');
const launched = spawnSync(
'python3',
[launcherPath, '--runtime', 'claude', '--', 'python3', '-c', probe],
{
encoding: 'utf8',
env: {
...process.env,
MOSAIC_LEASE_BROKER_SOCKET: socket,
MOSAIC_RUNTIME_GENERATION: '1',
},
},
);
expect(launched.status, launched.stderr).toBe(0);
expect(JSON.parse(launched.stdout)).toMatchObject({
session_id: expect.stringMatching(/^[a-f0-9]{64}$/),
reply: { ok: true, decision: 'allow' },
});
const unavailable = spawnSync(
'python3',
[launcherPath, '--runtime', 'claude', '--', 'python3', '-c', "print('EXECUTED')"],
{
encoding: 'utf8',
env: {
...process.env,
MOSAIC_LEASE_BROKER_SOCKET: join(tmpdir(), 'missing-mosaic-broker.sock'),
MOSAIC_RUNTIME_GENERATION: '1',
},
},
);
expect(unavailable.status).not.toBe(0);
expect(unavailable.stdout).not.toContain('EXECUTED');
});
test.each(runtimeLaunchEntries)(
'$name registers before launch, denies an unverified mutator, and fails closed without broker',
async (entry) => {
const missingSocket = join(tmpdir(), `missing-${entry.name}-${process.pid}.sock`);
const unavailable = await runRuntimeLaunchEntry(entry, missingSocket);
expect(unavailable.status).not.toBe(0);
expect(`${unavailable.stdout}${unavailable.stderr}`).not.toContain('RUNTIME_PROBE=');
const { socket } = await startBroker();
const launched = await runRuntimeLaunchEntry(entry, socket);
expect(launched.status, launched.stderr).toBe(0);
const match = /RUNTIME_PROBE=(\{[^\n]+\})/.exec(`${launched.stdout}${launched.stderr}`);
expect(match).not.toBeNull();
expect(JSON.parse(match![1]!)).toEqual({
session_id: expect.stringMatching(/^[a-f0-9]{64}$/),
denied: true,
});
},
);
test.each([
{ command: 'mosaic claudex', yolo: false },
{ command: 'mosaic yolo claudex', yolo: true },
])(
'$command registers a broker anchor, installs the all-tools hook, and denies an unverified mutator',
async ({ yolo }) => {
const { socket } = await startBroker();
const root = await mkdtemp(join(tmpdir(), 'mosaic-claudex-gate-'));
temporaryRoots.push(root);
const configDir = join(root, 'isolated-claude');
const binDir = join(root, 'bin');
await mkdir(configDir, { recursive: true });
await mkdir(binDir, { recursive: true });
const fakeClaude = join(binDir, 'claude');
const probe = `#!/usr/bin/env python3
import json
import os
import subprocess
import sys
from pathlib import Path
session_id = os.environ.get("MOSAIC_LEASE_SESSION_ID", "")
settings_path = Path(os.environ["CLAUDE_CONFIG_DIR"]) / "settings.json"
try:
settings = json.loads(settings_path.read_text())
except (OSError, json.JSONDecodeError):
settings = {}
pre_tool = settings.get("hooks", {}).get("PreToolUse", [])
hook_present = any(
item.get("matcher") == ".*" and any("mutator-gate.py" in hook.get("command", "") for hook in item.get("hooks", []))
for item in pre_tool
)
denied = subprocess.run(
["python3", ${JSON.stringify(gatePath)}, "--runtime", "claude"],
input=json.dumps({"tool_name": "Bash"}) + "\\n",
text=True,
capture_output=True,
env=os.environ,
).returncode == 2
is_yolo = "--dangerously-skip-permissions" in sys.argv[1:]
result = {
"session_id": session_id,
"hook_present": hook_present,
"denied": denied,
"is_yolo": is_yolo,
}
print(json.dumps(result))
raise SystemExit(0 if len(session_id) == 64 and hook_present and denied else 1)
`;
await writeFile(fakeClaude, probe, { mode: 0o700 });
await chmod(fakeClaude, 0o700);
let execution: ReturnType<typeof spawnSync> | undefined;
const run = (cmd: string, args: string[], env: NodeJS.ProcessEnv) => {
execution = spawnSync(cmd, args, { encoding: 'utf8', env });
};
const adapter = {
harnessPreflight: () => {},
composePrompt: () => '# composed Claude contract',
// Claudex exposes only the shared register-before-exec boundary.
execLeaseGated: (args: string[], env: NodeJS.ProcessEnv, dangerous: boolean) =>
run(
'python3',
[
launcherPath,
...(dangerous ? ['--dangerous'] : []),
'--runtime',
'claude',
'--',
'claude',
...args,
],
env,
),
} as unknown as ClaudexHarnessAdapter;
await launchClaudex([], yolo, adapter, {
baseEnv: {
...process.env,
PATH: `${binDir}:${process.env.PATH ?? ''}`,
MOSAIC_LEASE_BROKER_SOCKET: socket,
MOSAIC_RUNTIME_GENERATION: '1',
},
proxyGate: () =>
Promise.resolve({
ok: true,
report: {
binaryPresent: true,
binaryPath: '/test/claude-code-proxy',
auth: { state: 'valid' },
live: true,
listenerVerdict: 'ok',
needsReauth: false,
ok: true,
problems: [],
},
problems: [],
}),
resolveConfigDir: () => configDir,
log: () => {},
errorLog: () => {},
fail: ((code: number) => {
throw new Error(`exit ${code}`);
}) as (code: number) => never,
});
expect(execution).toBeDefined();
expect(execution!.status, String(execution!.stderr)).toBe(0);
expect(JSON.parse(String(execution!.stdout))).toEqual({
session_id: expect.stringMatching(/^[a-f0-9]{64}$/),
hook_present: true,
denied: true,
is_yolo: yolo,
});
},
);
test('Claude and Pi runtime adapters consult the broker for every tool class', async () => {
const { socket } = await startBroker();
const sessionId = await register(socket);
expect(runRuntimeGate(socket, sessionId, 'claude', 'Read').status).toBe(0);
expect(runRuntimeGate(socket, sessionId, 'claude', 'Bash').status).toBe(2);
expect(runRuntimeGate(socket, sessionId, 'pi', 'unknown_custom_tool').status).toBe(2);
const pending = await beginVerification(socket, sessionId, 'claude');
await promote(socket, sessionId, pending.promotion_token!);
expect(runRuntimeGate(socket, sessionId, 'claude', 'Bash').status).toBe(0);
const settings = JSON.parse(await readFile(claudeSettingsPath, 'utf8')) as {
hooks: { PreToolUse: Array<{ matcher?: string; hooks: Array<{ command: string }> }> };
};
expect(
settings.hooks.PreToolUse.some(
(entry) =>
entry.matcher === '.*' &&
entry.hooks.some((hook) => hook.command.includes('mutator-gate.py')),
),
).toBe(true);
const piExtension = await readFile(piExtensionPath, 'utf8');
expect(piExtension).toContain("pi.on('tool_call'");
expect(piExtension).toContain('mutator-gate.py');
});
});

View File

@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""Contract tests for the permanent consequential-runtime launch guard."""
from __future__ import annotations
import importlib.util
import io
import json
import runpy
import sys
import tempfile
import unittest
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path
from unittest.mock import patch
MOSAIC_ROOT = Path(__file__).parents[2]
REPO_ROOT = Path(__file__).parents[4]
GUARD_PATH = MOSAIC_ROOT / "framework/tools/lease-broker/check-runtime-launches.py"
SPEC = importlib.util.spec_from_file_location("runtime_launch_guard", GUARD_PATH)
if SPEC is None or SPEC.loader is None:
raise RuntimeError("unable to load runtime launch guard")
GUARD = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(GUARD)
class RuntimeLaunchGuardTest(unittest.TestCase):
def test_detects_direct_shell_and_process_api_launches(self) -> None:
cases = {
"shell-exec.sh": 'exec claude --dangerously-skip-permissions "prompt"\n',
"shell-print.sh": 'claude -p "prompt" | tee report.log\n',
"typescript.ts": "spawn('pi', ['--print', prompt]);\n",
"python.py": "subprocess.run(['claude', '-p', prompt])\n",
"dynamic.ts": "return [runtime, '-p', prompt];\n",
"plain-shell.sh": 'claude "$prompt"\n',
"node-exec.ts": 'exec("claude --print hello");\n',
"command-array.ts": "const launchCommand = ['pi', '--print', prompt];\n",
"python-system.py": 'os.system("claude -p prompt")\n',
"dynamic-shell.sh": 'exec "$runtime" "$prompt"\n',
"dynamic-spawn.ts": 'spawn(runtime, args);\n',
"dynamic-command.sh": 'LAUNCH_COMMAND=("$MOSAIC_AGENT_RUNTIME" --print)\n',
"absolute-shell.sh": 'exec /usr/local/bin/claude -p prompt\n',
"absolute-spawn.ts": "spawn('/opt/bin/pi', args);\n",
"terra-comment.sh": 'exec claude --dangerously-skip-permissions "terra-r3" # launch-runtime.py\n',
"python-comment.py": "subprocess.run(['claude', '-p', prompt]) # launch-runtime.py\n",
"typescript-comment.ts": "spawn('pi', args); // launch-runtime.py\n",
"marker-argument.sh": 'exec claude --dangerously-skip-permissions "launch-runtime.py"\n',
"marker-echo.sh": 'exec claude --dangerously-skip-permissions "prompt"; echo launch-runtime.py\n',
"marker-variable.sh": 'marker=launch-runtime.py; exec claude --dangerously-skip-permissions "prompt"\n',
"heredoc.sh": "cat <<'EOF'\nexec claude --dangerously-skip-permissions prompt # launch-runtime.py\nEOF\n",
"continued.sh": "exec \\\n claude --dangerously-skip-permissions prompt # launch-runtime.py\n",
"chain-semicolon.sh": "true; claude -p prompt\n",
"chain-and.sh": "true && claude -p prompt\n",
"chain-pipe.sh": "printf input | claude -p prompt\n",
"command-substitution.sh": "output=$(claude -p prompt)\n",
"eval.sh": "launcher='claude -p prompt'\neval \"$launcher\"\n",
"variable-exec.sh": "launcher=claude\n\"$launcher\" -p prompt\n",
"env-prefix.sh": "env SAFE=1 claude --help\n",
"command-prefix.sh": "command pi --help\n",
"nohup-prefix.sh": "nohup claude --help &\n",
}
for filename, source in cases.items():
with self.subTest(filename=filename):
violations = GUARD.scan_text(Path(filename), source)
self.assertNotEqual(violations, [], source)
def test_allows_only_explicit_gated_boundaries(self) -> None:
cases = {
"shell-helper.sh": 'exec "$GATED_RUNTIME" claude -- claude -p "prompt"\n',
"mosaic.sh": 'exec mosaic yolo "$runtime" "prompt"\n',
"launch.ts": "execLeaseGatedRuntime('claude', args);\n",
"coord.ts": "return ['mosaic', runtime, '-p', prompt];\n",
}
for filename, source in cases.items():
with self.subTest(filename=filename):
self.assertEqual(GUARD.scan_text(Path(filename), source), [])
def test_detects_prefixed_tracked_runtime_variable_execution(self) -> None:
multiline = {
"exec-quoted": 'exec "$v" -p x',
"exec-unquoted": "exec $v -p x",
"command": 'command "$v" -p x',
"nohup": 'nohup "$v" -p x',
"env": 'env A=1 "$v" -p x',
}
cases = {
**{f"multiline-{name}.sh": f"v=claude\n{command}\n" for name, command in multiline.items()},
**{f"same-line-{name}.sh": f"v=claude; {command}\n" for name, command in multiline.items()},
}
for filename, source in cases.items():
with self.subTest(filename=filename):
self.assertNotEqual(GUARD.scan_text(Path(filename), source), [], source)
def test_accepts_only_validated_multiline_typescript_wrapper_invocation(self) -> None:
source = """execRuntime(
'python3',
[launcher, ...dangerousArgs, '--runtime', runtime, '--', runtime, ...args],
environment,
);
"""
sites = GUARD.classify_text(Path("launch.ts"), source)
self.assertEqual(len(sites), 1)
self.assertEqual(sites[0].classification, "gated")
def test_marker_comments_strings_and_assignments_are_not_gated_sites(self) -> None:
harmless_sources = {
"comment.sh": "# launch-runtime.py --runtime claude --\n",
"echo.sh": "echo 'launch-runtime.py --runtime claude --'\n",
"assignment.sh": "marker='launch-runtime.py --runtime claude --'\n",
"argument.sh": "printf '%s' 'launch-runtime.py --runtime claude --'\n",
}
for filename, source in harmless_sources.items():
with self.subTest(filename=filename):
self.assertEqual(GUARD.classify_text(Path(filename), source), [])
def test_dangerous_primitive_backstops_parser_exotic_alias_indirection(self) -> None:
source = (
"alias hidden_runtime=claude\n"
"hidden_runtime --dangerously-skip-permissions -p x\n"
)
sites = GUARD.scan_text(Path("alias-launch.sh"), source)
self.assertEqual(len(sites), 1)
self.assertEqual(sites[0].classification, "dangerous-primitive")
def test_dangerous_primitive_is_owned_only_by_the_choke_point(self) -> None:
primitive = "--dangerously-skip-permissions"
self.assertNotEqual(GUARD.scan_text(Path("caller.ts"), f"args = ['{primitive}'];\n"), [])
self.assertEqual(
GUARD.scan_text(Path("framework/tools/lease-broker/launch-runtime.py"), f'FLAG = "{primitive}"\n'),
[],
)
def test_repository_has_no_ungated_consequential_runtime_launch(self) -> None:
violations = GUARD.scan_repository(REPO_ROOT)
self.assertEqual(
violations,
[],
"\n".join(GUARD.format_violation(violation) for violation in violations),
)
inventory = GUARD.inventory_repository(REPO_ROOT)
self.assertEqual(len(inventory), 14)
self.assertTrue(all(site.classification == "gated" for site in inventory))
def test_repository_walk_skips_tests_build_outputs_and_reports_unscannable_source(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
production = root / "packages/example/src/launch.sh"
production.parent.mkdir(parents=True)
production.write_text("exec claude -p prompt\n")
(production.parent / "launch.spec.ts").write_text("spawn('pi', [])\n")
dist = root / "packages/example/dist/launch.js"
dist.parent.mkdir(parents=True)
dist.write_text("exec('claude -p prompt')\n")
ignored_suffix = production.parent / "notes.txt"
ignored_suffix.write_text("claude -p prompt\n")
invalid = production.parent / "invalid.py"
invalid.write_bytes(b"\xff\xfe")
violations = GUARD.scan_repository(root)
formatted = [GUARD.format_violation(item) for item in violations]
self.assertEqual(len(violations), 2)
self.assertTrue(any("launch.sh:1: direct" in item for item in formatted))
self.assertTrue(any("invalid.py:0: unscannable" in item for item in formatted))
self.assertFalse(any("spec" in item or "dist" in item or "notes" in item for item in formatted))
inventory = GUARD.inventory_repository(root)
self.assertEqual({item.classification for item in inventory}, {"direct", "unscannable"})
def test_main_emits_machine_inventory_and_fails_on_a_direct_site(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = root / "packages/example/launch.sh"
source.parent.mkdir(parents=True)
source.write_text(
'exec claude --dangerously-skip-permissions "terra-r3" # launch-runtime.py\n'
)
stdout = io.StringIO()
stderr = io.StringIO()
with redirect_stdout(stdout), redirect_stderr(stderr):
result = GUARD.main(["--root", str(root), "--json"])
payload = json.loads(stdout.getvalue())
self.assertEqual(result, 1)
self.assertEqual(payload["gated"], 0)
self.assertEqual(payload["total"], 1)
self.assertIn("ungated consequential runtime", stderr.getvalue())
def test_main_text_mode_reports_a_green_gated_inventory(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
source = root / "packages/example/launch.sh"
source.parent.mkdir(parents=True)
source.write_text("exec mosaic yolo claude prompt\n")
stdout = io.StringIO()
with redirect_stdout(stdout):
result = GUARD.main(["--root", str(root)])
self.assertEqual(result, 0)
self.assertIn("1 gated/1 total", stdout.getvalue())
def test_script_entrypoint_uses_current_directory_default(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "packages").mkdir()
with patch.object(sys, "argv", [str(GUARD_PATH)]), patch("pathlib.Path.cwd", return_value=root):
with redirect_stdout(io.StringIO()), self.assertRaises(SystemExit) as raised:
runpy.run_path(str(GUARD_PATH), run_name="__main__")
self.assertEqual(raised.exception.code, 0)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,386 @@
#!/usr/bin/env python3
"""Branch-focused tests for the lease-gated runtime executables."""
from __future__ import annotations
import importlib.util
import io
import json
import os
import runpy
import socket
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest.mock import patch
TOOLS_DIR = Path(__file__).parents[2] / "framework/tools/lease-broker"
def load_tool(module_name: str, filename: str):
spec = importlib.util.spec_from_file_location(module_name, TOOLS_DIR / filename)
if spec is None or spec.loader is None:
raise RuntimeError(f"unable to load {filename}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
LAUNCHER = load_tool("lease_runtime_launcher", "launch-runtime.py")
GATE = load_tool("lease_mutator_gate", "mutator-gate.py")
class FakeSocket:
def __init__(self, *chunks: bytes):
self.chunks = list(chunks)
self.timeout = None
self.connected = None
self.sent = b""
self.shutdown_how = None
def __enter__(self):
return self
def __exit__(self, *_args):
return False
def settimeout(self, value: float) -> None:
self.timeout = value
def connect(self, value: str) -> None:
self.connected = value
def sendall(self, value: bytes) -> None:
self.sent += value
def shutdown(self, how: int) -> None:
self.shutdown_how = how
def recv(self, _size: int) -> bytes:
return self.chunks.pop(0) if self.chunks else b""
class LaunchRuntimeTest(unittest.TestCase):
def test_success_registers_then_injects_session_before_exec(self) -> None:
calls: dict[str, object] = {}
session_id = "a" * 64
def request(path: Path, payload: dict[str, object]) -> dict[str, object]:
calls["path"] = path
calls["request"] = payload
return {"ok": True, "session_id": session_id}
def execute(command: str, argv: list[str], environment: dict[str, str]) -> None:
calls["execute"] = (command, argv, environment)
result = LAUNCHER.main(
["--runtime", "claude", "--", "claude", "--print", "hello"],
environ={
"MOSAIC_LEASE_BROKER_SOCKET": "/run/test/broker.sock",
"MOSAIC_RUNTIME_GENERATION": "7",
"PRESERVED": "yes",
},
request=request,
execute=execute,
)
self.assertEqual(result, 0)
self.assertEqual(calls["path"], Path("/run/test/broker.sock"))
self.assertEqual(
calls["request"],
{"action": "register_anchor", "runtime_generation": 7},
)
command, argv, environment = calls["execute"]
self.assertEqual(command, "claude")
self.assertEqual(argv, ["claude", "--print", "hello"])
self.assertEqual(environment["MOSAIC_LEASE_SESSION_ID"], session_id)
self.assertEqual(environment["MOSAIC_RUNTIME_GENERATION"], "7")
self.assertEqual(environment["MOSAIC_LEASE_RUNTIME"], "claude")
self.assertEqual(environment["PRESERVED"], "yes")
def test_dangerous_claude_mode_is_owned_and_injected_by_the_wrapper(self) -> None:
executed: list[tuple[str, list[str], dict[str, str]]] = []
result = LAUNCHER.main(
["--runtime", "claude", "--dangerous", "--", "claude", "-p", "hello"],
environ={"MOSAIC_LEASE_BROKER_SOCKET": "/broker"},
request=lambda *_args: {"ok": True, "session_id": "e" * 64},
execute=lambda *args: executed.append(args),
)
self.assertEqual(result, 0)
self.assertEqual(
executed[0][1],
["claude", "--dangerously-skip-permissions", "-p", "hello"],
)
with redirect_stderr(io.StringIO()):
self.assertEqual(
LAUNCHER.main(
["--runtime", "pi", "--dangerous", "--", "pi"],
environ={"MOSAIC_LEASE_BROKER_SOCKET": "/broker"},
request=lambda *_args: {"ok": True, "session_id": "e" * 64},
execute=lambda *_args: self.fail("invalid dangerous runtime executed"),
),
64,
)
def test_command_without_separator_is_forwarded_unchanged(self) -> None:
executed: list[tuple[str, list[str], dict[str, str]]] = []
result = LAUNCHER.main(
["--runtime", "pi", "pi", "--print", "hello"],
environ={"MOSAIC_LEASE_BROKER_SOCKET": "/broker"},
request=lambda *_args: {"ok": True, "session_id": "f" * 64},
execute=lambda *args: executed.append(args),
)
self.assertEqual(result, 0)
self.assertEqual(executed[0][0:2], ("pi", ["pi", "--print", "hello"]))
def test_missing_command_is_usage_error(self) -> None:
with redirect_stderr(io.StringIO()):
self.assertEqual(
LAUNCHER.main(
["--runtime", "pi", "--"],
environ={},
request=lambda *_args: {},
execute=lambda *_args: None,
),
64,
)
def test_registration_validation_and_environment_fail_closed(self) -> None:
good_session = "b" * 64
cases = [
({}, {"ok": True, "session_id": good_session}),
({"MOSAIC_LEASE_BROKER_SOCKET": "/x", "MOSAIC_RUNTIME_GENERATION": "bad"}, {}),
({"MOSAIC_LEASE_BROKER_SOCKET": "/x", "MOSAIC_RUNTIME_GENERATION": "-1"}, {}),
({"MOSAIC_LEASE_BROKER_SOCKET": "/x"}, {"ok": False, "session_id": good_session}),
({"MOSAIC_LEASE_BROKER_SOCKET": "/x"}, {"ok": True, "session_id": 4}),
({"MOSAIC_LEASE_BROKER_SOCKET": "/x"}, {"ok": True, "session_id": "b" * 63}),
({"MOSAIC_LEASE_BROKER_SOCKET": "/x"}, {"ok": True, "session_id": "z" * 64}),
]
for environment, reply in cases:
with self.subTest(environment=environment, reply=reply), redirect_stderr(io.StringIO()):
executed: list[object] = []
result = LAUNCHER.main(
["--runtime", "pi", "--", "pi"],
environ=environment,
request=lambda *_args, value=reply: value,
execute=lambda *args: executed.append(args),
)
self.assertEqual(result, 1)
self.assertEqual(executed, [])
def test_registration_exceptions_fail_closed(self) -> None:
failures = [ValueError("bad"), OSError("down"), json.JSONDecodeError("bad", "x", 0)]
for failure in failures:
with self.subTest(failure=type(failure).__name__), redirect_stderr(io.StringIO()):
def request(*_args, error=failure):
raise error
self.assertEqual(
LAUNCHER.main(
["--runtime", "claude", "--", "claude"],
environ={"MOSAIC_LEASE_BROKER_SOCKET": "/x"},
request=request,
execute=lambda *_args: self.fail("must not execute"),
),
1,
)
def test_exec_failure_is_fail_closed(self) -> None:
with redirect_stderr(io.StringIO()):
self.assertEqual(
LAUNCHER.main(
["--runtime", "pi", "--", "pi"],
environ={"MOSAIC_LEASE_BROKER_SOCKET": "/x"},
request=lambda *_args: {"ok": True, "session_id": "c" * 64},
execute=lambda *_args: (_ for _ in ()).throw(OSError("missing")),
),
1,
)
def test_broker_reply_framing_and_shape_validation(self) -> None:
replies = [
(b'{"ok":true}\n', {"ok": True}),
(b'{"ok":true}', ValueError),
(b'[]\n', ValueError),
(b"x" * (LAUNCHER.MAX_FRAME + 1), ValueError),
]
for wire_reply, expected in replies:
with self.subTest(size=len(wire_reply)):
fake = FakeSocket(wire_reply)
with patch.object(LAUNCHER.socket, "socket", return_value=fake):
if isinstance(expected, type) and issubclass(expected, Exception):
with self.assertRaises(expected):
LAUNCHER.broker_request(Path("/broker"), {"action": "register_anchor"})
else:
self.assertEqual(
LAUNCHER.broker_request(Path("/broker"), {"action": "register_anchor"}),
expected,
)
self.assertEqual(fake.timeout, LAUNCHER.BROKER_TIMEOUT_SECONDS)
self.assertEqual(fake.connected, "/broker")
self.assertEqual(fake.shutdown_how, socket.SHUT_WR)
class ExecutableEntrypointTest(unittest.TestCase):
def test_launcher_entrypoint_returns_usage_without_a_command(self) -> None:
with patch.object(
sys,
"argv",
[str(TOOLS_DIR / "launch-runtime.py"), "--runtime", "claude"],
), redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as raised:
runpy.run_path(str(TOOLS_DIR / "launch-runtime.py"), run_name="__main__")
self.assertEqual(raised.exception.code, 64)
def test_gate_entrypoint_denies_when_identity_environment_is_absent(self) -> None:
class Stdin:
buffer = io.BytesIO(b'{"tool_name":"Bash"}')
with patch.object(
sys,
"argv",
[str(TOOLS_DIR / "mutator-gate.py"), "--runtime", "claude"],
), patch.object(sys, "stdin", Stdin()), patch.dict(
os.environ, {}, clear=True
), redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as raised:
runpy.run_path(str(TOOLS_DIR / "mutator-gate.py"), run_name="__main__")
self.assertEqual(raised.exception.code, 2)
class MutatorGateTest(unittest.TestCase):
@staticmethod
def environment() -> dict[str, str]:
return {
"MOSAIC_LEASE_BROKER_SOCKET": "/run/test/broker.sock",
"MOSAIC_LEASE_SESSION_ID": "d" * 64,
"MOSAIC_RUNTIME_GENERATION": "2",
}
def run_main(self, *, tool: object = "Bash", reply: dict[str, object] | None = None):
calls: list[tuple[Path, dict[str, object]]] = []
def request(path: Path, payload: dict[str, object]) -> dict[str, object]:
calls.append((path, payload))
return reply if reply is not None else {"ok": True, "decision": "allow"}
stderr = io.StringIO()
with redirect_stderr(stderr):
result = GATE.main(
["--runtime", "claude"],
environ=self.environment(),
stream=io.BytesIO(json.dumps({"tool_name": tool}).encode()),
request=request,
)
return result, stderr.getvalue(), calls
def test_allow_and_denial_decisions(self) -> None:
allowed, allowed_stderr, calls = self.run_main()
self.assertEqual(allowed, 0)
self.assertEqual(allowed_stderr, "")
self.assertEqual(calls[0][0], Path("/run/test/broker.sock"))
self.assertEqual(
calls[0][1],
{
"action": "authorize_tool",
"session_id": "d" * 64,
"runtime_generation": 2,
"runtime": "claude",
"tool_name": "Bash",
},
)
denied, denied_stderr, _ = self.run_main(reply={"ok": False, "code": "LEASE_EXPIRED"})
self.assertEqual(denied, 2)
self.assertIn("LEASE_EXPIRED", denied_stderr)
defaulted, defaulted_stderr, _ = self.run_main(reply={"ok": False, "code": 4})
self.assertEqual(defaulted, 2)
self.assertIn("MUTATOR_UNVERIFIED", defaulted_stderr)
def test_input_validation_fails_closed(self) -> None:
payloads = [
b"x" * (GATE.MAX_FRAME + 1),
b"[]",
b"{}",
json.dumps({"tool_name": ""}).encode(),
json.dumps({"tool_name": 4}).encode(),
json.dumps({"tool_name": "x" * 257}).encode(),
b"not-json",
]
for payload in payloads:
with self.subTest(size=len(payload)), redirect_stderr(io.StringIO()):
self.assertEqual(
GATE.main(
["--runtime", "pi"],
environ=self.environment(),
stream=io.BytesIO(payload),
request=lambda *_args: self.fail("invalid input reached broker"),
),
2,
)
def test_environment_generation_and_request_failures_deny(self) -> None:
environments = [
{},
{**self.environment(), "MOSAIC_RUNTIME_GENERATION": "bad"},
{**self.environment(), "MOSAIC_RUNTIME_GENERATION": "-1"},
]
for environment in environments:
with self.subTest(environment=environment), redirect_stderr(io.StringIO()):
self.assertEqual(
GATE.main(
["--runtime", "claude"],
environ=environment,
stream=io.BytesIO(b'{"tool_name":"Read"}'),
request=lambda *_args: {},
),
2,
)
failures = [ValueError("bad"), OSError("down"), json.JSONDecodeError("bad", "x", 0)]
for failure in failures:
with self.subTest(failure=type(failure).__name__), redirect_stderr(io.StringIO()):
def request(*_args, error=failure):
raise error
self.assertEqual(
GATE.main(
["--runtime", "claude"],
environ=self.environment(),
stream=io.BytesIO(b'{"tool_name":"Read"}'),
request=request,
),
2,
)
def test_broker_request_framing_payload_and_shape_validation(self) -> None:
with self.assertRaises(ValueError):
GATE.broker_request(Path("/broker"), {"session_id": "x" * GATE.MAX_FRAME})
replies = [
(b'{"ok":true,"decision":"allow"}\n', {"ok": True, "decision": "allow"}),
(b'{"ok":true}', ValueError),
(b'[]\n', ValueError),
(b"x" * (GATE.MAX_FRAME + 1), ValueError),
]
for wire_reply, expected in replies:
with self.subTest(size=len(wire_reply)):
fake = FakeSocket(wire_reply)
with patch.object(GATE.socket, "socket", return_value=fake):
if isinstance(expected, type) and issubclass(expected, Exception):
with self.assertRaises(expected):
GATE.broker_request(Path("/broker"), {"action": "authorize_tool"})
else:
self.assertEqual(
GATE.broker_request(Path("/broker"), {"action": "authorize_tool"}),
expected,
)
self.assertEqual(fake.timeout, GATE.BROKER_TIMEOUT_SECONDS)
self.assertEqual(fake.connected, "/broker")
self.assertEqual(fake.shutdown_how, socket.SHUT_WR)
if __name__ == "__main__":
unittest.main()