feat(database): 使用SQLite替换MySQL作为默认数据库

将默认数据库从MySQL切换到SQLite以简化本地开发环境配置

BREAKING CHANGE: 数据库连接字符串已从MySQL更改为SQLite格式

---

refactor(audit): 实现敏感数据脱敏功能

添加敏感键名常量定义,并实现递归脱敏函数,
确保审计日志中不会泄露敏感信息如API密钥、密码等

---

fix(legacy-mysql): 修复查询参数限制验证和注入漏洞

增强只读查询参数处理逻辑,添加类型验证并防止SQL注入
同时修复参数限制数值越界问题

---

test(smoke): 增加审计脱敏和审批流程测试用例

添加审计日志脱敏验证测试和审批决策流程测试,
确保敏感数据不会被记录到审计日志中

---

chore(config): 添加Ruff缓存目录到忽略列表

更新.gitignore和.dockerignore文件,
添加.ruff_cache/目录到忽略列表以避免提交临时文件

---

build(deps): 添加Ruff依赖项到环境配置

在environment.yml中添加ruff==0.8.4依赖项,
并在pyproject.toml中配置相关忽略规则

---

refactor(approval): 移除审批创建中的冗余字段

从ApprovalDecision模型中移除不必要的approver字段,
简化审批决策接口设计

---

refactor(database): 明确数据库模块导出接口

为app/core/database.py添加__all__列表,
明确指定模块对外暴露的公共接口
```
This commit is contained in:
2026-07-06 11:03:29 +08:00
parent 9b87c6a7a3
commit 0dbe5c1c2d
11 changed files with 124 additions and 5 deletions

View File

@@ -33,6 +33,7 @@ from app.core.database import Base, engine
from app.core.pagination import bounded_limit, bounded_offset
from app.core.security import require_api_key, require_approval_api_key
from app.main import _allow_cors_credentials, app
from app.modules.audit.constants import AUDIT_REDACTED_VALUE
from app.modules.legacy_mysql.service import LegacyMySQLService
from app.modules.reports.constants import (
LifecycleAttentionKey,
@@ -108,6 +109,12 @@ def test_feishu_webhook_routes_message_event() -> None:
assert data["handled"] is True
assert data["result"]["command"] == "risk_summary"
logs_response = client.get("/api/v1/audit/logs", headers=headers)
assert logs_response.status_code == 200
audit_payload = json.dumps(logs_response.json(), ensure_ascii=False)
assert "test-feishu-token" not in audit_payload
assert AUDIT_REDACTED_VALUE in audit_payload
def test_api_key_and_feishu_webhook_fail_closed(monkeypatch) -> None:
monkeypatch.setenv("API_KEY", "")
@@ -195,7 +202,7 @@ def test_approval_gate_for_high_risk_update() -> None:
approve_create_response = client.post(
f"/api/v1/approvals/{create_ticket_id}/approve",
headers=approval_headers,
json={"approver": "spoofed-manager", "comment": "ok"},
json={"comment": "ok"},
)
assert approve_create_response.status_code == 200
assert approve_create_response.json()["approver"] == "approval-manager"
@@ -606,3 +613,55 @@ def test_legacy_readonly_query_requires_allowlist(monkeypatch) -> None:
finally:
monkeypatch.delenv("LEGACY_PROJECT_QUERY", raising=False)
get_settings.cache_clear()
def test_legacy_readonly_query_clamps_param_limit(monkeypatch) -> None:
captured: dict[str, dict] = {}
class FakeResult:
def mappings(self) -> "FakeResult":
return self
def all(self) -> list:
return []
class FakeConnection:
def __enter__(self) -> "FakeConnection":
return self
def __exit__(self, exc_type, exc, traceback) -> None:
return None
def execute(self, statement, params):
captured["params"] = params
return FakeResult()
class FakeEngine:
def connect(self) -> FakeConnection:
return FakeConnection()
monkeypatch.setenv("LEGACY_PROJECT_QUERY", "SELECT id FROM projects LIMIT :limit")
get_settings.cache_clear()
monkeypatch.setattr(
LegacyMySQLService,
"_ensure_engine",
staticmethod(lambda: FakeEngine()),
)
try:
result = LegacyMySQLService(None).execute_readonly(
"SELECT id FROM projects LIMIT :limit",
{"limit": 9999},
limit=9999,
)
assert result["row_count"] == 0
assert captured["params"]["limit"] == 500
with pytest.raises(HTTPException) as exc_info:
LegacyMySQLService(None).execute_readonly(
"SELECT id FROM projects LIMIT :limit",
{"limit": "invalid"},
)
assert exc_info.value.status_code == 422
finally:
monkeypatch.delenv("LEGACY_PROJECT_QUERY", raising=False)
get_settings.cache_clear()