From 13db4aaf7c7d2e1e3968ec6812adaca239a9b2d6 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Fri, 17 Jul 2026 00:23:31 +0800 Subject: [PATCH] Add VedAstro secret and renderer audit tools --- scripts/configure_vedastro_secret.py | 64 +++++++++++++++ scripts/report_renderer_isolation_poc.py | 87 +++++++++++++++++++++ tests/test_configure_vedastro_secret.py | 18 +++++ tests/test_report_renderer_isolation_poc.py | 9 +++ 4 files changed, 178 insertions(+) create mode 100644 scripts/configure_vedastro_secret.py create mode 100644 scripts/report_renderer_isolation_poc.py create mode 100644 tests/test_configure_vedastro_secret.py create mode 100644 tests/test_report_renderer_isolation_poc.py diff --git a/scripts/configure_vedastro_secret.py b/scripts/configure_vedastro_secret.py new file mode 100644 index 00000000..9d510216 --- /dev/null +++ b/scripts/configure_vedastro_secret.py @@ -0,0 +1,64 @@ +"""Configure VedAstro local secret without echoing it. + +Run manually from a trusted terminal. This script writes only to ignored local +env files; it must never be used to commit or print secrets. +""" + +from __future__ import annotations + +import getpass +from pathlib import Path + + +DEFAULT_ENV = Path(".env.local") +DEFAULT_ENDPOINT = "https://api.vedastro.org/api" + + +def update_env_text(text: str, updates: dict[str, str]) -> str: + lines = text.splitlines() + seen: set[str] = set() + output: list[str] = [] + for line in lines: + stripped = line.strip() + if not stripped or stripped.startswith("#") or "=" not in line: + output.append(line) + continue + key, _value = line.split("=", 1) + key = key.strip() + if key in updates: + output.append(f"{key}={updates[key]}") + seen.add(key) + else: + output.append(line) + for key, value in updates.items(): + if key not in seen: + output.append(f"{key}={value}") + return "\n".join(output).rstrip() + "\n" + + +def write_env(path: Path, updates: dict[str, str]) -> None: + existing = path.read_text(encoding="utf-8") if path.exists() else "" + path.write_text(update_env_text(existing, updates), encoding="utf-8") + + +def main() -> int: + key = getpass.getpass("VedAstro API key (hidden): ").strip() + if not key: + print("No key entered; nothing changed.") + return 1 + endpoint = input(f"VedAstro endpoint [{DEFAULT_ENDPOINT}]: ").strip() or DEFAULT_ENDPOINT + write_env( + DEFAULT_ENV, + { + "VEDASTRO_API_KEY": key, + "VEDASTRO_API_ENDPOINT": endpoint, + "VEDASTRO_ENABLE_NETWORK": "1", + "VEDASTRO_TIMEOUT_SECONDS": "20", + }, + ) + print(f"Updated {DEFAULT_ENV}; secret value was not printed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/report_renderer_isolation_poc.py b/scripts/report_renderer_isolation_poc.py new file mode 100644 index 00000000..bbc10cfe --- /dev/null +++ b/scripts/report_renderer_isolation_poc.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Run an isolated Chromium proof that report rendering cannot fetch external resources.""" +from __future__ import annotations + +import json +import argparse +import tempfile +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Any + +try: + from report_builder import is_allowed_report_resource_url +except ImportError: + from scripts.report_builder import is_allowed_report_resource_url + + +class _ProbeHandler(BaseHTTPRequestHandler): + requests = 0 + + def do_GET(self) -> None: # noqa: N802 + type(self).requests += 1 + self.send_response(200) + self.end_headers() + + def log_message(self, _format: str, *_args: Any) -> None: + return + + +def run_poc() -> dict[str, Any]: + try: + from playwright.sync_api import sync_playwright + except ImportError: + return {"scope": "report_renderer_isolation_poc", "status": "blocked", "reason": "playwright_python_missing"} + + server = ThreadingHTTPServer(("127.0.0.1", 0), _ProbeHandler) + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + secret = root / "secret.txt" + secret.write_text("must-not-load", encoding="utf-8") + html = root / "report.html" + remote_url = f"http://127.0.0.1:{server.server_port}/probe" + html.write_text( + f'

report

', + encoding="utf-8", + ) + report_url = html.as_uri() + blocked: list[str] = [] + try: + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True) + context = browser.new_context(java_script_enabled=False) + page = context.new_page() + page.route( + "**/*", + lambda route: route.continue_() + if is_allowed_report_resource_url(route.request.url, report_url=report_url) + else (blocked.append(route.request.url), route.abort())[1], + ) + page.goto(report_url, wait_until="networkidle") + context.close() + browser.close() + except Exception as exc: # Browser binary/startup is an environment boundary. + return {"scope": "report_renderer_isolation_poc", "status": "blocked", "reason": f"chromium_unavailable:{type(exc).__name__}"} + return { + "scope": "report_renderer_isolation_poc", + "status": "pass" if _ProbeHandler.requests == 0 and len(blocked) >= 2 else "fail", + "http_probe_requests": _ProbeHandler.requests, + "blocked_resource_count": len(blocked), + "blocked_schemes": sorted({url.split(":", 1)[0] for url in blocked}), + } + finally: + server.shutdown() + server.server_close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--strict", action="store_true", help="Return nonzero unless the isolation probe passes.") + args = parser.parse_args() + result = run_poc() + print(json.dumps(result, ensure_ascii=False, sort_keys=True)) + raise SystemExit(0 if not args.strict or result["status"] == "pass" else 1) diff --git a/tests/test_configure_vedastro_secret.py b/tests/test_configure_vedastro_secret.py new file mode 100644 index 00000000..c251dee7 --- /dev/null +++ b/tests/test_configure_vedastro_secret.py @@ -0,0 +1,18 @@ +from scripts.configure_vedastro_secret import update_env_text + + +def test_update_env_text_adds_and_replaces_vedastro_settings(): + text = "VEDASTRO_API_ENDPOINT=https://old.example/api\nOTHER=value\n" + updated = update_env_text( + text, + { + "VEDASTRO_API_KEY": "sample-secret", + "VEDASTRO_API_ENDPOINT": "https://api.vedastro.org/api", + "VEDASTRO_ENABLE_NETWORK": "1", + }, + ) + assert "VEDASTRO_API_ENDPOINT=https://api.vedastro.org/api" in updated + assert "VEDASTRO_API_KEY=sample-secret" in updated + assert "VEDASTRO_ENABLE_NETWORK=1" in updated + assert "OTHER=value" in updated + assert "https://old.example/api" not in updated diff --git a/tests/test_report_renderer_isolation_poc.py b/tests/test_report_renderer_isolation_poc.py new file mode 100644 index 00000000..108a6edd --- /dev/null +++ b/tests/test_report_renderer_isolation_poc.py @@ -0,0 +1,9 @@ +from scripts.report_renderer_isolation_poc import run_poc + + +def test_report_renderer_isolation_poc_never_claims_pass_without_browser() -> None: + result = run_poc() + assert result["status"] in {"pass", "fail", "blocked"} + if result["status"] == "pass": + assert result["http_probe_requests"] == 0 + assert result["blocked_resource_count"] >= 2