Symptom
In a GitLab CI job using the plumber component (image getplumber/plumber v0.4.56, platform mode), the run logs:
ci config digest: not computed (no checkout of the analyzed project) - treated as divergent
level=warning msg="Could not read the project's own CI file; continuing on the platform's merged configuration" action=GetFullGitlabCI context=platform/gitlab err="<nil>" errPlatform="404 Not Found"
level=warning msg="The project's own CI file was not read; the controls that compare against it will report not_evaluable"
and ends with 5 controls Not Evaluated: 4 include_attribution_unavailable (external refs, includes up to date, forbidden versions, security jobs not weakened) and 1 raw_config_unavailable (job variables override). The job DID check out the repository (Checking out 309312ed as detached HEAD (ref is main) in the runner log). Observed on gitlab.com SaaS runners, job https://gitlab.com/bigtech-150/frontend/angular-dashboard-4/-/jobs/16421236156.
Root cause
The image ends with USER plumber (uid 65532), while the GitLab docker executor's helper clones $CI_PROJECT_DIR as root. Git 2.35.2+ then refuses the repository:
$ docker run --rm -v <root-owned repo>:/builds/x -w /builds/x --entrypoint /usr/bin/git getplumber/plumber@sha256:7191202... remote get-url origin
fatal: detected dubious ownership in repository at '/builds/x'
utils.DetectGitRemote() shells out to git remote get-url origin and returns nil on any error, so conf.GitRepoRoot == "" and conf.CheckoutIsAnalyzedProject == false (cmd/analyze_gitlab.go buildGitLabConf). From there:
computeLocalCIDigest refuses the digest (cmd/platform_mode.go), the run is treated as divergent and goes through the resolve endpoint (Source = SourceResolved).
useCheckoutInPlatformMode is false (control/task.go), so GetFullGitlabCI falls to the GitLab raw-file API arm. In platform mode resolveGitLabToken allows an empty token, so the read is anonymous and a private project answers 404 (masked 403). ConfString stays empty beside a merged pipeline with jobs, so RawConfigUnavailable = true and pipelineMustNotOverrideJobVariables reports raw_config_unavailable.
ConfigAndIncludesAgree() is Source == SourceSnapshot only (internal/platform/run.go), so every enabled include-attribution control reports include_attribution_unavailable.
So a perfectly normal platform-mode job on a private project loses 5 controls whenever the runner's checkout is root-owned, which is the default on the docker executor.
Suggested fix
Either or both:
- In the image (or in
templates/plumber.yml before plumber analyze): git config --global --add safe.directory '*' (or safe.directory "$CI_PROJECT_DIR"), so the non-root user can read the runner's checkout.
- Make
DetectGitRemote() log the git error at warning level instead of silently returning nil, so the reason for "no checkout of the analyzed project" is visible in the job log.
Separately (smaller): the resolve endpoint's 200 already carries includes, but ResolvedConfig has no Includes field and ConfigAndIncludesAgree hard-codes the snapshot source; wiring that through would keep the 4 include controls evaluable on every digest-divergent run, independent of this bug.
Found by the platform team on 2026-09-10 while explaining the job log above; platform side has nothing to change for this.
Symptom
In a GitLab CI job using the
plumbercomponent (imagegetplumber/plumberv0.4.56, platform mode), the run logs:and ends with 5 controls Not Evaluated: 4
include_attribution_unavailable(external refs, includes up to date, forbidden versions, security jobs not weakened) and 1raw_config_unavailable(job variables override). The job DID check out the repository (Checking out 309312ed as detached HEAD (ref is main)in the runner log). Observed on gitlab.com SaaS runners, job https://gitlab.com/bigtech-150/frontend/angular-dashboard-4/-/jobs/16421236156.Root cause
The image ends with
USER plumber(uid 65532), while the GitLab docker executor's helper clones$CI_PROJECT_DIRas root. Git 2.35.2+ then refuses the repository:utils.DetectGitRemote()shells out togit remote get-url originand returns nil on any error, soconf.GitRepoRoot == ""andconf.CheckoutIsAnalyzedProject == false(cmd/analyze_gitlab.gobuildGitLabConf). From there:computeLocalCIDigestrefuses the digest (cmd/platform_mode.go), the run is treated as divergent and goes through the resolve endpoint (Source = SourceResolved).useCheckoutInPlatformModeis false (control/task.go), soGetFullGitlabCIfalls to the GitLab raw-file API arm. In platform moderesolveGitLabTokenallows an empty token, so the read is anonymous and a private project answers 404 (masked 403).ConfStringstays empty beside a merged pipeline with jobs, soRawConfigUnavailable = trueandpipelineMustNotOverrideJobVariablesreportsraw_config_unavailable.ConfigAndIncludesAgree()isSource == SourceSnapshotonly (internal/platform/run.go), so every enabled include-attribution control reportsinclude_attribution_unavailable.So a perfectly normal platform-mode job on a private project loses 5 controls whenever the runner's checkout is root-owned, which is the default on the docker executor.
Suggested fix
Either or both:
templates/plumber.ymlbeforeplumber analyze):git config --global --add safe.directory '*'(orsafe.directory "$CI_PROJECT_DIR"), so the non-root user can read the runner's checkout.DetectGitRemote()log the git error at warning level instead of silently returning nil, so the reason for "no checkout of the analyzed project" is visible in the job log.Separately (smaller): the resolve endpoint's 200 already carries
includes, butResolvedConfighas noIncludesfield andConfigAndIncludesAgreehard-codes the snapshot source; wiring that through would keep the 4 include controls evaluable on every digest-divergent run, independent of this bug.Found by the platform team on 2026-09-10 while explaining the job log above; platform side has nothing to change for this.