DAP central image pull fails under inbound CI_JOB_TOKEN scope: add companion auth on duo_workflows_default_image_registry
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Problem to solve
duo_workflows_default_image_registry is hostname-only with no companion auth field. The GitLab runner pulls the central image with the calling project's CI_JOB_TOKEN. The registry's /jwt/auth?service=container_registry endpoint rejects this token under enforce_ci_inbound_job_token_scope_enabled=true, which is the default on hardened self-managed instances.
Result: any project that does not pre-allowlist the central image's host project hits a 401 on image pull, before a single line of the flow runs.
Reproducer
On a self-managed instance with inbound CI_JOB_TOKEN scope enforced:
- Set Admin > GitLab Duo > Image registry to a host project's registry hostname.
- Run a DAP flow from any project that is not in the host's
ci_inbound_job_token_scope_allowlist. - Observe HTTP 401 from
/jwt/auth?service=container_registryin the runner job log.
Field trace: cs.gitlabdemo.cloud, 2026-04-29, runner job 10623081.
Customer Impact
- A US financial institution (Ultimate, Self-Managed): worked around via Artifactory path-rewrite, fragile pattern.
- A European insurance customer: blocked, the hostname-only setting cannot point at their external Artifactory.
- A German financial-services customer: PoC blocked at the same step.
(SFDC links: see internal Slack thread.)
Why internal-visibility doesn't fix it
Empirical PAT-vs-CI_JOB_TOKEN test on cs.gitlabdemo.cloud (2026-04-29):
- PAT auth on
/jwt/auth: succeeds, returns JWT with pull action. - CI_JOB_TOKEN auth on
/jwt/auth: fails 401 even when the host project hascontainer_registry_access_level=enabledandvisibility=internal.
The "internal visibility bypasses inbound scope" guarantee in docs.gitlab.com/ci/jobs/ci_job_token/ applies only to the PAT auth path, not the runner's CI_JOB_TOKEN path.
Proposal
Ranked:
- Companion service-account / deploy-token field on the admin form. Stored encrypted, injected into the runner pull credentials when the image is pulled from the configured registry.
- Auto-allowlist the instance root group when
duo_workflows_default_image_registryis set, so every project's inbound scope already includes the host. - Document
DOCKER_AUTH_CONFIGinstance variable as the supported pattern, with explicit warning that consumers must rotate it manually.
Option 1 is the cleanest, option 2 has the best ergonomics, option 3 is the cheapest stopgap.
Acceptance Criteria
- Admin can configure pull credentials alongside the image registry hostname.
- Pulls succeed from any project on the instance without per-project allowlist edits.
- Documented in the existing Custom container registry docs page.
- Backwards compatible with existing 18.9+ deployments that already set the hostname.
Out of scope
- Full image path/tag overridability (covered by #594208).
- Virtual-registry support (covered by sibling issue under
~"group::package registry", link added once filed).
Other links/references
- Precursor: #584689 (closed)
- Implementation MR that omitted auth: !222281 (merged)
- CI_JOB_TOKEN scope docs: docs.gitlab.com/ci/jobs/ci_job_token/
- Field reference deployment: dap-image-hardened
- Initiating Slack thread:
#dap-controls-for-agents-and-flows