fix(router-gate): narrow cd app whitelist (TDD, tools 1978 GREEN)
Add /^cd\s+app$/ to SAFE_EXACT so already-whitelisted commands (pest, php artisan test) run from app/. Scope limited to the literal `app` dir: cd into any other path (incl. protected .claude/runtime, memory/, transcripts) stays default-deny, so the cwd-shift read-bypass is contained. Mutations remain caught at the hard-blacklist + chain-mutating rule, and each chain segment after `cd app &&` must still be independently whitelisted. Owner-authorized, narrow scope = literal `app` only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,14 @@ const SAFE_EXACT = [
|
||||
/^composer\s+(?:test|pint|stan|insights|rector)\b/,
|
||||
/^(?:\.\/)?vendor\/bin\/pest\b/,
|
||||
/^pest\b/,
|
||||
// Narrow `cd app` (2026-05-31, owner-authorized) — enter the Laravel project dir
|
||||
// so already-whitelisted commands (pest, php artisan test) run from app/.
|
||||
// Scope deliberately limited to the literal `app` dir: `cd` into any other path
|
||||
// (incl. protected .claude/runtime, memory/, transcripts) stays default-deny, so
|
||||
// the cwd-shift read-bypass is contained. Mutations remain caught at the
|
||||
// hard-blacklist + chain-mutating rule (both run before the whitelist), and each
|
||||
// chain segment after `cd app &&` must still be independently whitelisted.
|
||||
/^cd\s+app$/,
|
||||
];
|
||||
|
||||
export function classifyWhitelist(segments) {
|
||||
|
||||
@@ -227,3 +227,46 @@ describe('SAFE_EXACT — Laravel dev workflow (whitelist expansion 2026-05-30)',
|
||||
expect(classifyBashCommand('composer outdated', {}).result).toBe('allow');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SAFE_EXACT — narrow `cd app` whitelist (2026-05-31, owner-authorized)', () => {
|
||||
// Allowed: enter the Laravel project dir, alone or chained with whitelisted cmds
|
||||
it.each([
|
||||
'cd app',
|
||||
'cd app && pest',
|
||||
'cd app && php artisan test',
|
||||
'cd app && composer test',
|
||||
])('allows %s', (cmd) => {
|
||||
expect(classifyBashCommand(cmd, {}).result).toBe('allow');
|
||||
});
|
||||
|
||||
// Scope: cd into any other dir stays default-deny (cwd-shift read-bypass contained)
|
||||
it.each([
|
||||
'cd ~/.claude/runtime',
|
||||
'cd ../memory',
|
||||
'cd app/storage',
|
||||
'cd /tmp',
|
||||
'cd ..',
|
||||
])('still blocks cd into non-app dir: %s', (cmd) => {
|
||||
expect(classifyBashCommand(cmd, {}).result).toBe('block');
|
||||
});
|
||||
|
||||
// cwd-shift read-exfil attempt via narrow cd app stays blocked (protected path by name)
|
||||
it('still blocks reading a protected file from app/ via literal path', () => {
|
||||
expect(classifyBashCommand('cd app && cat ../.env', {}).result).toBe('block');
|
||||
expect(classifyBashCommand('cd app && cat ~/.claude/runtime/state.json', {}).result).toBe('block');
|
||||
});
|
||||
|
||||
// Mutations after cd app remain caught (hard-blacklist + chain-mutating rule)
|
||||
it.each([
|
||||
'cd app && rm foo',
|
||||
'cd app && mkdir x',
|
||||
'cd app && git commit -m x',
|
||||
])('still blocks mutating chain: %s', (cmd) => {
|
||||
expect(classifyBashCommand(cmd, {}).result).toBe('block');
|
||||
});
|
||||
|
||||
// Second segment must still be independently whitelisted
|
||||
it('still blocks cd app chained with a non-whitelisted command', () => {
|
||||
expect(classifyBashCommand('cd app && frobnicate', {}).result).toBe('block');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user