fix(workflow): durable HMAC, dedicated mint capability, head-exact approve match (#709)
Address formal review 435 REQUEST_CHANGES on PR #710: - F4: require durable GITEA_IRRECOVERABLE_AUTH_HMAC_KEY (fail closed; no ephemeral per-process secret); bind key_version into HMAC; cross-process verify works - F5: dedicated gitea.decision_lock.irrecoverable_recovery only; reject reconciler equivalence; authoritative incident body + author + content_digest; reject self-authored incident evidence - F3 residual: lock_targets_merged_pr_approval requires recorded-head match when expected_head_sha is provided (legacy no-head approve no longer primary-clears) Co-Authored-By: Grok 4.5 (xAI) <[email protected]>
This commit is contained in:
+36
-20
@@ -5140,12 +5140,14 @@ def gitea_issue_irrecoverable_provenance_authorization(
|
||||
org: str | None = None,
|
||||
repo: str | None = None,
|
||||
) -> dict:
|
||||
"""Mint a server-side authorization artifact for irrecoverable recovery (#709 F1).
|
||||
"""Mint a server-side authorization artifact for irrecoverable recovery (#709 F1/F5).
|
||||
|
||||
Non-forgeable: requires production native MCP transport (or pytest), a
|
||||
dedicated/reconciler mutation capability, live head equality, and validated
|
||||
incident evidence. Confirmation is human intent only — never authorization.
|
||||
Caller Booleans are not accepted.
|
||||
Non-forgeable: requires production native MCP transport (or pytest), the
|
||||
dedicated ``gitea.decision_lock.irrecoverable_recovery`` capability (no
|
||||
reconciler equivalence), live head equality, durable HMAC key, and
|
||||
authoritative incident evidence (author + canonical content_digest).
|
||||
Confirmation is human intent only — never authorization. Caller Booleans
|
||||
are not accepted.
|
||||
"""
|
||||
import irrecoverable_provenance as irp
|
||||
|
||||
@@ -5224,7 +5226,7 @@ def gitea_issue_irrecoverable_provenance_authorization(
|
||||
report["reasons"].extend(head_gate.get("reasons") or [])
|
||||
return report
|
||||
|
||||
# Incident evidence live validation.
|
||||
# Incident evidence live validation (author + canonical digest, #709 F5).
|
||||
comment_payload = None
|
||||
comment_err = None
|
||||
try:
|
||||
@@ -5243,6 +5245,10 @@ def gitea_issue_irrecoverable_provenance_authorization(
|
||||
expected_remote=remote,
|
||||
expected_org=o,
|
||||
expected_repo=r,
|
||||
expected_pr_number=pr_number,
|
||||
expected_head_sha=str(expected_head_sha),
|
||||
mint_actor_username=actor,
|
||||
reject_self_authored=True,
|
||||
)
|
||||
if not incident_gate.get("valid"):
|
||||
report["reasons"].extend(incident_gate.get("reasons") or [])
|
||||
@@ -5285,19 +5291,23 @@ def gitea_issue_irrecoverable_provenance_authorization(
|
||||
)
|
||||
return report
|
||||
|
||||
artifact = irp.build_authorization_artifact(
|
||||
remote=remote,
|
||||
org=o,
|
||||
repo=r,
|
||||
pr_number=pr_number,
|
||||
expected_head_sha=str(expected_head_sha),
|
||||
incident_issue=int(incident_issue),
|
||||
incident_comment_id=int(incident_comment_id),
|
||||
destroyed_subject=destroyed_subject,
|
||||
issuer_username=actor,
|
||||
issuer_profile=profile_name or "unknown",
|
||||
native_provenance=mcp_daemon_guard.mutation_provenance_fields(),
|
||||
)
|
||||
try:
|
||||
artifact = irp.build_authorization_artifact(
|
||||
remote=remote,
|
||||
org=o,
|
||||
repo=r,
|
||||
pr_number=pr_number,
|
||||
expected_head_sha=str(expected_head_sha),
|
||||
incident_issue=int(incident_issue),
|
||||
incident_comment_id=int(incident_comment_id),
|
||||
destroyed_subject=destroyed_subject,
|
||||
issuer_username=actor,
|
||||
issuer_profile=profile_name or "unknown",
|
||||
native_provenance=mcp_daemon_guard.mutation_provenance_fields(),
|
||||
)
|
||||
except irp.AuthSecretError as exc:
|
||||
report["reasons"].append(str(exc))
|
||||
return report
|
||||
artifact["kind"] = mcp_session_state.KIND_IRRECOVERABLE_PROVENANCE_AUTH
|
||||
saved = mcp_session_state.save_state(
|
||||
kind=mcp_session_state.KIND_IRRECOVERABLE_PROVENANCE_AUTH,
|
||||
@@ -5313,7 +5323,8 @@ def gitea_issue_irrecoverable_provenance_authorization(
|
||||
report["success"] = True
|
||||
report["reasons"].append(
|
||||
"issued server-side irrecoverable provenance authorization "
|
||||
"(non-forgeable; bound to remote/org/repo/PR/head/incident)"
|
||||
"(non-forgeable; bound to remote/org/repo/PR/head/incident; "
|
||||
f"key_version={artifact.get('key_version')})"
|
||||
)
|
||||
return report
|
||||
|
||||
@@ -5471,8 +5482,13 @@ def gitea_record_irrecoverable_decision_lock_provenance(
|
||||
incident_comment_id=incident_comment_id,
|
||||
comment_payload=comment_payload if isinstance(comment_payload, dict) else None,
|
||||
comment_lookup_error=comment_err,
|
||||
expected_remote=remote,
|
||||
expected_org=o,
|
||||
expected_repo=r,
|
||||
expected_pr_number=pr_number,
|
||||
expected_head_sha=str(expected_head_sha),
|
||||
mint_actor_username=actor,
|
||||
reject_self_authored=True,
|
||||
)
|
||||
if not incident_gate.get("valid"):
|
||||
report["reasons"].extend(incident_gate.get("reasons") or [])
|
||||
|
||||
Reference in New Issue
Block a user