Skip to content

feat(triage-security): add runner-side mutation authorization opt-in - #355

Closed
mrizzi wants to merge 2 commits into
RHEcosystemAppEng:mainfrom
mrizzi:TC-6934
Closed

mrizzi wants to merge 2 commits into
RHEcosystemAppEng:mainfrom
mrizzi:TC-6934

Conversation

@mrizzi

@mrizzi mrizzi commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • pre_triage_security.py collect now reads the runner-only opt-in FULLSEND_TRIAGE_MUTATION_AUTHORIZED and passes it to collect_bundle → build_bundle → authorization.mutation_authorized, replacing the hardcoded False.
    • exact true → true; unset or false → false (unchanged report-only default); any other value → ERROR: on stderr, exit 1, no bundle written (fail closed).
    • The variable is read from the runner environment only (same trusted-host pattern as FULLSEND_RELEASE_DECISIONS); it is not added to the harness env.sandbox and never reaches the sandbox.
  • No executor or schema change: execute_plan already accepts mutation-authorized results only when the bundle is authorized (covered by the existing test_authorized_actions_execute_in_order_with_footnoted_comments / test_unauthorized_mutating_plan_fails_without_jira_calls).
  • fullsend.md "Triage-security release orchestration" documents the opt-in and that enabling it in CI is gated on TC-6191.
  • Re-pin + re-lock of the triage-security harness only (verify-pr untouched) follows in a second commit.

Tests

  • New: test_mutation_authorization_opt_in_reaches_collector (unset/false/true), test_invalid_mutation_authorization_fails_closed (7 malformed values), test_authorized_bundle_satisfies_input_contract.
  • python3 -m pytest plugins/sdlc-workflow/scripts/ -q → 740 passed, 3 skipped; uvx skillsaw 0 errors; claude plugin validate plugins/sdlc-workflow passed.

Implements TC-6934

🤖 Generated with Claude Code

Summary by Sourcery

Enable explicit, fail-closed runner authorization for triage-security mutations while preserving report-only behavior by default.

New Features:

  • Allow the trusted runner to opt in to mutation-authorized triage bundles through an explicit environment setting.

Bug Fixes:

  • Fail closed before collection when the mutation authorization setting is missing an accepted exact value.

Enhancements:

  • Document runner-only mutation authorization and its CI gating requirements.
  • Add coverage for authorization propagation, malformed settings, and authorized bundle validation.

Chores:

  • Re-pin and re-lock the triage-security harness dependencies.

The pre-script sets authorization.mutation_authorized from the runner-only
FULLSEND_TRIAGE_MUTATION_AUTHORIZED variable: exact 'true' authorizes,
unset or 'false' stays report-only, any other value fails closed (exit 1,
no bundle). The variable is never exposed to the sandbox. Enabling it in
CI is gated on TC-6191.

Implements TC-6934

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces a trusted runner-side opt-in for triage mutation authorization, propagates it into validated bundles while failing closed on malformed configuration, adds coverage for default/authorized/rejected modes, and documents the operational and CI safeguards.

Sequence diagram for runner-side triage mutation authorization

sequenceDiagram
    participant Runner
    participant PreScript as pre_triage_security.py
    participant Collector as collect_bundle
    participant Bundle as Authorization bundle
    participant Executor

    Runner->>PreScript: collect
    PreScript->>PreScript: os.environ.get(FULLSEND_TRIAGE_MUTATION_AUTHORIZED)
    PreScript->>PreScript: parse_mutation_authorization(value)
    alt exact true
        PreScript->>Collector: collect_bundle(..., mutation_authorized=True)
        Collector->>Bundle: mutation_authorized=True
        Bundle-->>Executor: authorized bundle
        Executor->>Executor: accept mutation-authorized result
    else unset or exact false
        PreScript->>Collector: collect_bundle(..., mutation_authorized=False)
        Collector->>Bundle: mutation_authorized=False
        Bundle-->>Executor: report-only bundle
    else malformed value
        PreScript-->>Runner: ERROR on stderr, exit 1
        PreScript-->>Runner: no bundle written
    end
Loading

File-Level Changes

Change Details Files
Adds a fail-closed, runner-only mutation authorization opt-in and propagates it into collected bundle authorization metadata.
  • Parse FULLSEND_TRIAGE_MUTATION_AUTHORIZED with exact true/false semantics.
  • Pass the parsed authorization through collection into authorization.mutation_authorized, preserving report-only behavior by default.
  • Reject malformed values before collection with an error, exit status 1, and no output bundle.
  • Keep the variable out of harness sandbox environment propagation.
plugins/sdlc-workflow/scripts/pre_triage_security.py
plugins/sdlc-workflow/scripts/test_pre_triage_security.py
Documents operational usage, security boundaries, and CI rollout gating for mutation authorization.
  • Document the runner environment opt-in and fail-closed behavior.
  • Clarify that mutation execution remains executor-gated by bundle authorization.
  • Record the TC-6191 gate for enabling the setting in GitLab CI.
fullsend.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="plugins/sdlc-workflow/scripts/pre_triage_security.py" line_range="743" />
<code_context>

-def collect_bundle(issue_key, project_root, release_decisions=None):
-    """Collect all credentialed evidence for one poller-dispatched security issue."""
+def collect_bundle(issue_key, project_root, release_decisions=None, mutation_authorized=False):
+    """Collect all credentialed evidence for one poller-dispatched security issue.
+
</code_context>
<issue_to_address>
**String input grants mutation authorization**

When a Python caller passes a string or another truthy non-boolean value to `collect_bundle`, `collect_bundle` forwards the value to `build_bundle`, which applies `bool(mutation_authorized)`. A caller passing the non-empty string `"false"` therefore produces an authorized bundle, and the executor accepts mutation plans from it.

Require `mutation_authorized` to be an actual `bool` at the collector or bundle-building boundary instead of coercing arbitrary values.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if the runner opts in when it should not, the bundle is marked authorized and the executor can perform Jira mutations; those changes may remain after this code is reverted. The default stays report-only, so the risk depends on the trusted runner setting the opt-in.

Blocking findings: plugins/sdlc-workflow/scripts/pre_triage_security.py:743


Sourcery is free for open source - if you like our reviews please consider sharing them ✨


def collect_bundle(issue_key, project_root, release_decisions=None):
"""Collect all credentialed evidence for one poller-dispatched security issue."""
def collect_bundle(issue_key, project_root, release_decisions=None, mutation_authorized=False):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · String input grants mutation authorization

When a Python caller passes a string or another truthy non-boolean value to collect_bundle, collect_bundle forwards the value to build_bundle, which applies bool(mutation_authorized). A caller passing the non-empty string "false" therefore produces an authorized bundle, and the executor accepts mutation plans from it.

Require mutation_authorized to be an actual bool at the collector or bundle-building boundary instead of coercing arbitrary values.

Prompt for AI agents
In `plugins/sdlc-workflow/scripts/pre_triage_security.py` at line 743:

**String input grants mutation authorization**

When a Python caller passes a string or another truthy non-boolean value to `collect_bundle`, `collect_bundle` forwards the value to `build_bundle`, which applies `bool(mutation_authorized)`. A caller passing the non-empty string `"false"` therefore produces an authorized bundle, and the executor accepts mutation plans from it.

Require `mutation_authorized` to be an actual `bool` at the collector or bundle-building boundary instead of coercing arbitrary values.

Re-pin the triage-security child base to the commit carrying the
mutation-authorization opt-in and re-lock triage-security only; the
verify-pr harness and lock entry are unchanged.

Implements TC-6934

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mrizzi

mrizzi commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #356 (same commits plus the triage-security re-pin, opened from an upstream branch so fullsend lock can fetch the pinned commit).

@mrizzi mrizzi closed this Oct 26, 2026
@mrizzi
mrizzi deleted the TC-6934 branch October 26, 2026 16:34
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