Files
Agent-System/.doc/remediation/2026-09-26-system-code-review-fixes.md
T

9.4 KiB
Raw Blame History

title, status, created, updated, owner, task_id, severity, source_audit, base_dev_sha
title status created updated owner task_id severity source_audit base_dev_sha
系统 Code Review P0/P1 修复(IDOR、门禁盲区、fail-open、命令白名单、validated_only 硬拒) done 2026-09-26 2026-09-26 pi system-review-p0-p1-fixes-2026-09-26 critical ../audit/2026-09-26-system-code-review.md f4c1b73f28

系统 Code Review P0/P1 修复

来源:2026-09-26 系统全面 code review(被审 tip f4c1b73f)。 用户授权(2026-09-26):修复 2 Critical(FIND-SEC-01、FIND-ARCH-01)+ 3 个 High 行(FIND-SEC-02/03、FIND-FLOW-01、FIND-QUAL-01),修复后与报告一起集成,清理任务 worktree。本文件是下列 issue 的唯一主记录;发现细节与证据链以来源审计为准,此处记修复方案、经过与验收。

修复范围与 issue 映射

FIND issue_id 修复内容 状态
FIND-SEC-01 ISSUE-20260926-ctx-idor GetUserContext 会话归属校验(handler 403 + SQL/内存双防线) resolved
FIND-ARCH-01 ISSUE-20260926-archcheck-untracked arch-check 未登记表 fail-closed + 补登记 50 表 resolved
FIND-SEC-02 ISSUE-20260926-authz-failopen Authorizer 无池严格拒绝;显式 NewInMemoryAuthorizer 接缝 resolved
FIND-SEC-03 ISSUE-20260926-swboot-shell-prefix argv 化执行(去 sh -c)+ 白名单精确匹配 resolved
FIND-FLOW-01 ISSUE-20260926-intentflow-validated-confirm Confirm 拒 validated_only + 执行器硬读 mode resolved
FIND-QUAL-01 ISSUE-20260926-dual-flags-drift 组合根 flag 一致性 fail-fast resolved(含残差)
FIND-QUAL-01(入口收敛残差) ISSUE-20260926-governance-multi-entry docgov 三入口 + Coordinator/OwnerMux 合并 deferred
FIND-ARCH-02 ISSUE-20260926-server-pool-wiring server 持 Pool 直装 swboot(本轮未授权修复) open

各项修复经过

1. FIND-SEC-01 会话上下文 IDOR(ISSUE-20260926-ctx-idor)

  • 根因:handleGetUserContext 只校验 user_id 归属,session_id 直通 GetUserContext;chat_messages 查询 WHERE session_id = $1 无归属条件,绕过 chatflow 的会话授权。
  • 修复(双层):
    • internal/server/handlers_context.go:sessionID 非空时经 GetSessionOwnerID 校验归属(不存在与归属他人同返 403,不泄露存在性)。
    • internal/ctxmgr/manager.go:SQL 增加 EXISTS(chat_sessions 归属目标用户) 子句;内存分支按 sessions[sessionID].UserID == userID 过滤——未来调用方遗漏 handler 校验也不泄露。
  • 回归:TestGetUserContext_OtherUserSessionDenied(他户 session→403、不存在→403、本人→200 且含正文,内存 HTTP);TestGetUserContext_PG_SessionOwnerFilter(隔离 PG 库:他人视角 manager 层不返回正文、归属者正常)。

2. FIND-ARCH-01 arch-check 未登记表静默跳过(ISSUE-20260926-archcheck-untracked)

  • 根因:internal/archtest/scan.go if !tracked { continue }——表唯一写者门禁对未登记表(含 swboot_controlled_executions、projects、outbox_events 等)无强制力。
  • 修复:
    • scan.go:未登记表产生 sql_write finding(fail-closed);仅豁免扫描正则已知假阳性词汇(set/of/skip/below/row/state/step/doc/docgov/message/project/aggregation/ref_count,来自 ON CONFLICT DO UPDATE SET、FOR UPDATE OF/SKIP LOCKED 与注释散文;静态清单,非策略豁免)。
    • module-boundaries.yaml:补登记 50 张既有写者真实表。多 owner 条目(projects、users、project_members、context_memory_chunks)是现状快照,新包写入仍被 block;单一写者收敛留在各模块批次(见 ISSUES 残差说明)。
  • 回归:TestUntrackedTableWriteIsBlocked(新表写入必须 block 且 detail 提示登记);TestUpsertSetKeywordIsNotFlaggedAsUntracked(SET 噪音不误报)。全仓 arch-check --mode block PASS。

3. FIND-SEC-02 Authorizer 无池 fail-open(ISSUE-20260926-authz-failopen)

  • 根因:IsProjectMember/IsProjectOwner 在 !enabled()(pool==nil)时 return userID > 0 ——任意登录用户获得任意项目访问;组合根始终 NewAuthorizer(pool),nil pool 也构造成功,绕过「nil→failClosed」语义。
  • 修复:NewAuthorizer(nil) 一律严格拒绝(false);新增显式 NewInMemoryAuthorizer() 仅供 ALLOW_IN_MEMORY 演示/测试接缝(视任意登录用户为成员);组合根 app.Assemble 在 pool==nil && cfg.AllowInMemory 时选用。误配(无库且未显式声明)→ 拒绝。
  • 波及与处置:8 个依赖 fail-open 作测试接缝/隐式放行的既有用例——1 个改用显式内存授权 override,5 个业务行为用例改走 newServerWithMemoryIAM/AllowInMemory=true(显式演示模式契约),1 组 u5 用例补 ModeLive(属 FLOW-01 波及),1 个生命周期用例由组合根修复自然恢复。无削弱鉴权断言的用例。
  • 回归:TestMembershipChecks_FailClosedWithoutPool;隔离 PG 库 authz 套件(ConcurrentLastWriter/DocumentDuty/AtomicDutyRevoke/UpsertProjectMember)PASS。

4. FIND-SEC-03 swboot sh -c + 前缀白名单(ISSUE-20260926-swboot-shell-prefix)

  • 根因:受控命令经 sh -c cmdLine 执行;允许判定为 basename 前缀匹配(go 放行 go-evil)。ApproveMerge 自身恒拒未变。
  • 修复:runCommand 改 argv 化 exec.CommandContext(fields[0], fields[1:]...)(元字符只成为字面参数);commandMatchesPrefixes→commandAllowed,仅 basename 精确相等(含绝对路径条目取其 basename)。admin 白名单粒度仍是真实边界(报告 FIND-SEC-03 说明),运维登记白名单时应只放具体命令名。
  • 回归:TestExecutor_CommandAllowlistIsExactNotPrefix(前缀伪装拒绝、精确/绝对路径放行、空命令拒绝);TestExecutor_NoShellEvaluation(echo x && touch <marker> 不求值、marker 不产生、输出为字面参数)。

5. FIND-FLOW-01 validated_only 可 Confirm 后 dispatch(ISSUE-20260926-intentflow-validated-confirm)

  • 根因:DecisionConfirm 允许 PlanProposed;确认后 HTTP 即 DispatchEligible;ExecuteClaimed 不读 ExecutionMode——shadow「安全」依赖「没人点 Confirm」。
  • 修复(双层):
    • DecidePlan:Confirm 对 plan.ExecutionMode == validated_only 一律 ErrPlanModeNotConfirmable(403)。
    • ExecuteClaimed:硬读步骤级(缺省计划级)ExecutionMode,validated_only 一律 ErrPlanModeNotExecutable——覆盖所有派发路径(confirm 后、worker、误推进)。
    • 新错误码 PLAN_MODE_NOT_CONFIRMABLE / PLAN_MODE_NOT_EXECUTABLE 入 ErrorCode 与 HTTP 映射。
  • 回归:TestConfirmRejectedForValidatedOnlyPlan;TestExecuteClaimedRefusesValidatedOnlyStep(经 DecisionContinue 误推进后 DispatchEligible 硬拒,步骤不成 succeeded、owner 零执行)。既有 4 个用例的 draft() helper 补显式 ModeLive(缺省 validated_only 是安全默认,live 机制用例需显式声明)。

6. FIND-QUAL-01 双套 flags 静默漂移(ISSUE-20260926-dual-flags-drift,含残差)

  • 修复:intentflow.ValidateFlagConsistency(组合根 Assemble 调用)——PlanTier 升档但 MULTI_INTENT_ENABLED=false 时启动期 fail-fast(Assemble 最早阶段调用),消除「intentflow 已升档、intent 仍关」静默漂移配置上线。
  • 回归:TestValidateIntentFlagConsistency(升档+开=过;legacy 恒过;四档×关=拒)。
  • 残差(deferred,ISSUE-20260926-governance-multi-entry):docgov 三入口(chat PendingAction / docgov_owner / handlers_docgov)与 Coordinator ~40 intent case + OwnerMux 平行面合并是多日级重构(自带回归矩阵),超出本批授权合理范围;已在 ISSUES 登记 owner/触发条件。

验收汇总

被测 SHA:本任务 worktree HEAD(见下方提交记录);基线 f4c1b73f。

检查 结果
./scripts/arch-check.sh --mode block PASS(fail-closed 改动 + 50 表登记后)
go vet ./... PASS
go test -short ./... 全绿
隔离 PG:TestGetUserContext_PG_SessionOwnerFilter PASS(临时库建删于 55432 verify 容器)
隔离 PG:authz + server duty 套件 PASS
隔离 PG:duty-sync 脚本 TestHealProjectDocuments_SkipsDutyLocked_PG FAIL——基线 f4c1b73f 同样失败(FK fixture 既有问题,与本批无关)
TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E 非 short 模式因 KB 离线前置检查失败——基线同样失败(需真实 Runner/KB,非本批回归)

部署

已部署(2026-09-26 用户授权):launch.sh --no-docker FORCE_BUILD 重建并重启 no-docker dev 服务(8080)。核验:

  • binary vcs.revision=1ceda34c(= dev tip,含全部修复)
  • /health、/ready ok;schema 75;PI sidecar 18865 健康
  • 线上 IDOR 负例:他户读会话→403、user_id 覆写→403、未知会话→403;归属者→200 且正文完整
  • 探测留痕(dev 业务库):probe 用户 dpv_*/dpa_*、项目 probe-shape*/idor-live-probe-*,属一次性验证数据

经验

见 LESSON-20260926-failclosed-bypass-contract:旁路入口必须继承主路径硬边界;门禁/执行器对「未知」默认必须拒绝。