Documentation
¶
Overview ¶
Command wardyn-git-helper is the git credential helper for Wardyn-governed agent sandboxes. It implements the git-credential protocol (get/store/erase) as a thin broker client: on "get" it resolves a grant for the requested host — a GitHub installation token (github.com / *.github.com via WARDYN_GITHUB_GRANT_ID, username x-access-token), OR — for a GitHub host with no App grant configured, or any other host — a stored Personal Access Token matched by host (Azure DevOps / GitLab / a plain GitHub PAT via WARDYN_GIT_PAT_GRANTS, username resolved by the broker) — calls the proxy's local mint route, then prints the credentials to stdout for git to consume. The credential never touches disk or argv — stdout only, consumed ephemerally.
The helper is configured GLOBALLY (git config --system credential.helper), so for any host it does NOT broker (no matching grant) it emits NOTHING and exits 0, letting git fall through to its normal behavior. It must never break unrelated git auth.
Invariants:
- The brokered credential is never persisted to disk, env, or args — it is written to stdout only, for git to consume ephemerally (security invariant 1). It is never logged.
- Requests to the proxy use a direct transport (Proxy: nil) — the proxy URL is a known on-segment address, not subject to HTTP_PROXY env.
- If no grant is configured for the host (github.com without WARDYN_GITHUB_GRANT_ID AND absent from WARDYN_GIT_PAT_GRANTS, or any other host absent from WARDYN_GIT_PAT_GRANTS) the helper exits 0 with no output so git falls through to its normal prompting. This is intentional: runs without grants must not be blocked.
Caller authentication (in-sandbox token-exfiltration hardening):
The sandbox shares one kernel and one uid (agent). Without a check, ANY
in-sandbox process that speaks git's credential protocol — a sub-process, a
snooping `wardyn attach` shell, a careless tool — could invoke this helper
and have a LIVE GitHub token streamed to it on stdout. To raise the bar, the
helper requires the caller to PRESENT a per-run secret before it emits a
token:
- The canonical per-run secret lives in a FILE that is owned by the agent
uid and mode 0400 (provisioned by agent-run; see deploy/images/*/agent-run).
Its path is passed to the helper via the git credential.helper config
(`--secret-file <path>`). The helper reads it as the EXPECTED value.
- The caller PRESENTS the secret via the WARDYN_GIT_HELPER_SECRET
environment variable. agent-run exports it for its descendants (the agent
process and its git invocations), so it is process-scoped — it is NOT in
the container-wide sandbox env, so a separate attach exec or any
non-descendant process does not inherit it.
- Before minting, the helper constant-time-compares the presented value
against the expected file content. A mismatch (or no presented value)
fails CLOSED: no token is emitted, but git is NOT errored (we return
success with empty output, exactly like the no-grant case) so the helper
never breaks git itself.
- If no secret file is provisioned for the run (the file is absent/empty —
e.g. an interactive run that never goes through agent-run, or a
deployment that has not wired the gate), the helper preserves the legacy
behaviour and mints, so legitimate git is never blocked.
RESIDUAL (documented honestly): this raises the bar from "any in-sandbox
process" to "code executing AS the agent uid". A process running as the agent
user can still read the 0400 secret file (or read WARDYN_GIT_HELPER_SECRET
from a descendant's /proc/<pid>/environ, or simply be a descendant of
agent-run) and thereby obtain the token. Closing that gap requires
per-process credentials (e.g. SPIFFE-attested mint, or a per-invocation
nonce), which is future work. Interactive runs are likewise not gated by this
mechanism (no agent-run to provision the secret) and rely on the human
attach principal being authorised.
The secret is never logged.
Usage (set by git credential helper config):
git config credential.helper \ '/usr/local/bin/wardyn-git-helper --secret-file /home/agent/.wardyn/git-helper.secret'
git invokes: wardyn-git-helper [--secret-file <path>] get|store|erase