from __future__ import annotations
import json
import pathlib
import time
from conftest import RUN_DIR, http_get, http_post
def test_r009_rate_limit_triggers_429(rl_strict_server):
port = rl_strict_server["port"]
codes = []
for _ in range(10):
st, _ = http_post(port, "/api/1/embed", {"text": "限流触发探测"})
codes.append(st)
assert 429 in codes, f"10 次请求未触发 429: {codes}"
def test_r009_whitelist_bypass(rl_pass_server):
port = rl_pass_server["port"]
codes = []
for _ in range(10):
st, _ = http_post(port, "/api/1/embed", {"text": "白名单直通探测"})
codes.append(st)
assert all(c == 200 for c in codes), f"白名单 IP 被限流: {codes}"
def test_r009_rate_limit_audit_event(rl_strict_server):
codes = []
for _ in range(3):
st, _ = http_post(rl_strict_server["port"], "/api/1/embed", {"text": "审计前置"})
codes.append(st)
f = RUN_DIR / "rl_strict" / "logs" / "audit.log"
assert f.exists(), f"审计日志不存在: {f}"
text = ""
for _ in range(20):
text = f.read_text(errors="replace")
if "RateLimit" in text or "rate_limit" in text.lower():
break
time.sleep(0.5)
assert "RateLimit" in text or "rate_limit" in text.lower(), \
f"审计日志无限流事件,样本: {text[:300]}"
def test_r010_path_traversal_rejected(base_server):
port = base_server["port"]
cases = [
("/api/1/embed/file", {"path": "../../Cargo.toml"}),
("/api/1/embed/file", {"path": "/etc/passwd"}),
("/api/1/model/switch", {"model_name": "x", "model_path": "../../etc"}),
("/api/1/model/switch", {"model_name": "../../etc/passwd"}),
]
for path, payload in cases:
st, body = http_post(port, path, payload)
assert st >= 400, \
f"{path} {payload.get('path', payload.get('model_name'))} 未被拒绝({st})"
def test_r010_error_response_sanitized(base_server):
port = base_server["port"]
probes = []
st, body = http_post(port, "/api/1/model/switch", {"model_name": "BAAI/nope-xyz"})
probes.append(str(body))
st2, body2 = http_post(port, "/api/1/embed/file", {"path": "../../nonexistent"})
probes.append(str(body2))
st3, body3 = http_post(port, "/api/1/embed", raw_body="{bad json")
probes.append(str(body3))
joined = " ".join(probes).lower()
assert "/home/kirky" not in joined, f"错误响应泄露家目录路径: {joined[:200]}"
assert "src/api" not in joined and "src/engine" not in joined, f"泄露源码路径: {joined[:200]}"
def test_r011_audit_log_jsonl(base_server):
f = RUN_DIR / "base" / "logs" / "audit.log"
if not f.exists():
import pytest
pytest.skip("能力记录:base 场景未产生审计日志文件(无安全事件触发),已由 rl_strict 场景覆盖 R-auth-011")
lines = [ln for ln in f.read_text(errors="replace").splitlines() if ln.strip()]
if not lines:
import pytest
pytest.skip("能力记录:base 场景无安全事件,审计日志为空属预期(rl_strict 场景覆盖事件内容)")
parsed = 0
for ln in lines[:50]:
try:
obj = json.loads(ln)
assert "event" in obj or "type" in obj or "action" in obj or len(obj) >= 1
parsed += 1
except json.JSONDecodeError:
continue
assert parsed > 0, f"审计日志前 50 行无一为 JSON: {lines[0][:200]}"