feat(scripts): כפתור "הרץ" מ-UI לסקריפטי read-only (קטגוריה B #4)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s

הרצת-סקריפט-מ-UI ב-/scripts, רק לסקריפטי קריאה-בלבד/אודיט, דרך גשר-המארח
הקיים (court-fetch) — שיכפול דפוס /adapter-migration.

אבטחה:
- allowlist בצד-המארח (services/script_runner.py, מקור-אמת יחיד): name→argv
  קבוע ובטוח. 5 סקריפטים מאומתים: leak_guard, check_undefined_names,
  storage_leak_tripwire, audit_training_corpus, audit_corpus_integrity --no-notify.
- הגשר (court_fetch_service/server.py): endpoint POST /run-script, Bearer-auth,
  ולידציית-allowlist, create_subprocess_exec (ללא shell), timeout 600s,
  audit-log; מתעלם מארגומנטים מהבקשה (argv מה-allowlist בלבד).
- קונטיינר (web/app.py): POX /api/scripts/{name}/run proxy ל-גשר + pre-check
  allowlist; הרחבת /api/scripts/catalog ב-runnable_scripts.
- UI: כפתור "הרץ" (ירוק) רק לשורות-runnable + דיאלוג-פלט (exit-code+stdout);
  שאר השורות "מקור" בלבד. confirm לפני הרצה.

מאושר דרך שער-העיצוב (מוקאפ 16-scripts עודכן עם כפתור "הרץ").
G12: leak_guard עובר (אין סמלי-פלטפורמה בשכבת-האינטליגנציה).

Deploy דו-שלבי: גשר-המארח דורש git pull בעותק-המארח + pm2 restart
legal-court-fetch-service; הקונטיינר נפרס דרך Coolify כרגיל.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 04:30:43 +00:00
parent b4cb0a69c3
commit 221975fe23
5 changed files with 315 additions and 25 deletions

View File

@@ -0,0 +1,50 @@
"""Allowlist of read-only scripts runnable from the /scripts page (#4).
Single source of truth shared by BOTH:
- the host bridge (``court_fetch_service.server`` — the ENFORCER that actually
launches the process), and
- the container API (``web/app.py`` — display-only: tells the UI which rows get
a "הרץ" button).
Each entry maps a script's basename to the EXACT, fixed argument list it runs
with. **Read-only / audit scripts only**, with a safe fixed argv and **no
user-supplied arguments** — never ``--apply``/``--force``. The system is
single-user/internal, so this allowlist is a footgun-guard, not an auth boundary;
enforcement lives on the trusted host side.
Adding an entry is a security decision: verify the script is read-only (no DB
writes, no destructive side effects) with the given argv before listing it.
stdlib-only on purpose, so the lightweight host bridge can import it cheaply.
"""
from __future__ import annotations
_REPO = "/home/chaim/legal-ai"
_PYTHON = f"{_REPO}/mcp-server/.venv/bin/python"
# basename → argv tail (script path relative to the repo root, then fixed flags).
# Verified read-only 2026-06-17. `audit_corpus_integrity` runs with --no-notify
# so it stays report-only (no notification side-effect) when run from the dashboard.
SCRIPT_RUN_ALLOWLIST: dict[str, list[str]] = {
"leak_guard.py": ["scripts/leak_guard.py"],
"check_undefined_names.py": ["scripts/check_undefined_names.py"],
"storage_leak_tripwire.py": ["scripts/storage_leak_tripwire.py"],
"audit_training_corpus.py": ["scripts/audit_training_corpus.py"],
"audit_corpus_integrity.py": ["scripts/audit_corpus_integrity.py", "--no-notify"],
}
def runnable_names() -> list[str]:
"""Sorted basenames the UI may show a "הרץ" button for (display-only)."""
return sorted(SCRIPT_RUN_ALLOWLIST)
def build_argv(name: str) -> list[str] | None:
"""Full argv (python + absolute script path + fixed flags) for an allowlisted
script, or ``None`` when *name* is not allowlisted. Arguments are taken ONLY
from the allowlist — anything the caller passes is ignored."""
tail = SCRIPT_RUN_ALLOWLIST.get(name)
if tail is None:
return None
return [_PYTHON, f"{_REPO}/{tail[0]}", *tail[1:]]