Merge pull request 'feat(scripts): כפתור "הרץ" מ-UI לסקריפטי read-only (קטגוריה B #4)' (#283) from worktree-scripts-run-ui into main
This commit was merged in pull request #283.
This commit is contained in:
50
mcp-server/src/legal_mcp/services/script_runner.py
Normal file
50
mcp-server/src/legal_mcp/services/script_runner.py
Normal 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:]]
|
||||
Reference in New Issue
Block a user