coder / coder/xum

🤖 Docker credential sharing is not portable across non-root users and host-specific Git helpers

Open
#3,819 2 comments 0 reactions 0 assignees View on GitHub
approved investigation
Dominant language
TypeScript
Stars
2k
Forks
134
Avg merge
14h 57m
Merged PRs (30d)
307

Description

## Summary

Docker workspace credential sharing currently assumes that the host global Git configuration can be copied into a container unchanged. That assumption fails in two related ways:

1. `DockerRuntime.setupCredentials()` copies the host config to `:/root/.gitconfig`, while Docker workspace images can run as a non-root default user.
2. Even when the file is written to the effective container user’s home directory, a host Git config can reference credential helpers, includes, commands, sockets, files, keyrings, or network configuration that are not available inside the container.

The first is a concrete bug. The second is a broader design question about the supported contract for Git and SSH credential forwarding in Docker and Dev Container workspaces.

## Current behavior

- `src/node/runtime/DockerRuntime.ts` calls `setupCredentials()` both after fresh provisioning and when `postCreateSetup()` reuses an already-valid forked container.
- `setupCredentials()` currently uses `docker cp` to copy the host config to `/root/.gitconfig`, then runs `gh auth setup-git` when `GH_TOKEN` is available.
- Fresh provisioning detects the container uid/gid/home before credential setup, but the reuse/fork branch invokes credential setup before `containerHome` has been cached.
- `DevcontainerRuntime.setupCredentials()` already uses a more user-correct transport: it reads the host config and writes it through `cat > "$HOME/.gitconfig"` as the configured container user. It also forwards a broader set of Coder-related environment/mount inputs.

Relevant prior work to re-evaluate:

- #1458 — initial credential sharing
- #1506 — reuse lifecycle
- #1587 — non-root user support
- #2980 — uid/gid hardening

## Example: Coder workspace credentials

In some Coder-backed environments, the host Git config contains a credential helper that depends on Coder-specific executables, agent state, environment variables, or mounts. Copying that config into a generic Docker workspace does not make those dependencies available. The helper may fail, block a supported fallback, or point Git at paths that do not exist in the container.

This is not unique to Coder. Similar examples include OS keychain helpers, Git Credential Manager, libsecret, password-manager helpers, enterprise helpers, custom shell helpers, and helpers backed by local files or sockets.

## Why copying a global Git config is not a portable credential transport

Potentially non-portable configuration includes:

- `credential.helper`, including multiple helpers and URL-scoped helpers;
- shell or absolute-path helpers;
- helper state files, daemons, sockets, and browser/OAuth flows;
- `[include]` and `[includeIf]` files, including conditional configuration activated by the container’s Git directory, branch, or remote URL;
- `core.sshCommand`, `IdentityFile`, `ProxyCommand`, SSH certificate/known-host paths, and other SSH-wrapper configuration;
- custom CA bundles, proxies, client certificates, VPN/DNS assumptions, or URL rewrites;
- signing configuration (`gpg.program`, SSH signing) and Git external tools/filters/hooks that point to host-only executables.

Copying the file can therefore produce a configuration that is syntactically valid but operationally broken. Blind filtering is also non-trivial: Git config supports quoting, repeated values, URL subsections, and includes, and removing all `credential.*` settings could discard valid provider-specific username or policy configuration.

## Lifecycle impact

The issue should consider at least these paths:

| Lifecycle | Risk |
| --- | --- |
| Fresh provisioning | Non-root home/ownership and helper portability |
| Reuse/fork | Credential setup can run before user-home metadata is cached |
| Runtime recreated for an existing workspace | In-memory home metadata is absent |
| Restart/upgrade of an image | Helper binaries, daemons, and sockets may no longer exist |
| Private submodules during provisioning | `.mux/init` runs too late to repair credentials needed for bundle sync, checkout, or submodule materialization |

## Existing supported mechanisms vs. opaque host helpers

Mux already has mechanisms with an explicit transport contract:

- `GH_TOKEN` followed by `gh auth setup-git`;
- SSH agent forwarding;
- for Dev Containers, selected `GIT_ASKPASS`/`CODER_*` environment forwarding and an optional `/.coder-agent` mount.

These should be considered separately from copying arbitrary host configuration. A copied Git config says which helper Git should invoke; it does not provide the helper’s executable, dependencies, session, or secrets.

## `.mux/init` as an escape hatch

A repository can provide an executable `.mux/init` script. In Docker and Dev Container runtimes it runs in the workspace directory as the effective container user and receives workspace/project secrets via environment variables.

This can help teams with known, container-native setups, for example:

- run `gh auth setup-git` against an intentionally supplied `GH_TOKEN`;
- configure a credential helper that is installed in the image;
- remove or replace a known incompatible helper;
- configure an enterprise provider from project secrets;
- verify or adjust a forwarded SSH agent.

However, it is not a complete solution:

- it is non-interactive (`GIT_TERMINAL_PROMPT=0`, no TTY);
- it cannot reconstruct an opaque host helper or host keyring/session;
- it runs after Docker provisioning, checkout, and submodule materialization, so it cannot repair credentials needed before those stages;
- it is repository-controlled and only appropriate for trusted projects.

## Questions for design discussion

1. What is the supported credential-portability contract for Docker versus Dev Container workspaces?
2. Should Mux distinguish between portable Git preferences, explicitly supported credential transports, and host-specific helper configuration that is intentionally not imported?
3. Should Docker adopt the DevcontainerRuntime transport pattern (`readHostGitconfig()` + `this.exec('cat > "$HOME/.gitconfig"')`) as the minimal fix for the non-root/reuse bug, while treating helper portability as a separate problem?
4. Should the default behavior be full config copy, selective/sanitized copy, only explicitly supported transports, or opt-in integrations for known helpers/providers?
5. If `.mux/init` is the intended extensibility point, do we need an earlier lifecycle hook for credentials required during clone/submodule setup?
6. Would a redacted credential diagnostic be useful (effective user/home, configured helper origins, active includes, `gh`/`GH_TOKEN`/SSH-agent/Coder-agent availability, and lifecycle phase of failure)?

## Suggested acceptance criteria for an initial narrow fix

- Docker writes any imported Git config as the actual default container user, using `$HOME`, not hardcoded `/root`.
- The reuse/fork path works when `containerHome` has not yet been cached.
- Raw config bytes are streamed via stdin rather than interpolated into a shell command.
- Existing `GH_TOKEN` and SSH-agent behavior remains intact.
- Tests cover root and non-root users, fresh provisioning and reused/forked containers, including the uncached-home reuse case.
- The implementation and documentation explicitly state that this alone does **not** make host-specific credential helpers portable.

---

_Generated with `mux` • Model: `openai:gpt-5.6-terra` • Thinking: `high` • Cost: `$0.15`_

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in src/node/runtime/DockerRuntime.ts with setupCredentials(), its fresh-provisioning path, and the reuse/fork path where containerHome may be uncached. Compare DevcontainerRuntime.setupCredentials(), readHostGitconfig(), and the existing GH_TOKEN and SSH-agent behavior. Done means root and non-root fresh and reused containers write config through the effective user's HOME, stream raw bytes safely, preserve supported transports, and include tests plus documentation of helper portability limits.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, git, typescript
Domain
authentication, devops, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.