chore: 忽略离线部署包,提交安全加固、数据库初始化与文档
- .gitignore: 忽略 docker/offline 离线部署包(镜像/运行时等大文件) - 安全加固: 新增 compute/api/security.py 及各端安全测试,补充 docs/security-hardening.md - 数据库: 新增完整初始化 SQL 与 docs/database-config.md - 数据转换与评测: 修复类型检查、增强校验并补充测试 - Docker 配置与环境变量更新 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
57
backend/tests/test_data_convert_security.py
Normal file
57
backend/tests/test_data_convert_security.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""data_convert 模块安全回归测试:输出文件名路径穿越与鉴权。
|
||||
|
||||
- ``output_filename`` 必须通过白名单校验,阻断 ``../``、``/``、``\\`` 及控制字符,
|
||||
否则转换结果可被写出到存储根目录之外(任意文件读写/删除)。
|
||||
- 所有 data_convert 路由必须挂载 ``get_current_user`` 鉴权依赖。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.core.auth import get_current_user
|
||||
from app.modules.data_convert.router import _safe_output_filename, router
|
||||
|
||||
|
||||
def test_safe_output_filename_defaults() -> None:
|
||||
assert _safe_output_filename(None) == "converted-data.jsonl"
|
||||
assert _safe_output_filename("") == "converted-data.jsonl"
|
||||
|
||||
|
||||
def test_safe_output_filename_valid() -> None:
|
||||
assert _safe_output_filename("converted-data.jsonl") == "converted-data.jsonl"
|
||||
assert _safe_output_filename("my-data.v1.jsonl") == "my-data.v1.jsonl"
|
||||
assert _safe_output_filename(" 报告.jsonl ") == "报告.jsonl"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bad",
|
||||
[
|
||||
"../../etc/passwd",
|
||||
"../x.jsonl",
|
||||
"a/b.jsonl",
|
||||
r"a\b.jsonl",
|
||||
"a\\b.jsonl",
|
||||
"..",
|
||||
".",
|
||||
"x\x00.jsonl",
|
||||
"x\n.jsonl",
|
||||
"x\t.jsonl",
|
||||
],
|
||||
)
|
||||
def test_safe_output_filename_rejects_traversal(bad: str) -> None:
|
||||
with pytest.raises(HTTPException):
|
||||
_safe_output_filename(bad)
|
||||
|
||||
|
||||
def test_all_data_convert_routes_require_auth() -> None:
|
||||
for route in router.routes:
|
||||
node = getattr(route, "dependant", None)
|
||||
assert node is not None, f"route {route.path} has no dependency graph"
|
||||
stack = list(node.dependencies)
|
||||
calls: list = []
|
||||
while stack:
|
||||
dep = stack.pop()
|
||||
stack.extend(getattr(dep, "dependencies", []))
|
||||
calls.append(getattr(dep, "call", None))
|
||||
assert get_current_user in calls, f"route {route.path} is missing get_current_user auth"
|
||||
76
backend/tests/test_docs_security.py
Normal file
76
backend/tests/test_docs_security.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Swagger / ReDoc / OpenAPI 文档路由安全开关测试。
|
||||
|
||||
生产环境(APP_ENV=prod)默认关闭 /docs、/redoc、/openapi.json,
|
||||
避免未授权访问泄露 API 结构;本地开发环境默认开放,可用 ENABLE_DOCS 覆盖。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.config import docs_kwargs, get_settings
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_settings_cache():
|
||||
"""每次测试前后清空 get_settings 的 lru_cache,避免环境变量互相污染。"""
|
||||
get_settings.cache_clear()
|
||||
yield
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def test_docs_kwargs_enabled() -> None:
|
||||
assert docs_kwargs(True) == {}
|
||||
|
||||
|
||||
def test_docs_kwargs_disabled() -> None:
|
||||
assert docs_kwargs(False) == {"docs_url": None, "redoc_url": None, "openapi_url": None}
|
||||
|
||||
|
||||
def test_docs_disabled_by_default_in_prod(monkeypatch) -> None:
|
||||
monkeypatch.delenv("ENABLE_DOCS", raising=False)
|
||||
monkeypatch.setenv("APP_ENV", "prod")
|
||||
assert get_settings().enable_docs is False
|
||||
|
||||
|
||||
def test_docs_enabled_by_default_outside_prod(monkeypatch) -> None:
|
||||
monkeypatch.delenv("ENABLE_DOCS", raising=False)
|
||||
monkeypatch.setenv("APP_ENV", "local")
|
||||
assert get_settings().enable_docs is True
|
||||
|
||||
|
||||
def test_docs_env_override_enables_in_prod(monkeypatch) -> None:
|
||||
monkeypatch.setenv("ENABLE_DOCS", "true")
|
||||
monkeypatch.setenv("APP_ENV", "prod")
|
||||
assert get_settings().enable_docs is True
|
||||
|
||||
|
||||
def test_docs_env_override_disables_outside_prod(monkeypatch) -> None:
|
||||
monkeypatch.setenv("ENABLE_DOCS", "false")
|
||||
monkeypatch.setenv("APP_ENV", "local")
|
||||
assert get_settings().enable_docs is False
|
||||
|
||||
|
||||
def test_create_app_disables_docs_in_prod(monkeypatch, tmp_path) -> None:
|
||||
pytest.importorskip("fastapi")
|
||||
from app.main import create_app
|
||||
|
||||
monkeypatch.delenv("ENABLE_DOCS", raising=False)
|
||||
monkeypatch.setenv("APP_ENV", "prod")
|
||||
monkeypatch.setenv("LOG_DIR", str(tmp_path))
|
||||
app = create_app()
|
||||
assert app.docs_url is None
|
||||
assert app.redoc_url is None
|
||||
assert app.openapi_url is None
|
||||
|
||||
|
||||
def test_create_app_enables_docs_outside_prod(monkeypatch, tmp_path) -> None:
|
||||
pytest.importorskip("fastapi")
|
||||
from app.main import create_app
|
||||
|
||||
monkeypatch.delenv("ENABLE_DOCS", raising=False)
|
||||
monkeypatch.setenv("APP_ENV", "local")
|
||||
monkeypatch.setenv("LOG_DIR", str(tmp_path))
|
||||
app = create_app()
|
||||
assert app.docs_url == "/docs"
|
||||
assert app.redoc_url == "/redoc"
|
||||
assert app.openapi_url == "/openapi.json"
|
||||
Reference in New Issue
Block a user