"""Regression checks for the review and the 8788 black-box report.""" from __future__ import annotations import json import os import socket import subprocess import sys import tempfile import threading import unittest import urllib.error import urllib.parse import urllib.request from datetime import datetime, timezone from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from unittest import mock if "asrservice.config" not in sys.modules: _boot = tempfile.mkdtemp(prefix="asr-harden-") os.environ["ASR_DATA_DIR"] = str(Path(_boot) / "data") os.environ["ASR_STATE_DIR"] = str(Path(_boot) / "state") os.environ["ASR_VAULT"] = str(Path(_boot) / "vault") os.environ["ASR_NOTIFY"] = "0" os.environ["ASR_GIT_SYNC"] = "0" os.environ["ASR_AGENT_TOKEN"] = "test-agent-token" from asrpipe import locking, refine, review, webauth # noqa: E402 from asrpipe.config import Config # noqa: E402 from asrpipe import providers # noqa: E402 from asrservice import config as svc_cfg # noqa: E402 from asrservice import db as svc_db # noqa: E402 from asrservice import web as svc_web # noqa: E402 from asrservice import worker # noqa: E402 class _NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): raise urllib.error.HTTPError(req.full_url, code, msg, headers, fp) def _opener(follow=True): if follow: return urllib.request.build_opener(urllib.request.HTTPCookieProcessor()) return urllib.request.build_opener(_NoRedirect) def _start_server(): server = ThreadingHTTPServer(("127.0.0.1", 0), svc_web.Handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() return server, server.server_address[1] def _raw(port: int, payload: bytes) -> bytes: sock = socket.create_connection(("127.0.0.1", port), timeout=5) try: sock.sendall(payload) sock.shutdown(socket.SHUT_WR) chunks = [] while True: part = sock.recv(65536) if not part: break chunks.append(part) return b"".join(chunks) finally: sock.close() class HardeningTests(unittest.TestCase): def setUp(self): os.environ.pop("ASR_WEB_ALLOW_ANON", None) os.environ["ASR_WEB_PASSWORD"] = "test-web-password" os.environ["ASR_NOTIFY"] = "0" os.environ["ASR_GIT_SYNC"] = "0" svc_cfg.ensure_dirs() svc_db.connect().close() self._hindsight = Config.use_hindsight.__get__(svc_web.CFG, type(svc_web.CFG)) svc_web.CFG.use_hindsight = False def tearDown(self): svc_web.CFG.use_hindsight = self._hindsight def test_closed_without_password_and_csrf_and_bad_length(self): os.environ["ASR_WEB_PASSWORD"] = "" server, port = _start_server() try: opener = _opener(False) with self.assertRaises(urllib.error.HTTPError) as caught: opener.open(f"http://127.0.0.1:{port}/review/", timeout=5) self.assertEqual(caught.exception.code, 303) self.assertIn("/login", caught.exception.headers.get("Location", "")) page = urllib.request.urlopen(f"http://127.0.0.1:{port}/login", timeout=5).read().decode() self.assertIn("ASR_WEB_PASSWORD", page) raw = _raw(port, b"POST /login HTTP/1.0\r\nContent-Length: abc\r\n\r\n") self.assertIn(b" 400 ", raw) self.assertNotIn(b"Empty", raw) finally: server.shutdown() server.server_close() os.environ["ASR_WEB_PASSWORD"] = "test-web-password" def test_pages_hide_secrets_escape_titles_and_reject_bad_edits(self): svc_db.save_settings({ "litellm_api_key": "sk-SECRET-abcd-wxyz", "diarize_url": "http://192.168.50.100:10099/asr", }) jid = "abc123abc123abc123abc123" title = "X');window.__xss=1;//" svc_db.create_job(jid, "录音.wav", "sample.wav", title=title, size=100, source="web") audio = svc_cfg.PROCESSED / "sample.wav" audio.write_bytes(b"0123456789" * 10) draft = svc_cfg.DRAFTS / f"{jid}.md" draft.write_text("原文一行\n", encoding="utf-8") result = svc_cfg.RESULTS / "2026-纪要.md" result.write_text("# 纪要\n", encoding="utf-8") svc_db.update_job(jid, "complete", "完成", draft=str(draft), result=str(result)) q1 = review.enqueue("rec-a", "第一题?", {"kind": "draft_question", "candidates": ["甲"]}) q2 = review.enqueue("rec-b", "第二题?", {"kind": "draft_question", "candidates": ["乙"]}) server, port = _start_server() try: opener = _opener(True) opener.open(urllib.request.Request( f"http://127.0.0.1:{port}/login", data=urllib.parse.urlencode({"password": "test-web-password"}).encode(), method="POST", ), timeout=5) home = opener.open(f"http://127.0.0.1:{port}/", timeout=5).read().decode() self.assertNotIn('onclick="delOne(', home) self.assertIn("data-del", home) settings = opener.open(f"http://127.0.0.1:{port}/settings", timeout=5).read().decode() self.assertNotIn("192.168.50.100", settings) self.assertNotIn("sk-SECRET", settings) self.assertIn("已配置", settings) review_html = opener.open(f"http://127.0.0.1:{port}/review/", timeout=5).read().decode() forms = review_html.split('