52d500db5d
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
144 lines
7.3 KiB
JavaScript
144 lines
7.3 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
||
import { classifyRemoteReadCommand } from './remote-read-allow.mjs';
|
||
|
||
const allow = (cmd) => classifyRemoteReadCommand(cmd).ok;
|
||
|
||
describe('remote-read-allow — ssh liderra-prod read-only', () => {
|
||
it('пропускает одиночный grep (проверочная команда из задачи)', () => {
|
||
expect(allow(`ssh liderra-prod 'grep -n "function register" -A 60 /var/www/liderra/app/app/Http/Controllers/Api/AuthController.php'`)).toBe(true);
|
||
});
|
||
|
||
it('пропускает cat/ls/head/tail/stat/wc/file', () => {
|
||
expect(allow(`ssh liderra-prod 'cat /var/www/liderra/.env.example'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'ls -la /var/www/liderra'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'head -100 /var/log/nginx/access.log'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'tail -f /var/log/liderra/laravel.log'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'stat /var/www/liderra/artisan'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'wc -l /var/log/nginx/error.log'`)).toBe(true);
|
||
});
|
||
|
||
it('пропускает sed в print-форме', () => {
|
||
expect(allow(`ssh liderra-prod 'sed -n 5,20p /etc/nginx/nginx.conf'`)).toBe(true);
|
||
});
|
||
|
||
it('блокирует sed in-place (-i) и sed с write-командой w', () => {
|
||
expect(allow(`ssh liderra-prod 'sed -i s/a/b/ /etc/nginx/nginx.conf'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'sed --in-place s/a/b/ file'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'sed s/a/b/w out file'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'sed -f script.sed file'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует мутации (touch/mv/cp/chmod/tee)', () => {
|
||
expect(allow(`ssh liderra-prod 'touch /tmp/x'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'mv a b'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cp a b'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'chmod 777 /etc/passwd'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'tee /etc/x'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'rm -rf /var/www'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует чужой хост и ssh-опции (-L/-o/...)', () => {
|
||
expect(allow(`ssh otherhost 'cat /etc/passwd'`)).toBe(false);
|
||
expect(allow(`ssh -L 8080:localhost:80 liderra-prod 'cat x'`)).toBe(false);
|
||
expect(allow(`ssh -o ProxyCommand=evil liderra-prod 'cat x'`)).toBe(false);
|
||
expect(allow(`ssh root@otherhost 'cat x'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует пайпы/цепочки/редиректы/sub-shell в удалённой команде', () => {
|
||
expect(allow(`ssh liderra-prod 'cat a | tee b'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cat a && rm b'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cat a; rm b'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cat $(whoami)'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cat a > b'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует цепочку/редирект на внешнем уровне', () => {
|
||
expect(allow(`ssh liderra-prod 'cat a' && rm b`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cat a' > out`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'cat a' | tee b`)).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('remote-read-allow — psql SELECT-only', () => {
|
||
it('пропускает SELECT/WITH/SHOW/EXPLAIN через -c', () => {
|
||
expect(allow(`ssh liderra-prod 'psql -c "SELECT count(*) FROM deals"'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'psql -c "WITH t AS (SELECT 1) SELECT * FROM t"'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'psql -c "SHOW server_version"'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'psql -c "EXPLAIN SELECT 1"'`)).toBe(true);
|
||
expect(allow(`ssh liderra-prod 'psql -c "SELECT 1;"'`)).toBe(true);
|
||
});
|
||
|
||
it('блокирует write-SQL (INSERT/UPDATE/DELETE/DROP/...)', () => {
|
||
expect(allow(`ssh liderra-prod 'psql -c "UPDATE deals SET x=1"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "INSERT INTO deals VALUES (1)"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "DELETE FROM deals"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "DROP TABLE deals"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "TRUNCATE deals"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "GRANT ALL ON deals TO x"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "COPY deals TO PROGRAM \\'sh\\'"'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует множественные стейтменты и SELECT…INTO', () => {
|
||
expect(allow(`ssh liderra-prod 'psql -c "SELECT 1; DROP TABLE x"'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -c "SELECT * INTO newt FROM deals"'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует -f (файл-скрипт) и -o (запись результата в файл)', () => {
|
||
expect(allow(`ssh liderra-prod 'psql -f /tmp/evil.sql'`)).toBe(false);
|
||
expect(allow(`ssh liderra-prod 'psql -o /tmp/out -c "SELECT 1"'`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует psql без явного -c (интерактив)', () => {
|
||
expect(allow(`ssh liderra-prod 'psql liderra'`)).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('remote-read-allow — gh GET only', () => {
|
||
it('пропускает run list|view, workflow list|view', () => {
|
||
expect(allow(`gh run list`)).toBe(true);
|
||
expect(allow(`gh run view 12345`)).toBe(true);
|
||
expect(allow(`gh run view 12345 --log`)).toBe(true);
|
||
expect(allow(`gh workflow list`)).toBe(true);
|
||
expect(allow(`gh workflow view deploy.yml`)).toBe(true);
|
||
});
|
||
|
||
it('пропускает gh api GET (по умолчанию и явно)', () => {
|
||
expect(allow(`gh api repos/liderra/portal/actions/runs`)).toBe(true);
|
||
expect(allow(`gh api -X GET repos/liderra/portal/commits`)).toBe(true);
|
||
});
|
||
|
||
it('блокирует gh мутации (rerun/cancel/workflow run/pr merge)', () => {
|
||
expect(allow(`gh run rerun 12345`)).toBe(false);
|
||
expect(allow(`gh run cancel 12345`)).toBe(false);
|
||
expect(allow(`gh run delete 12345`)).toBe(false);
|
||
expect(allow(`gh workflow run deploy.yml`)).toBe(false);
|
||
expect(allow(`gh workflow enable deploy.yml`)).toBe(false);
|
||
expect(allow(`gh pr merge 1`)).toBe(false);
|
||
expect(allow(`gh release create v1`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует gh api с не-GET методом и телом запроса', () => {
|
||
expect(allow(`gh api -X POST repos/liderra/portal/dispatches`)).toBe(false);
|
||
expect(allow(`gh api --method DELETE repos/x/y`)).toBe(false);
|
||
expect(allow(`gh api -XPOST repos/x/y`)).toBe(false);
|
||
expect(allow(`gh api repos/x/y -f key=val`)).toBe(false);
|
||
expect(allow(`gh api graphql -f query=mutation`)).toBe(false);
|
||
expect(allow(`gh api repos/x/y --input body.json`)).toBe(false);
|
||
});
|
||
|
||
it('блокирует gh api без пути', () => {
|
||
expect(allow(`gh api`)).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('remote-read-allow — общие отказы', () => {
|
||
it('не пропускает локальные команды и мусор', () => {
|
||
expect(allow(`cat /etc/passwd`)).toBe(false);
|
||
expect(allow(`ls`)).toBe(false);
|
||
expect(allow(`scp a liderra-prod:b`)).toBe(false);
|
||
expect(allow(``)).toBe(false);
|
||
expect(allow(null)).toBe(false);
|
||
expect(allow(`ssh liderra-prod`)).toBe(false);
|
||
});
|
||
});
|