Repository navigation
fix: keep git provider credentials out of cloned repositories - #27
Merged
Merged
Conversation
Что: - GitHub, GitLab, Gitea и Bitbucket клонируют по URL без учетных данных; токен передается только через `git -c http.<origin>/.extraHeader=Authorization: Basic ...` и не записывается в `.git/config` и конфиги сабмодулей. - Для custom Git HTTPS-URL с `user:password@` учетные данные убираются из сохраняемого remote тем же способом. - Точный деплой compose (`expectedRevision`) делает fetch/checkout внутри той же команды клонирования с теми же временными учетными данными; после этого выполняется только `rev-parse`. - SSH-ключ custom Git пишется во временный файл с уникальным именем и правами 0600 (`umask 077`) вместо общего `/tmp/id_rsa` и удаляется после клонирования, в том числе при ошибке. Зачем: - Токен провайдера оставался в `<code>/.git/config`, где его могли прочитать шаги сборки и bind mount каталога сервиса; использование провайдера не должно давать доступ к его токенам. Риски: - Авторизация теперь отправляется сразу (preemptive Basic) только на origin провайдера; сабмодули на других хостах токен не получают, а сабмодули на том же хосте с абсолютным URL теперь тоже авторизуются. - Custom Git серверы, принимающие только NTLM/Negotiate, не смогут использовать `user:password@` в URL. - Уже существующие checkout'ы сохраняют старый токен до следующего деплоя, который пересоздает каталог. Проверки: - Команды и результаты: `CI=true bunx vitest run --config __test__/vitest.config.ts` по новым и измененным тестам (clone-credentials-persistence, clone-command-boundary, github-clone-host, compose-env-upsert) - 45/45; по папкам git-provider, env, compose, deployment, placement, volume-backups, security/patch-path, deploy/application.command - 79 файлов, 789 passed, 1 skipped; на исходниках из origin/AgentHits-Dev все 18 новых и измененных тестов падают; `bun run typecheck` - exit 0; `bunx biome check <измененные файлы>` - 0 ошибок. - Ограничения: реальное клонирование с git-сервером (включая сабмодули) не запускалось; передача `git -c` в сабмодули и `http.<url>.extraHeader` опираются на документированное поведение git. What: - GitHub, GitLab, Gitea and Bitbucket clone from a URL without credentials; the token is passed only through `git -c http.<origin>/.extraHeader=Authorization: Basic ...` and is not written to `.git/config` or submodule configs. - Custom Git HTTPS URLs with `user:password@` get the same treatment, so the stored remote has no credentials. - Exact compose deployments (`expectedRevision`) fetch and check out the revision inside the same clone command with the same per-invocation credentials; only `rev-parse` runs afterwards. - The custom Git SSH key is written to a uniquely named temp file created with 0600 (`umask 077`) instead of the shared `/tmp/id_rsa`, and is removed after the clone, also on failure. Why: - The provider token stayed in `<code>/.git/config`, readable by build steps and bind mounts of the service directory; using a provider must not give access to its tokens. Risks: - Authorization is now sent preemptively (Basic) and only to the provider origin; submodules on other hosts do not receive the token, while same-host submodules with absolute URLs now get it too. - Custom Git servers that accept only NTLM/Negotiate can no longer use `user:password@` in the URL. - Existing checkouts keep the old token until the next deployment recreates the directory. Checks: - Commands and results: `CI=true bunx vitest run --config __test__/vitest.config.ts` on the new and changed tests (clone-credentials-persistence, clone-command-boundary, github-clone-host, compose-env-upsert) - 45/45; on the git-provider, env, compose, deployment, placement, volume-backups, security/patch-path and deploy/application.command folders - 79 files, 789 passed, 1 skipped; with the sources from origin/AgentHits-Dev all 18 new and changed tests fail; `bun run typecheck` - exit 0; `bunx biome check <changed files>` - 0 errors. - Limitations: no real clone against a Git server (including submodules) was run; `git -c` propagation to submodules and `http.<url>.extraHeader` rely on documented Git behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this PR about?
Git provider tokens were embedded in the clone URL (
oauth2:<token>@,x-bitbucket-api-token-auth:<token>@,user:app-password@), sogit clonestored them in<code>/.git/configand in submodule configs. Build steps and bind mounts of the service directory could read them.buildGitCloneCommandnow clones from a URL without credentials and passes them only asgit -c http.<provider origin>/.extraHeader=Authorization: Basic ....git -cvalues are not persisted, they reach submodule clones, and the origin scope keeps the header away from submodules hosted elsewhere.user:password@are split the same way, so the stored remote has no credentials.expectedRevision) used a separategit fetch origin <sha>that relied on the stored credentials. The fetch andcheckout --detachnow run inside the clone command (in a subshell, so a failure still fails underset -e) with the same per-invocation credentials. Afterwards onlyrev-parse HEADruns./tmp/id_rsa. It is written to a uniquely named file created withumask 077and removed after the clone, including on failure.ExecError,redactSensitiveTextalready masksAuthorization: Basic ....Checks
CI=true bunx vitest run --config __test__/vitest.config.tson the new and changed tests (git-provider/clone-credentials-persistence,git-provider/clone-command-boundary,git-provider/github-clone-host,env/compose-env-upsert): 45/45 passed.git-provider,env,compose,deployment,placement,volume-backups,security/patch-pathanddeploy/application.commandtests: 79 files, 789 passed, 1 skipped.origin/AgentHits-Dev, all 18 new and changed tests fail.bun run typecheck: exit 0.bunx biome check <changed files>: 0 errors.git -cto submodules andhttp.<url>.extraHeadermatching rely on documented Git behaviour.Rollout notes
.git/configuntil the next deployment recreates the code directory. Redeploy the services that use providers. Rotate long-lived Bitbucket API tokens or app passwords if those directories were exposed.user:password@in the URL.🤖 Generated with Claude Code