Normalise DAP origin URL to drop the CI job token
What this fixes
With dap_git_credential_helper enabled, an agent's git push fails with
403 You are not allowed to push code to this project.
Reproduced on gitlab.com in gitlab-org/duo-workflow/test-project-playground/aherczeg:
job 16397592800
(convert_to_gl_ci/v1, private group runner) and
job 16533271763
(Developer flow, shared SaaS runner).
Root cause
Both GitLab.com runner fleets set post_get_sources_script to run, inside get_sources:
git remote set-url origin "${CI_REPOSITORY_URL}" || echo 'Not a git repository; skipping'CI_REPOSITORY_URL always embeds the job token — Ci::Build#repo_url hardcodes
"#{::Gitlab::Auth::CI_JOB_USER}:#{token}@". A credential in the remote URL takes
precedence over every credential helper, so the DAP OAuth helper installed by !243872 (merged) was
never consulted. The push authenticated as gitlab-ci-token, which is denied
:build_push_code by rule { ~push_repository_for_job_token_allowed } — hence the 403.
Why FF_GIT_URLS_WITHOUT_TOKENS doesn't cover this
The flag works as intended. helpers/url/gitauth.go — "When authenticated is true, the
token from config is injected into URLs; when false, URLs are produced without
credentials" — and common/build.go passes !IsFeatureFlagOn(GitURLsWithoutTokens). So
every URL the runner constructs is credential-free.
CI_REPOSITORY_URL is not one of those URLs: it is a Rails-generated job variable, and no
runner code references it at all (no hits in common/build.go, shells/abstract.go,
functions/concrete/run/stages/get_sources.go). The flag cannot scrub it.
Why this only appeared after !243872 (merged)
oauth_remote_commands returns [] when the flag is on. With the flag off it runs in
step_script — after get_sources, so after the hook — and overwrote the job-token origin
as a side effect. That accidental ordering is why the sed path worked for months.
This wasn't caught pre-merge because GDK carries no post_get_sources_script: the
verification recorded on #593467 confirmed a credential-free origin on GDK, where the hook
does not exist.
The change
remote_url_commands replaces oauth_remote_commands at the call site. With the flag on it
rewrites origin to a credential-free URL and lets the helper supply the OAuth token.
Restoring oauth_remote_commands was rejected deliberately: it puts the token back into
.git/config, which is the vulnerability #2778 exists to remove. git -C pins the repo
directory because setup_script runs in the same shell and may cd away.
Flag-off behaviour is unchanged.
Testing
- New real-
gitexample seeds a repo whoseorigincarries a job token — as the hook leaves it — runs the actual emitted command, and asserts the URL comes back credential-free. Verified failing without the fix (the job script does not rewrite origin) and passing with it. - A variables-hash assertion could not catch this class of bug: the defect was that no
rewrite ran at all. The pre-existing
expect(script).not_to include('git remote set-url origin')was itself the assertion encoding the wrong assumption; it now asserts a credential-free rewrite plus the absence ofoauth:and of the token. - The
'a workload with a full coding environment'shared example also asserts the credential-free rewrite now. Its oldnot_to includeonly passed because-Csplit the substring. start_workflow_service_spec.rbandresume_workflow_service_spec.rbon currentmaster: 188 examples, 0 failures. RuboCop clean.
How to set up and validate locally
<
GDK runners carry no post_get_sources_script, so the bug only reproduces once you add the hook the GitLab.com runner fleets use.
- Use a GDK where Duo Agent Platform flows run on a local runner, and enable the flag in
gdk rails console:Feature.enable(:dap_git_credential_helper) - Add the GitLab.com hook to the runner's
[[runners]]section (<gdk-root>/gitlab-runner-config.tomlfor the GDK-managed runner), thengdk restart runner:post_get_sources_script = "git remote set-url origin \"${CI_REPOSITORY_URL}\" || echo 'Not a git repository; skipping'" - On
master, open an issue in any project and start the Developer flow with Generate MR with Duo. In the flow's CI job log, the firstgit push -u origin ...fails with403 You are not allowed to push code to this project. The agent may retry against an explicit URL; the failingoriginpush is the bug. - Check out this branch, run
gdk restart rails-web rails-background-jobs, and start the flow again on a new issue. The job log now runsgit -C "$CI_PROJECT_DIR" remote set-url origin ...before the flow starts,git push -u origin ...succeeds on the first try, and the flow opens an MR. - Optional:
Feature.disable(:dap_git_credential_helper)and repeat. The job log shows the unchangedoauth:origin rewrite and the push still succeeds.
</
Rollout note
This affects every DAP job on GitLab.com once dap_git_credential_helper rolls out, not
just one project — relevant to #612645. Worth confirming the hook's provenance with the
Runner/infra owners so nothing else depends on "origin is credential-free when
FF_GIT_URLS_WITHOUT_TOKENS=true".
No merge-order constraint with ai-assist:
ai-assist!6713
was closed in favour of
ai-assist!6962,
which keeps git_setup and sets a credential-free origin itself, so either can land first.
Related to #593467 · follows up !243872 (merged)