fix(#829): guard prefixed runtime variable launches
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
ms-lead-reviewer
2026-07-18 00:39:13 -05:00
parent 91a4a98370
commit 04cc065c2b
5 changed files with 108 additions and 23 deletions

View File

@@ -24,6 +24,7 @@ interface BrokerPaths {
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');
@@ -282,6 +283,39 @@ describe('whole mutator-class lease gate', () => {
});
});
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);

View File

@@ -114,6 +114,15 @@ class RuntimeLaunchGuardTest(unittest.TestCase):
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"), [])