Skip to content

fix(risk): resolve run_payload's true risk on every gate surface#160

Merged
xunholy merged 1 commit into
mainfrom
fix/run-payload-consent-downgrade
Jun 29, 2026
Merged

fix(risk): resolve run_payload's true risk on every gate surface#160
xunholy merged 1 commit into
mainfrom
fix/run-payload-consent-downgrade

Conversation

@xunholy

@xunholy xunholy commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

A consent/confirm-gate downgrade for Critical run_payload operations (category A, security). run_payload's nominal classification is High, but its effective risk depends on the target path — a .sub transmit, a badusb .txt, or an evil_portal launch dispatches to a Critical op. Only the interactive Run loop resolved this (resolveRunPayloadRisk → escalate before the confirm gate). The other two dispatch surfaces gated on the static High classification:

  • RunTool (used by the rules automation engine + the campaign executor, and callable directly): max(spec.Risk, Classify) = High, never resolved the path — so with --confirm-risk critical a Critical .sub ran with no confirm prompt.
  • MCP consent gate: classified by tool name only (High) — so PROMPTZERO_MCP_ALLOW_HIGH=1 (without ALLOW_CRITICAL) executed a Critical run_payload, breaking the invariant that ALLOW_HIGH never unlocks Critical.

The audit fail-closed floor (RequireOpen(High)) still held, so this was a confirm/consent downgrade, not an audit bypass — but it weakened two gates the project promises never to weaken.

Fix

A single source of truth — risk.ResolveRunPayloadRisk(path) — now used by all four surfaces (Run loop, RunTool, MCP consent handler, generator). RunTool escalates from the path before the audit + confirm gates; the MCP handler resolves the effective level per call before consentDecision. The two duplicated resolvers (agent.resolveRunPayloadRisk, tools.genRunPayloadRisk) are removed/delegated so the surfaces cannot drift.

Found via the internal audit sweep.

Verification

  • TestRunTool_RunPayloadCriticalGate: confirm-risk=critical + run_payload(.sub) via RunTool must hit the gate at Critical.
  • TestServer_CallTool_RunPayloadCriticalNotUnlockedByAllowHigh: ALLOW_HIGH=1 (no ALLOW_CRITICAL) + run_payload(.sub) over MCP must be refused with the Critical-tier message.
  • Both proven to FAIL on the pre-fix code and pass after.
  • task ci green (lint 0 / vet / build / test:full 0 fail / govulncheck clean); task eval 17/17.

Security fix — anchors v0.778.0.

run_payload's nominal classification is High, but its EFFECTIVE risk depends
on the target path: a .sub transmit, a badusb .txt, or an evil_portal launch
dispatches to a Critical operation. Only the interactive Run loop resolved
this (resolveRunPayloadRisk -> escalate before the confirm gate). The other two
dispatch surfaces gated on the static High classification, so a Critical
payload could slip past the Critical gate:

- RunTool (used by the rules automation engine and the campaign executor, and
  callable directly): computed max(spec.Risk, Classify)=High and never resolved
  the path, so with --confirm-risk critical a Critical .sub ran with NO confirm
  prompt.
- MCP consent gate: classified by tool name only (High), so
  PROMPTZERO_MCP_ALLOW_HIGH=1 (without ALLOW_CRITICAL) executed a Critical
  run_payload — breaking the invariant that ALLOW_HIGH never unlocks Critical.

The audit fail-closed floor (RequireOpen(High)) still held, so this was a
confirm/consent downgrade, not an audit bypass — but it weakened two of the
gates the project promises never to weaken.

Fix: a single source of truth, risk.ResolveRunPayloadRisk(path), now used by
all four surfaces (the Run loop, RunTool, the MCP consent handler, and the
generator's genRunPayloadRisk). RunTool escalates from the path before the
audit + confirm gates; the MCP handler resolves the effective level per call
before consentDecision. The two duplicated copies (agent.resolveRunPayloadRisk,
tools.genRunPayloadRisk) are removed/delegated so the surfaces cannot drift.

Found via the internal audit sweep.

Tests:
- TestRunTool_RunPayloadCriticalGate: with confirm-risk=critical, a
  run_payload(.sub) via RunTool must hit the confirm gate at Critical.
- TestServer_CallTool_RunPayloadCriticalNotUnlockedByAllowHigh: with
  ALLOW_HIGH=1 but not ALLOW_CRITICAL, run_payload(.sub) over MCP must be
  refused with the Critical-tier message.
Both proven to FAIL on the pre-fix code and pass after.

Verified: task ci green (lint 0 / vet / build / test:full 0 fail / govulncheck
clean); task eval 17/17.
@xunholy xunholy merged commit 4cd7875 into main Jun 29, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant