Skip to content

Bug: Cache poisoning vulnerability in PR remediator causes silent evaluation failures #6367

@AftAb-25

Description

@AftAb-25

Describe the bug

There's a critical state-leak in internal/engine/actions/remediate/pull_request/pull_request.go where failing to fully commit and push a pull request remediation poisons the ingestion cache for all subsequent rule evaluations in that run.

When the PR remediator runs, it checks out a new branch in the go-git workspace (which sits directly on the shared ingestCache.Fs virtual filesystem). If the remediation algorithm fails after modifying files but before committing (e.g. failing during wt.Add or crashing during file generation), the worktree is left in a dirty state.

The code attempts to clean up in a defer block using checkoutToOriginallyFetchedBranch:

func checkoutToOriginallyFetchedBranch(...) {
	err := wt.Checkout(&git.CheckoutOptions{
		Branch: originallyFetchedBranch, 
        // BUG: Missing `Force: true`
	})
}

Because this wt.Checkout call omits Force: true and CleanOptions{Dir: true}, go-git defaults to preserving all uncommitted/untracked modifications and carrying them into the target branch.

Impact

Because the executor shares a single ingestCache.Fs across all rule evaluations for a repository during a run, this dirty state leaks across boundaries. If Rule A attempts remediation, modifies a file to be "compliant", but crashes out, Rule B will then evaluate against that dirty cache. Rule B will falsely evaluate the repository as compliant, creating a silent security bypass (false negative).

Steps to reproduce

  1. Configure two rules against the same entity (e.g., Rule A and Rule B).
  2. Set Rule A to auto-remediate with PRs.
  3. Simulate a failure during Rule A's remediation (after it modifies fs but before wt.Commit).
  4. Observe that Rule B evaluates against the uncommitted files produced by Rule A rather than the upstream repository state.

Expected Behavior

A failed remediation attempt should completely blow away the dirty worktree and restore the ingest cache entirely to its pristine upstream state.

Suggested Fix

Update checkoutToOriginallyFetchedBranch to aggressively reset the workspace:

	err := wt.Checkout(&git.CheckoutOptions{
		Branch: originallyFetchedBranch,
		Force:  true, // Discards modified tracked files
	})
    // And also run:
    // wt.Clean(&git.CleanOptions{Dir: true}) // Removes left-behind untracked files

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions