SuperCharge Claude Code v1.0.0 - Complete Customization Package
Features: - 30+ Custom Skills (cognitive, development, UI/UX, autonomous agents) - RalphLoop autonomous agent integration - Multi-AI consultation (Qwen) - Agent management system with sync capabilities - Custom hooks for session management - MCP servers integration - Plugin marketplace setup - Comprehensive installation script Components: - Skills: always-use-superpowers, ralph, brainstorming, ui-ux-pro-max, etc. - Agents: 100+ agents across engineering, marketing, product, etc. - Hooks: session-start-superpowers, qwen-consult, ralph-auto-trigger - Commands: /brainstorm, /write-plan, /execute-plan - MCP Servers: zai-mcp-server, web-search-prime, web-reader, zread - Binaries: ralphloop wrapper Installation: ./supercharge.sh
This commit is contained in:
750
plugins/claude-code-safety-net/tests/edge-cases.test.ts
Normal file
750
plugins/claude-code-safety-net/tests/edge-cases.test.ts
Normal file
@@ -0,0 +1,750 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { assertAllowed, assertBlocked, runGuard, withEnv } from './helpers.ts';
|
||||
|
||||
describe('edge cases', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = mkdtempSync(join(tmpdir(), 'safety-net-test-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('input validation', () => {
|
||||
test('empty command allows', () => {
|
||||
assertAllowed('');
|
||||
});
|
||||
|
||||
test('whitespace command allows', () => {
|
||||
assertAllowed(' ');
|
||||
});
|
||||
|
||||
test('case insensitive matching blocks', () => {
|
||||
assertBlocked('GIT CHECKOUT -- file', 'git checkout --');
|
||||
});
|
||||
});
|
||||
|
||||
describe('strict mode', () => {
|
||||
test('strict mode parse error denies', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
const result = runGuard("git reset --hard 'unterminated");
|
||||
expect(result).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode unparseable safe command denies', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
const result = runGuard("echo 'unterminated");
|
||||
expect(result).not.toBeNull();
|
||||
expect(result).toContain('could not be safely analyzed');
|
||||
});
|
||||
});
|
||||
|
||||
test('non-strict mode unparseable safe command allows', () => {
|
||||
assertAllowed("echo 'unterminated");
|
||||
});
|
||||
|
||||
test('strict mode bash -c without arg allows', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed('bash -c');
|
||||
});
|
||||
});
|
||||
|
||||
test('non strict bash -c without arg allows', () => {
|
||||
assertAllowed('bash -c');
|
||||
});
|
||||
|
||||
test('bash double dash does not treat dash c as wrapper allowed', () => {
|
||||
assertAllowed("bash -- -c 'echo ok'");
|
||||
});
|
||||
|
||||
test('strict mode bash without dash c allowed', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed('bash -l echo ok');
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode bash only allowed', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed('bash');
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode bash double dash does not treat dash c as wrapper allowed', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed("bash -- -c 'echo ok'");
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode python without one liner allowed', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed('python script.py');
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode python double dash does not treat dash c as one liner allowed', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed("python -- -c 'print(1)'");
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode python one liner allowed', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed('python -c "print(\'ok\')"');
|
||||
});
|
||||
});
|
||||
|
||||
test('strict mode bash lc without arg allows', () => {
|
||||
withEnv({ SAFETY_NET_STRICT: '1' }, () => {
|
||||
assertAllowed('bash -lc');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('shell wrappers', () => {
|
||||
test('sh -lc wrapper blocked', () => {
|
||||
assertBlocked("sh -lc 'git reset --hard'", 'git reset --hard');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unparseable commands with heuristics', () => {
|
||||
test('non strict unparseable rm -rf still blocked by heuristic', () => {
|
||||
assertBlocked("rm -rf /some/path 'unterminated", 'rm -rf');
|
||||
});
|
||||
|
||||
test('non strict unparseable git push -f still blocked by heuristic', () => {
|
||||
assertBlocked("git push -f origin main 'unterminated", 'push --force');
|
||||
});
|
||||
|
||||
test('non strict unparseable find delete blocked by heuristic', () => {
|
||||
assertBlocked("find . -delete 'unterminated", 'find -delete');
|
||||
});
|
||||
|
||||
test('non strict unparseable non dangerous allows', () => {
|
||||
assertAllowed("echo 'unterminated");
|
||||
});
|
||||
|
||||
test('non strict unparseable git restore help allows', () => {
|
||||
assertAllowed("git restore --help 'unterminated");
|
||||
});
|
||||
|
||||
test('non strict unparseable git checkout dash dash still blocked by heuristic', () => {
|
||||
assertBlocked("git checkout -- file.txt 'unterminated", 'git checkout --');
|
||||
});
|
||||
|
||||
test('non strict unparseable git restore blocked by heuristic', () => {
|
||||
assertBlocked("git restore file.txt 'unterminated", 'git restore');
|
||||
});
|
||||
|
||||
test('non strict unparseable git restore worktree still blocked by heuristic', () => {
|
||||
assertBlocked("git restore --worktree file.txt 'unterminated", 'git restore');
|
||||
});
|
||||
|
||||
test('non strict unparseable git stash clear still blocked by heuristic', () => {
|
||||
assertBlocked("git stash clear 'unterminated", 'git stash drop/clear');
|
||||
});
|
||||
|
||||
test('non strict unparseable git branch D still blocked by heuristic', () => {
|
||||
assertBlocked("git branch -D feature 'unterminated", 'git branch -D');
|
||||
});
|
||||
|
||||
test('non strict unparseable git reset hard still blocked by heuristic', () => {
|
||||
assertBlocked("git reset --hard 'unterminated", 'git reset --hard');
|
||||
});
|
||||
|
||||
test('non strict unparseable git reset merge still blocked by heuristic', () => {
|
||||
assertBlocked("git reset --merge 'unterminated", 'git reset --merge');
|
||||
});
|
||||
|
||||
test('non strict unparseable git clean f still blocked by heuristic', () => {
|
||||
assertBlocked("git clean -f 'unterminated", 'git clean -f');
|
||||
});
|
||||
|
||||
test('non strict unparseable git stash drop still blocked by heuristic', () => {
|
||||
assertBlocked("git stash drop stash@{0} 'unterminated", 'git stash drop');
|
||||
});
|
||||
|
||||
test('non strict unparseable git push force still blocked by heuristic', () => {
|
||||
assertBlocked("git push --force origin main 'unterminated", 'push --force');
|
||||
});
|
||||
|
||||
test('unparseable echo mentions find delete allowed', () => {
|
||||
assertAllowed('echo "find . -delete');
|
||||
});
|
||||
|
||||
test('unparseable rg mentions find delete allowed', () => {
|
||||
assertAllowed('rg "find . -delete');
|
||||
});
|
||||
});
|
||||
|
||||
describe('command substitution', () => {
|
||||
test('command substitution git reset hard blocked', () => {
|
||||
assertBlocked('echo $(git reset --hard )', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('command substitution find delete blocked', () => {
|
||||
assertBlocked('echo $(find . -delete )', 'find -delete');
|
||||
});
|
||||
|
||||
test('command substitution rm f allowed', () => {
|
||||
assertAllowed('echo $(rm -f /tmp/a )');
|
||||
});
|
||||
|
||||
test('command substitution git status allowed', () => {
|
||||
assertAllowed('echo $(git status )');
|
||||
});
|
||||
|
||||
test('command substitution find without delete allowed', () => {
|
||||
assertAllowed('echo $(find . -name foo )');
|
||||
});
|
||||
});
|
||||
|
||||
describe('xargs', () => {
|
||||
test('xargs rm -rf blocked', () => {
|
||||
assertBlocked('echo / | xargs rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs delimiter option still blocks child rm', () => {
|
||||
assertBlocked("echo / | xargs --delimiter '\\n' rm -rf", 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs dash i does not consume child cmd still blocks', () => {
|
||||
assertBlocked('echo / | xargs -i rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs attached n option still blocks child rm', () => {
|
||||
assertBlocked('echo / | xargs -n1 rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs attached P option still blocks child rm', () => {
|
||||
assertBlocked('echo / | xargs -P2 rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs long opt equals still blocks child rm', () => {
|
||||
assertBlocked('echo / | xargs --arg-file=/tmp/paths rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs only options without child command allowed', () => {
|
||||
assertAllowed('echo ok | xargs -n1');
|
||||
});
|
||||
|
||||
test('xargs attached i option still blocks child rm', () => {
|
||||
assertBlocked('echo / | xargs -i{} rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs bash c script analyzed blocks', () => {
|
||||
assertBlocked("echo ok | xargs bash -c 'git reset --hard'", 'xargs');
|
||||
});
|
||||
|
||||
test('xargs child wrappers only allowed', () => {
|
||||
assertAllowed('echo ok | xargs sudo --');
|
||||
});
|
||||
|
||||
test('xargs busybox rm non destructive allowed', () => {
|
||||
assertAllowed('echo ok | xargs busybox rm -f /tmp/test');
|
||||
});
|
||||
|
||||
test('xargs find without delete allowed', () => {
|
||||
assertAllowed('echo ok | xargs find . -name foo');
|
||||
});
|
||||
|
||||
test('xargs print0 rm -rf blocked', () => {
|
||||
assertBlocked('find . -print0 | xargs -0 rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs arg file option still blocks child rm', () => {
|
||||
assertBlocked('echo ok | xargs -a /tmp/paths rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs echo allowed', () => {
|
||||
assertAllowed('echo ok | xargs echo');
|
||||
});
|
||||
|
||||
test('xargs busybox rm -rf blocked', () => {
|
||||
assertBlocked('echo / | xargs busybox rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs busybox find delete blocked', () => {
|
||||
assertBlocked('echo ok | xargs busybox find . -delete', 'find -delete');
|
||||
});
|
||||
|
||||
test('xargs without child command allowed', () => {
|
||||
assertAllowed('echo ok | xargs');
|
||||
});
|
||||
|
||||
test('xargs find delete blocked', () => {
|
||||
assertBlocked('echo ok | xargs find . -delete', 'find -delete');
|
||||
});
|
||||
|
||||
test('xargs git reset hard blocked', () => {
|
||||
assertBlocked('echo ok | xargs git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('xargs replace I rm rf blocked', () => {
|
||||
assertBlocked('echo / | xargs -I{} rm -rf {}', 'xargs', tempDir);
|
||||
});
|
||||
|
||||
test('xargs replace long option enables placeholder analysis', () => {
|
||||
assertBlocked("echo / | xargs --replace bash -c 'rm -rf {}'", 'xargs');
|
||||
});
|
||||
|
||||
test('xargs replace long option with custom token enables placeholder analysis', () => {
|
||||
assertBlocked("echo / | xargs --replace=FOO bash -c 'rm -rf FOO'", 'xargs');
|
||||
});
|
||||
|
||||
test('xargs replace long option empty value defaults to braces', () => {
|
||||
assertBlocked("echo / | xargs --replace= bash -c 'rm -rf {}'", 'xargs');
|
||||
});
|
||||
|
||||
test('xargs replacement token parsing ignores unknown options', () => {
|
||||
assertBlocked("echo / | xargs --replace -t bash -c 'rm -rf {}'", 'xargs');
|
||||
});
|
||||
|
||||
test('xargs replace I bash c script is input denied safe input', () => {
|
||||
assertBlocked('echo ok | xargs -I{} bash -c {}', 'arbitrary');
|
||||
});
|
||||
|
||||
test('xargs bash c without arg denied safe input', () => {
|
||||
assertBlocked('echo ok | xargs bash -c', 'arbitrary');
|
||||
});
|
||||
|
||||
test('xargs replace I bash c placeholder rm rf blocked', () => {
|
||||
assertBlocked("echo / | xargs -I{} bash -c 'rm -rf {}'", 'xargs', tempDir);
|
||||
});
|
||||
|
||||
test('xargs replace custom token bash c placeholder rm rf blocked', () => {
|
||||
assertBlocked("echo / | xargs -I% bash -c 'rm -rf %'", 'xargs', tempDir);
|
||||
});
|
||||
|
||||
test('xargs replace I bash c script is input denied', () => {
|
||||
assertBlocked("echo 'rm -rf /' | xargs -I{} bash -c {}", 'xargs');
|
||||
});
|
||||
|
||||
test('xargs J consumes value still blocks child rm', () => {
|
||||
assertBlocked('echo / | xargs -J {} rm -rf {}', 'rm -rf');
|
||||
});
|
||||
|
||||
test('xargs rm double dash prevents dash rf as option allowed', () => {
|
||||
assertAllowed('echo ok | xargs rm -- -rf', tempDir);
|
||||
});
|
||||
|
||||
test('xargs bash c dynamic denied', () => {
|
||||
assertBlocked("echo 'rm -rf /' | xargs bash -c", 'xargs');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parallel', () => {
|
||||
test('parallel bash c dynamic denied', () => {
|
||||
assertBlocked("parallel bash -c ::: 'rm -rf /'", 'parallel');
|
||||
});
|
||||
|
||||
test('parallel stdin mode blocks rm -rf', () => {
|
||||
assertBlocked('echo / | parallel rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('parallel busybox stdin mode blocks rm -rf', () => {
|
||||
assertBlocked('echo / | parallel busybox rm -rf', 'rm -rf');
|
||||
});
|
||||
|
||||
test('parallel busybox find delete blocked', () => {
|
||||
assertBlocked('parallel busybox find . -delete ::: ok', 'find -delete');
|
||||
});
|
||||
|
||||
test('parallel git reset hard blocked', () => {
|
||||
assertBlocked('parallel git reset --hard ::: ok', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('parallel find delete blocked', () => {
|
||||
assertBlocked('parallel find . -delete ::: ok', 'find -delete');
|
||||
});
|
||||
|
||||
test('parallel find without delete allowed', () => {
|
||||
assertAllowed('parallel find . -name foo ::: ok');
|
||||
});
|
||||
|
||||
test('parallel busybox find without delete allowed', () => {
|
||||
assertAllowed('parallel busybox find . -name foo ::: ok');
|
||||
});
|
||||
|
||||
test('parallel stdin without template allowed', () => {
|
||||
assertAllowed('echo ok | parallel');
|
||||
});
|
||||
|
||||
test('parallel marker without template allowed', () => {
|
||||
assertAllowed('parallel :::');
|
||||
});
|
||||
|
||||
test('parallel bash c script is input denied', () => {
|
||||
assertBlocked("echo 'rm -rf /' | parallel bash -c {}", 'parallel');
|
||||
});
|
||||
|
||||
test('parallel bash c script is input denied safe input', () => {
|
||||
assertBlocked('echo ok | parallel bash -c {}', 'arbitrary');
|
||||
});
|
||||
|
||||
test('parallel results option blocks rm rf', () => {
|
||||
assertBlocked('parallel --results out rm -rf {} ::: /', 'rm -rf', tempDir);
|
||||
});
|
||||
|
||||
test('parallel jobs attached option blocks', () => {
|
||||
assertBlocked('parallel -j2 rm -rf {} ::: /', 'root or home', tempDir);
|
||||
});
|
||||
|
||||
test('parallel jobs long equals option blocks', () => {
|
||||
assertBlocked('parallel --jobs=2 rm -rf {} ::: /', 'root or home', tempDir);
|
||||
});
|
||||
|
||||
test('parallel unknown long option is ignored for template parsing', () => {
|
||||
assertBlocked('parallel --eta rm -rf {} ::: /', 'root or home', tempDir);
|
||||
});
|
||||
|
||||
test('parallel unknown short option ignored for template parsing', () => {
|
||||
assertBlocked('parallel -q rm -rf {} ::: /', 'root or home', tempDir);
|
||||
});
|
||||
|
||||
test('parallel bash c stdin mode blocks rm rf placeholder', () => {
|
||||
assertBlocked("echo / | parallel bash -c 'rm -rf {}'", 'rm -rf');
|
||||
});
|
||||
|
||||
test('parallel commands mode blocks rm rf', () => {
|
||||
assertBlocked("parallel ::: 'rm -rf /'", 'rm -rf');
|
||||
});
|
||||
|
||||
test('parallel commands mode allows when all commands safe', () => {
|
||||
assertAllowed("parallel ::: 'echo ok' 'true'");
|
||||
});
|
||||
|
||||
test('parallel rm rf args after marker without placeholder blocked', () => {
|
||||
assertBlocked('parallel rm -rf ::: /', 'root or home');
|
||||
});
|
||||
|
||||
test('parallel rm rf with replacement args analyzed', () => {
|
||||
assertBlocked('parallel rm -rf {} ::: /', 'rm -rf', tempDir);
|
||||
});
|
||||
|
||||
test('parallel bash c rm rf with replacement args analyzed', () => {
|
||||
assertBlocked("parallel bash -c 'rm -rf {}' ::: /", 'rm -rf', tempDir);
|
||||
});
|
||||
|
||||
test('parallel busybox rm rf with replacement args analyzed', () => {
|
||||
assertBlocked('parallel busybox rm -rf {} ::: /', 'rm -rf', tempDir);
|
||||
});
|
||||
|
||||
test('parallel rm rf with safe replacement allowed', () => {
|
||||
assertAllowed('parallel rm -rf {} ::: build', tempDir);
|
||||
});
|
||||
|
||||
test('parallel bash c rm rf with safe replacement allowed', () => {
|
||||
assertAllowed("parallel bash -c 'rm -rf {}' ::: build", tempDir);
|
||||
});
|
||||
|
||||
test('parallel busybox rm rf with safe replacement allowed', () => {
|
||||
assertAllowed('parallel busybox rm -rf {} ::: build', tempDir);
|
||||
});
|
||||
|
||||
test('parallel bash c without placeholder analyzes script', () => {
|
||||
assertBlocked("parallel bash -c 'git reset --hard' ::: ok", 'git reset --hard');
|
||||
});
|
||||
|
||||
test('parallel bash c without placeholder allows safe script', () => {
|
||||
assertAllowed("parallel bash -c 'echo ok' ::: ok");
|
||||
});
|
||||
|
||||
test('parallel busybox rm rf args after marker without placeholder blocked', () => {
|
||||
assertBlocked('parallel busybox rm -rf ::: /', 'root or home');
|
||||
});
|
||||
});
|
||||
|
||||
describe('busybox', () => {
|
||||
test('busybox find delete blocked', () => {
|
||||
assertBlocked('busybox find . -delete', 'find -delete');
|
||||
});
|
||||
|
||||
test('busybox find without delete allowed', () => {
|
||||
assertAllowed('busybox find . -name foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('operators', () => {
|
||||
test('or operator split blocked', () => {
|
||||
assertBlocked('git status || git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('semicolon split blocked', () => {
|
||||
assertBlocked('git status; git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('newline split blocked', () => {
|
||||
assertBlocked('git status\ngit reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('redirection ampersand does not split blocked', () => {
|
||||
assertBlocked('echo ok 2>&1 && git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('redirection ampersand greater does not split blocked', () => {
|
||||
assertBlocked('echo ok &>out && git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('pipe stderr and stdout split blocked', () => {
|
||||
assertBlocked('echo ok |& git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('shell split with leading operator still blocks', () => {
|
||||
assertBlocked('&& git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('shell split with leading pipe still blocks', () => {
|
||||
assertBlocked('| git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('shell split with leading pipe stderr operator still blocks', () => {
|
||||
assertBlocked('|& git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('shell split with leading background operator still blocks', () => {
|
||||
assertBlocked('& git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('shell split with trailing separator ignored allowed', () => {
|
||||
assertAllowed('git status;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('env wrappers', () => {
|
||||
test('sudo double dash wrapper bypass blocked', () => {
|
||||
assertBlocked('sudo -- git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env unset equals wrapper bypass blocked', () => {
|
||||
assertBlocked('env --unset=PATH git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env unset attached wrapper bypass blocked', () => {
|
||||
assertBlocked('env -uPATH git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env C attached wrapper bypass blocked', () => {
|
||||
assertBlocked('env -C/tmp git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env C separate wrapper bypass blocked', () => {
|
||||
assertBlocked('env -C /tmp git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env P wrapper bypass blocked', () => {
|
||||
assertBlocked('env -P /usr/bin git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env S wrapper bypass blocked', () => {
|
||||
assertBlocked("env -S 'PATH=/usr/bin' git reset --hard", 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env dash breaks option scan still blocks', () => {
|
||||
assertBlocked('env - git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('command combined short opts wrapper bypass blocked', () => {
|
||||
assertBlocked('command -pv -- git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('command V wrapper bypass blocked', () => {
|
||||
assertBlocked('command -V git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('command combined short opts with V wrapper bypass blocked', () => {
|
||||
assertBlocked('command -pvV -- git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env assignments stripped blocked', () => {
|
||||
assertBlocked('FOO=1 BAR=2 git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('invalid env assignment key does not strip still blocks', () => {
|
||||
assertBlocked('1A=2 git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('invalid env assignment chars does not strip still blocks', () => {
|
||||
assertBlocked('A-B=2 git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('empty env assignment key does not strip still blocks', () => {
|
||||
assertBlocked('=2 git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('only env assignments allowed', () => {
|
||||
assertAllowed('FOO=1');
|
||||
});
|
||||
|
||||
test('sudo option wrapper bypass blocked', () => {
|
||||
assertBlocked('sudo -u root -- git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env P attached wrapper bypass blocked', () => {
|
||||
assertBlocked('env -P/usr/bin git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env S attached wrapper bypass blocked', () => {
|
||||
assertBlocked('env -SPATH=/usr/bin git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env unknown option wrapper bypass blocked', () => {
|
||||
assertBlocked('env -i git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('command unknown short opts not stripped still blocks', () => {
|
||||
assertBlocked('command -px git reset --hard', 'git reset --hard');
|
||||
});
|
||||
});
|
||||
|
||||
describe('interpreter one-liners', () => {
|
||||
test('node -e dangerous blocked', () => {
|
||||
assertBlocked('node -e "rm -rf /"', 'rm -rf');
|
||||
});
|
||||
|
||||
test('node -e safe allowed', () => {
|
||||
assertAllowed('node -e "console.log(\\"ok\\")"');
|
||||
});
|
||||
|
||||
test('ruby -e dangerous blocked', () => {
|
||||
assertBlocked('ruby -e "rm -rf /"', 'rm -rf');
|
||||
});
|
||||
|
||||
test('ruby -e safe allowed', () => {
|
||||
assertAllowed('ruby -e "puts \'ok\'"');
|
||||
});
|
||||
|
||||
test('perl -e dangerous blocked', () => {
|
||||
assertBlocked('perl -e "rm -rf /"', 'rm -rf');
|
||||
});
|
||||
|
||||
test('perl -e safe allowed', () => {
|
||||
assertAllowed('perl -e "print \'ok\'"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('paranoid mode', () => {
|
||||
test('paranoid mode python one liner denies', () => {
|
||||
withEnv({ SAFETY_NET_PARANOID_INTERPRETERS: '1' }, () => {
|
||||
assertBlocked('python -c "print(\'ok\')"', 'Paranoid mode');
|
||||
});
|
||||
});
|
||||
|
||||
test('global paranoid mode python one liner denies', () => {
|
||||
withEnv({ SAFETY_NET_PARANOID: '1' }, () => {
|
||||
assertBlocked('python -c "print(\'ok\')"', 'Paranoid mode');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('recursion', () => {
|
||||
test('shell dash c recursion limit reached blocks command', () => {
|
||||
let cmd = 'rm -rf /some/path';
|
||||
for (let i = 0; i < 11; i++) {
|
||||
cmd = `bash -c ${JSON.stringify(cmd)}`;
|
||||
}
|
||||
assertBlocked(cmd, 'recursion');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cwd handling', () => {
|
||||
test('cwd empty string treated as unknown', () => {
|
||||
assertBlocked('git reset --hard', 'git reset --hard', '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('display-only commands bypass fallback scanning', () => {
|
||||
test('echo with git reset --hard allowed', () => {
|
||||
assertAllowed('echo git reset --hard');
|
||||
});
|
||||
|
||||
test('echo with rm -rf allowed', () => {
|
||||
assertAllowed('echo rm -rf /');
|
||||
});
|
||||
|
||||
test('printf with git reset --hard allowed', () => {
|
||||
assertAllowed("printf 'git reset --hard'");
|
||||
});
|
||||
|
||||
test('printf with rm -rf allowed', () => {
|
||||
assertAllowed("printf 'rm -rf /'");
|
||||
});
|
||||
|
||||
test('cat with find -delete allowed', () => {
|
||||
assertAllowed('cat find -delete');
|
||||
});
|
||||
|
||||
test('grep with git checkout -- file allowed', () => {
|
||||
assertAllowed("grep 'git checkout -- file' log.txt");
|
||||
});
|
||||
|
||||
test('rg with rm -rf allowed', () => {
|
||||
assertAllowed("rg 'rm -rf' .");
|
||||
});
|
||||
|
||||
test('sed with git reset --hard allowed', () => {
|
||||
assertAllowed("sed 's/git reset --hard/safe/' file.txt");
|
||||
});
|
||||
|
||||
test('awk with rm -rf allowed', () => {
|
||||
assertAllowed("awk '/rm -rf/ {print}' log.txt");
|
||||
});
|
||||
|
||||
test('head with git clean -f allowed', () => {
|
||||
assertAllowed('head git clean -f');
|
||||
});
|
||||
|
||||
test('tail with git stash drop allowed', () => {
|
||||
assertAllowed('tail git stash drop');
|
||||
});
|
||||
|
||||
test('wc with rm -rf allowed', () => {
|
||||
assertAllowed('wc rm -rf /');
|
||||
});
|
||||
|
||||
test('less with git push --force allowed', () => {
|
||||
assertAllowed('less git push --force');
|
||||
});
|
||||
});
|
||||
|
||||
describe('recursion depth boundary', () => {
|
||||
test('shell dash c recursion at exactly MAX_RECURSION_DEPTH (10) blocks', () => {
|
||||
let cmd = 'rm -rf /some/path';
|
||||
for (let i = 0; i < 10; i++) {
|
||||
cmd = `bash -c ${JSON.stringify(cmd)}`;
|
||||
}
|
||||
assertBlocked(cmd, 'recursion');
|
||||
});
|
||||
|
||||
test('shell dash c recursion at depth 9 still blocks with rm reason', () => {
|
||||
let cmd = 'rm -rf /some/path';
|
||||
for (let i = 0; i < 9; i++) {
|
||||
cmd = `bash -c ${JSON.stringify(cmd)}`;
|
||||
}
|
||||
assertBlocked(cmd, 'rm -rf');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parallel rm placeholder expansion with mixed args', () => {
|
||||
test('parallel rm -rf with one safe and one dangerous arg blocked', () => {
|
||||
assertBlocked('parallel rm -rf {} ::: build /', 'rm -rf', tempDir);
|
||||
});
|
||||
|
||||
test('parallel rm -rf with multiple dangerous args blocked', () => {
|
||||
assertBlocked('parallel rm -rf {} ::: / ~', 'rm -rf', tempDir);
|
||||
});
|
||||
|
||||
test('parallel rm -rf with all safe args allowed', () => {
|
||||
assertAllowed('parallel rm -rf {} ::: build dist node_modules', tempDir);
|
||||
});
|
||||
|
||||
test('parallel bash -c rm -rf with mixed args blocked', () => {
|
||||
assertBlocked("parallel bash -c 'rm -rf {}' ::: build /", 'rm -rf', tempDir);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user