fix: changed_files resolved to zero files in the Docker container action (git dubious ownership) - #105
Conversation
…sses The pre-built Docker action runs as root while the checkout at GITHUB_WORKSPACE is owned by the runner user, so git's ownership check (git 2.35.2+) refused the repository. changed_files diff-only mode then resolved to zero files on every PR and the scanners silently skipped with a green run. Git-based repository/branch/commit and default-branch discovery failed the same way in local Docker runs. actions/checkout's own safe.directory entry cannot help: it lands in the runner's global config, which is not mounted into container actions. Inject safe.directory for the scan workspace into the environment of each git subprocess via command-scope GIT_CONFIG_* entries. No config files are touched, and caller-provided GIT_CONFIG_* entries (including the previously documented env-block workaround) are appended after, not clobbered. Tested: - unit: new TestDubiousOwnership tests drive the real git ownership check via GIT_TEST_ASSUME_DIFFERENT_OWNER; they fail on the unpatched code and pass with the fix. TestGitEnv covers append-after-caller, garbage GIT_CONFIG_COUNT, and the GITHUB_WORKSPACE default. Full suite: 222 passed. - container: on the published 3.0.0 and 2.2.1 images with a uid-1001 checkout and a root process, unpatched runs skip with zero targets; patched runs resolve the PR diff and report the seeded finding, with and without a pre-existing user GIT_CONFIG_* block. Same-owner and delete-only-PR behavior unchanged.
|
bugbot run |
Git ignores relative safe.directory values, so a relative --workspace under an ownership mismatch still failed the check and the diff kept resolving to empty. Resolve the path before writing the entry. Regression tests: a relative --workspace now passes the end-to-end ownership test, and TestGitEnv asserts the injected value is absolute. Both fail without this change. Full suite: 224 passed. Re-ran the container check on the published 3.0.0 image: unchanged, finding still reported.
A git failure during scope resolution previously collapsed into the same empty list as a genuinely empty diff, so any future breakage (beyond the safe.directory fix) would again skip every scanner and report green. - _detect_git_changed_files now captures git stderr (instead of DEVNULL), logs the failure reason, and returns None on failure vs [] for a truly empty diff. Ref-not-found is classified separately so the base-ref candidate loop still falls through, while unreadable-repo errors fail fast. - On failed resolution the config layer falls back to a full-repo scan with a prominent warning, never a silent zero-file skip. Delete-only diffs keep the empty-scope skip (existing test still guards this). - An unresolvable base ref in a PR context (e.g. shallow fetch) is now a failure rather than a quiet fall-through to the usually-empty staged diff. - Resolved scope is logged: file count at INFO, full list at DEBUG (the customer ask from the report). - Connector-internal staged-diff callers get 'or []' for the new contract. Full suite: 231 passed. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
|
bugbot run |
|
David Larsen (@dc-larsen) Heads up — I pushed What it adds: detection now distinguishes failed resolution ( One thing worth your eyes specifically: the full-scan-fallback and shallow-fetch-base cases are deliberate semantic changes from "skip silently" — flag if you know a customer scenario where that's the wrong call. |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3e836bd. Configure here.
|
Verified On your question, two customer scenarios worth weighing, both from the account that hit the original bug:
Both are loud and discoverable, which beats the silent skip. If you want to blunt them, the deterministic base-unresolvable case could fail fast with a config error naming the one-line fix, keeping the full-scan fallback for the non-deterministic failures. Fail-toward-scanning is a defensible default for a security tool and the warning names the cause, so I'm fine shipping it as is. The shallow-fetch change itself looks strictly better. The staged fallthrough in CI was almost always an empty diff, and local pre-commit runs have no |
The full-scan fallback is deliberate fail-toward-scanning behavior, with two known consequences on large repos (surprise full scans can be slow/OOM; the scan reports pre-existing findings until the checkout misconfiguration — usually a missing fetch-depth: 0 — is corrected). Document both, the fix, and how to tell an empty diff from a failed lookup in the logs. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
A shallow checkout (missing fetch-depth: 0) can never resolve the PR base ref, so the full-scan fallback would fire on every PR — slow or OOM-prone on large monorepos, and reporting pre-existing findings instead of the actual problem. That case is deterministic, so exit with a configuration error naming the one-line fix (matching the existing SystemExit convention for repository/branch discovery failures). Non-deterministic resolution failures keep the full-scan fallback. Proposed by @dc-larsen in review. Shallowness probed via git rev-parse --is-shallow-repository; tests fake it by touching .git/shallow. Full suite: 236 passed. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
|
David Larsen (@dc-larsen) thanks for running it end-to-end - and for raising the two real-world customer scenarios. I went one better than documenting: your fail-fast suggestion is now in the PR (b7c757a). Shallow checkout + unresolvable base → configuration error naming the fetch-depth: 0 fix (via The tradeoffs are documented, too (a204ae7): both your scenarios in So the final behavior matrix: deterministic misconfig → precise error with the fix; unknown failure → loud full scan; empty diff → skip; and nothing, ever, silently scans zero files. |
|
bugbot run |
Bugbot: the fail-fast only ran on the soft ref-miss path, but the common partial-fetch shape (base tip fetched, history disconnected) fails with 'A...HEAD: no merge base' — a hard error that skipped the shallow check and took the full-scan fallback on every PR. Classify no-merge-base distinctly and route both failure shapes through the shallow check; non-shallow no-merge-base keeps the fallback (with a warning). Docs broadened to cover both shapes. Full suite: 238 passed. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
An unresolvable diff scope previously widened to a full-repo scan. Both that and skipping the scanners are dishonest outcomes: skipping exits green having scanned nothing, so a passing check inspected no code and a warning in a run log is not a signal anyone acts on; widening does the expensive thing on every PR, which is precisely what requesting a diff scope was avoiding, and it reports pre-existing findings rather than the PR's own. Diff-only scoping is an explicit instruction, so when it cannot be honored the run now stops with a configuration error naming the underlying git error. This generalizes the fail-fast already applied to shallow checkouts, which keep their more specific fetch-depth message. scan_all is the documented opt-in for the previous widening behavior: it already meant "when the scope resolves to nothing, scan everything", so it doubles as the fail-open escape hatch. It is now a declared action input rather than env-only.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f96145c. Configure here.
* docs(changelog): draft 3.1.0 entry for the bundled release Covers #97, #98, #104, #105 and #106. Internal-only changes (#104, #106) are collapsed into a short section; customer-facing changes keep the detail needed to plan an upgrade, including the behavioral change where an unresolvable changed_files scope now fails instead of scanning. * chore(deps): refresh Socket-owned tool pins Socket npm CLI 1.1.154 -> 1.1.165 across all three images, matching the current npm release. Socket Python CLI 2.6.3 -> 2.7.0 in the heavy and app-tests images. 2.7.0 is NOT published to PyPI yet (latest is 2.6.11), so this is scaffolding: the heavy and app-tests image builds and core-tool-watch both fail until it lands. Keep this commit separate so it can be dropped or held if the CLI release slips. The socketdev Python SDK is already current at 3.5.0, so no change. * chore(release): 3.1.0 Version metadata, uv.lock, CHANGELOG date stamp, and 74 current-release documentation references, via scripts/prep_release.py --version 3.1.0. * fix(app-tests): refresh socketsecurity index metadata on install This install pins an exact version, so a stale cached uv index response makes a freshly published release look like it does not exist. Use --refresh-package for just this package rather than --no-cache, which would discard the cache mount's benefit for bandit and built wheels. Dockerfile.heavy already passes --no-cache-dir on its pip equivalent. * test: assert the socketsecurity pin, not the RUN's formatting The assertion matched an exact literal, so it broke when the install gained a --refresh-package flag and a line continuation even though the version pin it guards was unchanged. Collapse continuations and match the ARG-pinned spec after 'uv tool install' instead. Verified the guard still fails for an unpinned spec and for a hardcoded version that bypasses the ARG.
Bumps the pinned digest from 6b71cdf (v3.0.0) to 5b615c8 (v3.1.0). v3.1.0 is a minor, backwards-compatible release. Notable fixes relevant to how this workflow invokes the action: - changed_files resolved to zero files inside the Docker container action because of a git dubious-ownership error (SocketDev/socket-basics#105) - changed_files was not honored from every config source, and failed silently when it was not (SocketDev/socket-basics#98) Both bugs could cause the scan to silently cover no files, so this is a correctness fix for PR-scoped scanning, not just a version refresh. Digest verified against the v3.1.0 tag via the GitHub API.
Problem
changed_filesdiff-only mode resolves to zero files on every PR when Socket Basics runs as the pre-built Docker container action. Each run logsNo scan targets to analyze (scoped scan matched no existing files); skipping OpenGrepand exits green with zero alerts. The scan looks healthy while scanning nothing. Full-repo mode on the same checkout works. A customer hit this rolling the action out org-wide on v2.2.1 (eng-support thread: https://socketdev.slack.com/archives/C05TT2Q3FEZ/p1786394320795579), and it reproduces identically on 3.0.0.Root cause
GitHub runs Docker container actions as root, while the checkout mounted at
/github/workspaceis owned by the runner user (uid 1001). Git 2.35.2+ refuses to read a repository owned by a different user. Every git subprocess in_detect_git_changed_filesfails, the helper catches theCalledProcessErrorand returns[], and the empty scope makes every scanner skip.actions/checkoutdoes not cover this case: itssafe.directoryentry is written to/home/runner/.gitconfig, but container actions run withHOME=/github/home. A prior workflow step runninggit config --global --add safe.directorymisses the container for the same reason.The same mismatch breaks the git fallbacks in
_discover_repository,_discover_branch,_discover_commit_hash, and_discover_is_default_branch. That surface shows up in local Docker runs, where noGITHUB_*env vars mask it.Fix
A
_git_env()helper injectssafe.directory=<workspace>into the environment of each git subprocess via command-scopeGIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_nentries. Git honors command-scopesafe.directorysince 2.38, and the image ships 2.47.GIT_CONFIG_*entries are preserved, with ours appended after them, so users who already deployed the env-var workaround are unaffected.Testing
Unit (
tests/test_changed_files_scope.py)TestDubiousOwnershipdrives the real git ownership check end to end usingGIT_TEST_ASSUME_DIFFERENT_OWNER, git's own test knob for this code path. A control probe skips the tests on a git build without the knob. Both tests fail on the unpatched code and pass with the fix. I confirmed that by disabling the injection and re-running.TestGitEnvcovers appending after caller entries, a malformedGIT_CONFIG_COUNT, and theGITHUB_WORKSPACEdefault.Published container images
Repo owned by uid 1001, process as root,
GITHUB_BASE_REF=main, comment-only Go change:Total alerts: 0(bug)--repo/--branchTotal alerts: 1--repo/--branchTotal alerts: 1Total alerts: 1GIT_CONFIG_*block already setTotal alerts: 1(workaround coexists)Total alerts: 1(no regression)Total alerts: 0(empty-diff semantics preserved)Real GitHub Actions A/B
One workflow, two jobs on a comment-only Go-file PR in a scratch repo. Each job builds this action from source at a different ref. The rig branches on my fork swap
image:toDockerfileand the trivy base to the public upstream, and are otherwise identical to base and fix.baseline(unpatchedmain):No scan targets to analyze (scoped scan matched no existing files); skipping OpenGrep,Total alerts: 0, job passed green. That is the customer symptom on a real runner.fix(this branch): the diff resolved tosvc/main.go, OpenGrep ran on it, and the job failed the check withTotal alerts: 1andFound 1 high/critical severity issues. Failing the check is the intended blocking behavior for a seeded critical finding.Run: https://github.com/dc-larsen/sb-changed-files-test/actions/runs/31432924728 (private scratch repo, log lines quoted verbatim above). The rig branches
test-ownership-baselineandtest-ownership-fixondc-larsen/socket-basicsreproduce this against any scratch repo.Notes for review
fix/changed-files-scope-observabilityis complementary: it makes this failure loud instead of silent. One interaction: its troubleshooting doc recommendsgit config --global --add safe.directoryin a prior step, which cannot reach the container action. After this lands, that row can be dropped or swapped for the env-var form.Fixes CE-440
Note
Medium Risk
Changes CI failure modes for
changed_files(fail-closed by default) and scan-target precedence across all scanners; fixes a high-impact silent skip in container actions but may surface new errors on misconfigured checkouts untilfetch-depth: 0orscan_allis set.Overview
Fixes
changed_filesalways resolving to zero files in the pre-built Docker GitHub Action by marking the scan workspace assafe.directoryon git subprocesses via command-scopeGIT_CONFIG_*(no config files; existing userGIT_CONFIG_*entries are preserved). The same env is applied to repo/branch/commit/default-branch discovery.Diff scoping behavior changes: git failures are logged and distinguished from a genuinely empty diff (
Nonevs[]). When a requested scope cannot be resolved, the run now exits with a configuration error instead of a green job that scanned nothing; shallow checkouts get afetch-depth: 0hint. Resolved scope is logged (count at INFO, paths at DEBUG).Adds
scan_allas a GitHub Action input: optional fail-open widen to full-repo scan when resolution fails (with warning).get_scan_targetsprecedence flips so a successfulchanged_filesscope wins overscan_all; empty successful scopes still skip scanners. TruffleHog and Trivy honorchanged_files_scope_requestedso they do not fall back to staged files or widen past an intentional empty scope.Reviewed by Cursor Bugbot for commit f96145c. Configure here.