一、流程文档(按 AIcoding 六步落地,供新会话从交接文档开工) - 新增 docs/PRD/PRD-架构改进与稳定性加固.md:6 条 FR(文档勘误、非缺陷说明、 无密钥启动告警、审计失败告警、Redis 分布式锁、中间件顺序测试) - 新增 docs/项目框架设计/改进方案评审-问题清单与对比.md:24 项问题分档 A~G, 经两轮独立 AI 评审,无阻断级错误 - 新增 docs/项目框架设计/开发计划-架构改进.md:HOW 层设计,含合并前只做低风险 11 项的批次策略 - 新增 docs/项目框架设计/TODO-架构改进.md:T-101~T-109、T-201~T-202 可勾选项 - 新增 docs/交接文档-架构改进.md:自包含交接入口,hy3 新会话可直接开工 - 新增 docs/项目框架设计/架构设计说明书.md:按模块/分层逐一讲解的全量架构说明 二、代码改动(T-107/108/109、T-201.1、T-201.2) - app/main.py:启动时 DEEPSEEK_API_KEY 缺失告警,明确告知将走降级回复 - app/utils/authz.py:越权审计失败日志补 trace_id,便于串联全链路 - app/api/audit_middleware.py:审计失败日志补 status/path/request_id - app/service/risk/redis_gateway.py:新增 acquire_lock(SET NX EX)与 release_lock(Lua 原子释放,只删自己的锁) - app/service/risk/locks.py:run_locked 改为双层锁,Redis 为主、进程内锁为备; Redis 超时沿用 fn(locked=False) 降级语义,Redis 不可用(含测试 Fake 缺方法的 AttributeError)安全退回进程内锁,绝不抛异常 三、文档勘误(A1/A2/A3) - MEMORY.md:文件数 42→45、Tools 4→5 - 02-mysql-agent专用.sql:会话表 5→6 - 架构设计-风控模块.md:同步更正 四、测试 - 新增 tests/test_locks_redis.py:覆盖抢锁成功、占用超时、Redis 故障降级、 Fake 缺方法降级、只删自己锁、三处调用点 key 前缀 - tests/test_audit_middleware.py:补充告警字段断言 - 全量 pytest 510 passed(原基线 503)
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
"""鉴权拒绝留痕公共出口(手册 P-05 双写;T-04 评审 P1-2 收口)。
|
||
|
||
背景:越权留痕原本只在 API 层(api/deps._authz_audit)实现,对话 Tool 层
|
||
归属被拒时只落 agent_tool_call,全局鉴权审计维度缺失(输入防护台账查不到
|
||
对话链路的越权尝试)。本模块把该逻辑下沉到 utils,供两方共用:
|
||
- API 层:deps.deny / _authz_audit(403)
|
||
- 对话层:tool_service.run_tool 的 blocked 分支(AUTH_403_* 归属拒绝)
|
||
|
||
放 utils 而非 api 的原因:service 层不得反向依赖 api 层(分层约束)。
|
||
|
||
降级口径(与 T-02 审计一致):写库失败仅本地 logger.exception,不改变
|
||
拒绝语义——对话不因留痕故障中断,生产可切 fail-closed(待决,已登记)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from typing import Any, Sequence
|
||
|
||
from app.utils.trace import current_trace, new_trace
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 四 Agent 入口(input_guard_log.agent_type 的 ENUM 仅此四值;platform 跳过)
|
||
AGENT_TYPES = ("customer", "advisor", "analyst", "risk")
|
||
|
||
|
||
def record_authz_denial(
|
||
risk_repo: Any,
|
||
*,
|
||
actor_id: str,
|
||
code: str,
|
||
roles: Sequence[str] | None = None,
|
||
customer_id: str | None = None,
|
||
agent_type: str = "risk",
|
||
trace_id: str | None = None,
|
||
) -> None:
|
||
"""越权/拒绝留痕:audit_log(authz) + input_guard_log(四 Agent 内)。
|
||
|
||
agent_type 非四 Agent(如 platform 网关)时只写 audit_log——
|
||
input_guard_log.agent_type 是 ENUM,越界写会报数据截断(T-02 同口径)。
|
||
"""
|
||
trace_id = trace_id or current_trace() or new_trace()
|
||
try:
|
||
risk_repo.insert_audit_log(
|
||
{
|
||
"trace_id": trace_id,
|
||
"event_type": "authz",
|
||
"agent_type": agent_type,
|
||
"actor_id": actor_id or "anonymous",
|
||
"customer_id": customer_id,
|
||
"rule_id": None,
|
||
"input_summary": {"roles": list(roles or []), "code": code},
|
||
"decision": "forbidden",
|
||
"risk_score": None,
|
||
"handler_id": None,
|
||
"handler_result": None,
|
||
"handler_comment": None,
|
||
}
|
||
)
|
||
if agent_type in AGENT_TYPES:
|
||
risk_repo.insert_input_guard_log(
|
||
trace_id=trace_id,
|
||
agent_type=agent_type,
|
||
actor_id=actor_id or "anonymous",
|
||
guard_type="illegal_param",
|
||
action="blocked",
|
||
raw_excerpt=code,
|
||
)
|
||
except Exception:
|
||
logger.exception(
|
||
"authz 审计失败(已降级,拒绝语义不变):code=%s agent=%s actor=%s trace_id=%s",
|
||
code,
|
||
agent_type,
|
||
actor_id,
|
||
current_trace(),
|
||
)
|