code-to10 v6 A3: enforce signer separation on audit-verdict submission
process_audit_verdict now calls validate_signer_separation before any state save or ledger write (previously the function had no production caller on this path: it only ran inside collect_failed_checks via decide_pushback, which itself has no production caller). Signer is bound to the authenticated caller -- a body signer differing from the caller is rejected -- and executor/coordinator_approver/qa_tester must be declared and distinct from the caller; missing field, self-audit, or forged signer raises ValueError, mapped by _loop_run to HTTP 400 with no side effects. No new route. Honest limits are in the helper docstring: declared-identity enforcement only (task rows store no authoritative executor / coordinator / QA identity; task executor is a backend, not a person), and audit-fix reaudit verdicts are out of scope (the fix poster is not necessarily the re-auditor, so binding signer to the fix poster would be a mis-binding). (cherry picked from commit 56dce5165f4d08f38fe1730b9eea3b78f90cb97d) Reconcile-port: handler_lifecycle.py docstring hunk redirected into cloud/server.py audit-verdict handler (target keeps 6-mixin split; enforcement itself is in audit_loop_wiring.py and applied cleanly).
This commit is contained in:
1 parent
5db89d1b06
commit
19b9767478
4 files changed
+160
-3
No files matched your search
@@ -127,6 +127,7 @@ import time
|
||||
|
||||
from cloud import audit_loop_driver as _driver
|
||||
from cloud import audit_wiring as _wiring
|
||||
from cloud.audit_pushback import validate_signer_separation as _validate_signer_separation
|
||||
from cloud.merge_gate_hard import evaluate_hard_merge_gate as _hard_gate
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -715,6 +716,45 @@ def _finish_process(conn, ledger_store, task, action,
|
||||
}
|
||||
|
||||
|
||||
def _require_signer_separation(verdict, user_id):
|
||||
"""Verdict 提交时的 signer 分离强制(A3,fail-closed)。
|
||||
|
||||
signer 不取自请求体:它就是认证调用方 ``user_id`` 本人——
|
||||
体里自带 signer 若与调用方不一致即拒(否则调用方可随手
|
||||
写一个“别人”冒充独立审计,字段不等式检查就成了摆设)。
|
||||
executor / coordinator_approver / qa_tester 必须由提交方
|
||||
在 verdict 里显式声明,缺字段或任一与调用方相等即
|
||||
ValueError(HTTP 层 _loop_run 映射为 400),且校验发生在
|
||||
任何循环状态落盘与链写之前,拒掉的提交不留任何痕迹。
|
||||
|
||||
诚实边界:本校验只保证“提交方 ≠ 提交方自己声明的另三
|
||||
方”,不能识破提交方谎报另三方身份——任务行本身不存
|
||||
执行人/协调批准人/QA 测试人的权威身份(任务 executor 列
|
||||
是 local/docker/cloud 执行后端,不是自然人),服务端无
|
||||
从独立来源核对的依据;谎报身份的防范须另立身份溯源,
|
||||
不在本项用字段检查冒充。audit-fix 路径的 reaudit_verdict
|
||||
不走本校验:那里的 HTTP 提交人是修复提交人,未必是重审
|
||||
审计人,把 signer 绑到修复提交人身上是错绑,见交回报告
|
||||
的已知残留;该路径仍受自动化检查 + 完整重审结论约束。
|
||||
"""
|
||||
if not isinstance(verdict, dict):
|
||||
raise TypeError("verdict must be a dict")
|
||||
claimed = verdict.get("signer")
|
||||
if claimed is not None and claimed != user_id:
|
||||
raise ValueError(
|
||||
"verdict signer must equal the authenticated caller")
|
||||
audit = {
|
||||
"signer": user_id,
|
||||
"executor": verdict.get("executor"),
|
||||
"coordinator_approver": verdict.get("coordinator_approver"),
|
||||
"qa_tester": verdict.get("qa_tester"),
|
||||
}
|
||||
valid, reasons = _validate_signer_separation(audit)
|
||||
if not valid:
|
||||
raise ValueError(
|
||||
"audit signer separation failed: %s" % ",".join(reasons))
|
||||
|
||||
|
||||
def process_audit_verdict(conn, ledger_store, task, verdict, *,
|
||||
quota_check=None, cost_threshold=None,
|
||||
old_impl_ref=None, user_id="system",
|
||||
@@ -724,9 +764,17 @@ def process_audit_verdict(conn, ledger_store, task, verdict, *,
|
||||
The original task's status is never touched here (the
|
||||
server handler likewise does not change it): a fail blocks
|
||||
merge/release via the chain and derives a fix subtask, it
|
||||
does not roll the completed task back into a re-run."""
|
||||
does not roll the completed task back into a re-run.
|
||||
|
||||
Signer separation (A3) is enforced before anything is
|
||||
written: the caller is the signer, and the verdict must
|
||||
declare executor / coordinator_approver / qa_tester, all
|
||||
distinct from the caller; otherwise ValueError (HTTP 400)
|
||||
and no loop state or ledger row is created.
|
||||
"""
|
||||
if not isinstance(verdict, dict):
|
||||
raise TypeError("verdict must be a dict")
|
||||
_require_signer_separation(verdict, user_id)
|
||||
module_id = resolve_module_id(task)
|
||||
contract_version = resolve_contract_version(task, verdict)
|
||||
state = load_loop_state(conn, module_id, contract_version)
|
||||
|
||||
Reference in new issue
Block a user