fix(security): 多租户隔离全量修复 security-fix multi-tenant (OpenCode审查P0)
- bot_bridge 18数据端点全部 entity_id 隔离(Depends(get_entity_id)/body),/ping /risk-levels 豁免 - alert_rules 11端点 entity_id 隔离 + KPIAlert/DynamicThresholdCache 写入 entity_id - reports 17端点隔离 + generate_report 写 ReportHistory.entity_id + history 按 entity 过滤 - ai_analysis 移除硬编码默认key,改 _require_deepseek_key() 强制 env 缺失 503 - budget auto-decompose 硬编码 entity_id==1 改请求 entity - kpis update_kpi 加 UPDATE_KPI_WHITELIST 白名单(status/important_flag 不可越权改) - data_quality 收敛:删 MySQL JSON 版 _run_rule_checks,check-governance 复用 _run_governance_checks(SQLite 兼容) - _eval_threshold invert 参数修复(>=↔< 等取反),red 分支不传 invert 保持行为 - 新增 test_security_multitenant.py 13条(bot_bridge/alert_rules/reports 隔离 + invert + SQLite governance) - models 6表加 entity_id 列;生产库已 ALTER + 按真实归属回填(kpi_alerts 472行中216行属entity≠1)
This commit is contained in:
@@ -735,6 +735,19 @@ def create_kpi(data: dict, db: Session = Depends(get_db), user=WRITE_ROLES, enti
|
||||
return kpi_to_dict(kpi)
|
||||
|
||||
|
||||
# 可更新字段白名单(安全修复 2026-08-31):update_kpi 只允许更新业务属性。
|
||||
# status / important_flag 等敏感字段及主键/归属字段(id/kpi_code/entity_id/map_id/created_*)一律忽略,
|
||||
# 防越权修改(如借 update 篡改重要标记/上下架状态)。
|
||||
UPDATE_KPI_WHITELIST = {
|
||||
"kpi_name", "dimension", "objective", "formula", "formula_desc",
|
||||
"data_source_type", "data_source_config", "data_source", "data_owner",
|
||||
"frequency", "unit", "target_value", "target_monthly", "target_quarterly",
|
||||
"target_yearly", "target_calc_type", "threshold_green", "threshold_yellow",
|
||||
"threshold_red", "category", "data_level", "data_category",
|
||||
"responsible_dept", "responsible_user", "kpi_level", "bot_source", "epic",
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{kpi_id}")
|
||||
def update_kpi(kpi_id: int, data: dict, db: Session = Depends(get_db), user=WRITE_ROLES, entity_id: int = Depends(get_entity_id)):
|
||||
kpi = db.query(KPIDefinition).filter(KPIDefinition.id == kpi_id).first()
|
||||
@@ -749,6 +762,10 @@ def update_kpi(kpi_id: int, data: dict, db: Session = Depends(get_db), user=WRIT
|
||||
raise HTTPException(422, detail={"message": "数据校验不通过", "errors": errs})
|
||||
data.pop("entity_id", None) # 禁止通过update改企业归属
|
||||
data = apply_calc_type_inference(data, infer_missing=False)
|
||||
# 白名单过滤(安全修复 2026-08-31):白名单外字段(status/important_flag/主键等)忽略不修改
|
||||
for k in list(data.keys()):
|
||||
if k not in UPDATE_KPI_WHITELIST:
|
||||
data.pop(k, None)
|
||||
for k, v in data.items():
|
||||
if hasattr(kpi, k) and v is not None:
|
||||
setattr(kpi, k, v)
|
||||
|
||||
Reference in New Issue
Block a user