#!/usr/bin/env node /** * PreToolUse Bash gate (router-gate v4 §5.1). * Default-deny: команда не в whitelist → block. Hard-blacklist + sub-shell * sweep + chain-mutating + git (shared classifyGitCommand) + path-deny + watcher. * ParseError → fail-CLOSE. */ import { fileURLToPath } from 'url'; import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; import { homedir } from 'os'; import { tokenizeBash, isMutatingSegment } from './bash-tokenizer.mjs'; import { defaultPathNormalize, DEFAULT_PROTECTED_PATTERNS, pathDenyOverlay, extractPathArgs, matchBashHardBlacklist, BASH_HARD_BLACKLIST, classifyGitCommand, loadApprovedGitOps, } from './shell-content-rules.mjs'; import { readStdin, parseEventJson, exitDecision } from './enforce-hook-helpers.mjs'; // Owner-authorized 2026-06-18: строго read-only удалённый доступ к боевому порталу // (ssh liderra-prod '' / gh GET) — разрешитель в remote-read-allow.mjs. import { classifyRemoteReadCommand } from './remote-read-allow.mjs'; // M7 Task 1.0.5 (P-1): content-blacklist + матчер + stderr-redirect-страж переехали // в единый дом shell-content-rules.mjs (один источник правды для content-floor М5 и этого // гейта; порт-дрейф невозможен). Ре-экспорт для обратной совместимости — тесты импортируют // matchBashHardBlacklist отсюда, и тело гейта (ниже) зовёт его локально. export { matchBashHardBlacklist, BASH_HARD_BLACKLIST }; // ── whitelist ── export const READING_CMDS = new Set(['ls', 'pwd', 'wc', 'head', 'tail', 'file', 'stat', 'grep', 'egrep', 'fgrep', 'cat', 'less', 'more']); const SAFE_EXACT = [ /^npx\s+vitest\s+(?:run|--version)\b/, /^npm\s+(?:test|run\s+test|run\s+lint(?::[\w-]+)?)\b/, /^php\s+artisan\s+(?:list|route:list|migrate:status)\b/, /^composer\s+(?:show|outdated)\b/, // node — НЕ в SAFE_EXACT (Пакет 4.2, Δ4): обрабатывается отдельной ветвью // classifyWhitelist через nodeScriptAllowed — сужение с «любой node-файл» до // allowlist (плоский tools/.mjs + vitest-runner + --version). Анти-казуальная // мера (§7): остаток «контроллер commit'ит разрешённый скрипт» честно принят (Δ4), // file-watcher (scriptWatcherCheck) дополнительно блокирует session-edited до commit. // Laravel dev workflow (2026-05-30) — exclude tinker (REPL = arbitrary PHP exec risk). // Hard-blacklist (composer install/update/require/remove) remains the first check, unaffected. // `migrate(?=\s|$)` lookahead prevents `migrate:install` / `migrate:` from matching bare `migrate`. // Машина 5 Пакет 2.4 (C4): migrate:fresh/refresh/reset УБРАНЫ из whitelist — это floor-набор // (необратимый дроп БД, classify-destructive.mjs). Теперь они → default-deny router-gate'ом // даже при незарегистрированном enforce-floor (защита-в-глубину SPOF). Остаются bare migrate // (миграции вперёд) + migrate:rollback (обратимо). /^php\s+artisan\s+(?:test|migrate:rollback|migrate(?=\s|$)|db:seed|cache:clear|config:clear|view:clear|route:clear|optimize:clear)\b/, /^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$/, ]; // ── node-whitelist (Пакет 4.2, Δ4) ── // Сужение: `node