Files
blackbird/aibox/tests/_poc_audit_regression.py
T
qing 177a9ac9a6 fix(aibox): close audit findings H1-H3/M1-M2 (command exec protection chain)
- H1: CommandLineTool.execute_streaming now purely delegates to injected
  executor; removes 150-line duplicate Popen loop that bypassed 1MB cap,
  cancel() and timeout protections
- H2: thread the blocking readline into a daemon reader thread; main loop
  polls queue with 0.1s ticks so timeout/1MB checks fire even for silent
  commands (verified: 3s timeout on 15s sleep, was 15s+ before)
- H3: stdin=DEVNULL on both Popen sites; stdin-waiting commands now hit
  EOF instead of deadlocking the tool thread forever
- M1: heredoc delimiter uniquification in write_file guidance (content
  line equal to AIBOX_EOF no longer truncates into shell injection)
- M2: 3600s total deadline on registry streaming forward loop

Verification: PoC regression 5/5 PASS (timeout/1MB/cancel/delegation),
full unit suite 264 passed. Audit report updated with Batch E results.
2026-08-17 17:28:41 +08:00

99 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""审计修复 PoC 回归验证(H1/H2/H3 + 1MB + cancel)。
验证 2026-08-17 审计五项修复的实际运行效果:
PoC-1 (H2): 无输出命令 + timeout=3s → 应 ~3s 返回超时(修复前阻塞 15s+)
PoC-2 (H3): 等待 stdin 的命令 + timeout=3s → 应超时返回而非永久挂起
PoC-3 : 输出 >1MB → 应截断且总输出 ≈1MB
PoC-4 : cancel() 应在宽限时间内终止长命令
PoC-5 (H1) : CommandLineTool(工具层,非 executor)跑 >1MB 输出 →
截断生效即证明已走 executor 委托路径
"""
import queue
import sys
import threading
import time
import types
from pathlib import Path
AIBOX_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(AIBOX_DIR))
sys.modules.setdefault("websockets", types.ModuleType("websockets"))
from subsystems.executors import SubprocessCommandExecutor
from tools.command_tool import CommandLineTool
results = []
def check(name, ok, detail):
results.append((name, ok, detail))
print(f"[{'PASS' if ok else 'FAIL'}] {name}: {detail}")
# ---------- PoC-1 (H2): 无输出命令超时 ----------
ex = SubprocessCommandExecutor()
t0 = time.time()
r = ex.execute("powershell -NoProfile -Command Start-Sleep 15", timeout=3)
elapsed = time.time() - t0
check("PoC-1 无输出命令超时(H2)", elapsed < 6 and "超时" in r,
f"{elapsed:.1f}s 返回(修复前阻塞15s+),结果含超时: {'超时' in r}")
# ---------- PoC-2 (H3): 等待 stdin 的命令 ----------
# stdin=DEVNULL 下命令立即收到 EOF:要么快速退出,要么按 timeout 返回;
# 判定标准 = 不永久挂起(修复前 readline 无 EOF 永久阻塞)。
t0 = time.time()
r = ex.execute("powershell -NoProfile -Command Read-Host", timeout=3)
elapsed = time.time() - t0
check("PoC-2 stdin等待命令不挂起(H3)", elapsed < 6,
f"{elapsed:.1f}s 返回(修复前永久挂起)")
# ---------- PoC-3: 1MB 输出截断 ----------
t0 = time.time()
r = ex.execute(
'powershell -NoProfile -Command "$s=\'x\'*2048; 1..1024 | ForEach-Object { $s }"',
timeout=60)
elapsed = time.time() - t0
size_mb = len(r.encode("utf-8", errors="replace")) / 1024 / 1024
check("PoC-3 1MB输出截断", "截断" in r and size_mb < 1.15,
f"{elapsed:.1f}s,结果 {size_mb:.2f}MB,含截断标记: {'截断' in r}")
# ---------- PoC-4: cancel() 中断 ----------
out_q = queue.Queue()
done = threading.Event()
def _run():
ex.execute_streaming(
"powershell -NoProfile -Command Start-Sleep 60",
out_q, timeout=120)
done.set()
t0 = time.time()
th = threading.Thread(target=_run, daemon=True)
th.start()
time.sleep(2)
ex.cancel()
done.wait(timeout=10)
elapsed = time.time() - t0
check("PoC-4 cancel中断", done.is_set() and elapsed < 8,
f"cancel 后 {elapsed:.1f}s 结束(修复前杀不到进程需等满120s)")
# ---------- PoC-5 (H1): 工具层委托 executor(截断保护贯通) ----------
tool = CommandLineTool(executor=ex)
t0 = time.time()
r = tool.execute(
command='powershell -NoProfile -Command "$s=\'y\'*2048; 1..1024 | ForEach-Object { $s }"',
timeout=60)
elapsed = time.time() - t0
size_mb = len(r.encode("utf-8", errors="replace")) / 1024 / 1024
check("PoC-5 工具层1MB贯通(H1)", "截断" in r and size_mb < 1.15,
f"{elapsed:.1f}s,工具层结果 {size_mb:.2f}MB(修复前无限累积)")
# ---------- 汇总 ----------
fails = [n for n, ok, _ in results if not ok]
print("\n==== 汇总: %d/%d PASS ====" % (len(results) - len(fails), len(results)))
sys.exit(1 if fails else 0)