fix: honor changed_files from every config source and stop failing silently
Scoping a scan to changed files now works no matter how the config was built, and when a scope cannot be honored the run says so instead of quietly scanning nothing. Before this change, several common setups silently scanned zero files and reported a clean result, which is the worst way for a security scan to fail.
The failures this fixes
Each of these produced a green, empty scan with no warning.
| Setup | What happened |
|---|---|
Config built outside create_config_from_args() | INPUT_CHANGED_FILES was missing from the environment loader entirely, so the whole repository was scanned. |
changed_files: "auto" from a JSON or dashboard config | The string was stored verbatim, then iterated character by character looking for files named a, u, t, and o. Scope resolved to nothing. |
Any trigger other than pull_request | GITHUB_BASE_REF is unset there, so no base could be found and the diff was empty. |
| Running inside the action's container | The scan runs as root over a workspace owned by the runner user, and git refuses that with detected dubious ownership. Every diff failed. |
| Shallow checkout | No history to diff against, so the scope came back empty. |
The container case was the one nothing in a workflow could fix, because setting safe.directory in a workflow step writes the runner's git config rather than the container's.
How each one is fixed - one resolver, a real base lookup, and a targeted git ownership retry
One resolver for every source. changed_files used to be resolved only inside create_config_from_args(). Every config source now goes through the same resolver, so environment, JSON, and dashboard configs all behave identically. A raw string like "auto" is resolved against git rather than stored as characters.
Pull request base lookup. When GITHUB_BASE_REF is unset, the base now comes from pull_request.base.sha and pull_request.base.ref in the GitHub event payload. That covers pull_request_review and pull_request_review_comment, whose payloads carry a top-level pull_request.
It deliberately does not cover issue_comment. That payload only has issue.pull_request, which is URLs with no base ref or sha, so there is no base to be had without a GitHub API call. config.py makes no network requests today, and falling back to the default branch would diff against the wrong base for anything targeting a release or stacked branch. So that trigger now names itself in a warning and says to pass GITHUB_BASE_REF from the workflow instead.
Git ownership. When git refuses to read the workspace, the git reads retry with that one directory trusted, and log that they did. When git is not refusing, which is any ordinary local run, nothing is relaxed. The retry is scoped to the workspace path and never a wildcard.
Every unhonorable scope now explains itself - five specific warnings replacing silent empty lists
A shallow checkout names fetch-depth: 0 as the fix. A workspace that is not a git repository, git refusing to read the repository, and a missing PR base each get their own warning. A scope that resolves to zero files warns that the scanners are being skipped.
All five previously returned an empty list in complete silence.
scan_all still overrides changed_files, but now logs a warning naming the scope it discarded, because that setting can come from a dashboard config rather than the workflow. The warning is explicit that the override is partial: only scanners that ask get_scan_targets() for their paths widen, while the secret and container scanners read changed_files directly and stay scoped, so setting both produces a mixed run.
Scanners no longer substitute their own scope - three separate ways a narrow scope used to widen or misfire
TruffleHog and Trivy used to fall back to their own staged-file scope when an explicit changed_files request resolved to nothing, which quietly replaced the scope the user asked for.
Trivy's filesystem vulnerability scan also widened an empty scope back out to the whole workspace. It is the one scanner that builds its own path list instead of going through get_scan_targets(), so it needed the check in two places.
Two more came out of review. Trivy's Dockerfile scan kept scanning the configured Dockerfiles when the changed set contained none, so a Python-only change still triggered a Dockerfile scan. And TruffleHog was handed changed paths that no longer exist on disk, producing trufflehog filesystem <workspace>/gone.py for a deleted file. Both now skip cleanly.
scan_all keeps working the same way throughout: it does not mean "ignore the scope", it means "when the scope resolves to nothing, scan everything rather than nothing".
Testing
267 tests pass. The new coverage pins both directions for each fix: that a scope narrows when it should, and that it does not widen when it should not.
Worth noting for reviewers, two existing tests in test_trufflehog_excludes.py were encoding the old bug. They named changed files that were never created on disk and asserted those paths reached TruffleHog. They now get real files, with their assertions untouched.
[!NOTE]
Medium Risk
Changes how security scanners choose targets across many config entry points and CI triggers; fixes false-clean runs but mis-scoping could still skip scans or mix full-repo SAST with scoped secrets if scan_all and changed_files are both set.
Overview
Diff-only changed_files scoping is unified so env (INPUT_CHANGED_FILES), JSON/dashboard configs, and CLI all resolve through resolve_changed_files_request / Config._resolve_changed_files_scope instead of only create_config_from_args — fixing whole-repo scans and the bug where a raw "auto" string was walked character-by-character.
PR base detection for auto/pr now tries GITHUB_BASE_REF, then pull_request.base.sha / pull_request.base.ref from GITHUB_EVENT_PATH, with explicit warnings when the base cannot be resolved (shallow checkout, non-git workspace, issue_comment without a base). Failed scopes skip scanners rather than silently widening to the full repo.
Container actions: git reads retry with safe.directory on the workspace only when ownership blocks the diff (runner-owned checkout, root in container).
scan_all still widens SAST via get_scan_targets, but logs a warning that secret/container scanners may stay scoped to changed_files.
TruffleHog and Trivy no longer substitute staged changes or scan the whole workspace when an explicit scope resolved empty; TruffleHog drops missing paths before invoking the CLI; Trivy skips Dockerfile scans when the changed set includes no Dockerfile.
Reviewed by Cursor Bugbot for commit e0cdf45e523f2e0ca761e75adfb61885d4f90380. Configure here.