Store the token
Settings → Registries and repos → Repository credentials → Add credential.
Over the API:
credentials:manage is required, and it is not grantable to an API key —
this call needs a human session. A credential that could store or rotate a
repository token could decide what your scans clone.
A repository token is not a registry token
github.com serves repositories; ghcr.io serves container images. They are
different hosts, the tokens are different, and they are rotated on different
schedules. Storing a ghcr.io token under github.com produces a scan that
fails to clone, and the reason is not obvious from the error.
That is why the two stores are separate: /registry-credentials and
/git-credentials. A credential of the wrong kind is refused, not
converted — the scanner will not present a git token to a registry, or a
registry token to a git host.
Check it before a scan depends on it
Press Check access on the credential and name a repository, or:git ls-remote sends, and the request that authenticates — so your source code
never reaches the API host.
The answer is in the body, never in the status code. A refused token is a
successful request. Branch on access.ok.
access.rejected separates “the host said no” from “we could not ask”. Only a
rejection changes the credential’s stored status; an unreachable host leaves it
alone, because marking a working credential invalid on a network blip is how
scanning stops for a reason nobody can find.
Scan
Nothing special. The credential is selected by the repository’s host:ref is a branch, tag or commit. It is a property of the scan, not of the
asset: a repository scanned on main and on release/1.0 is one protected
thing with two scans.
What happens to the token
It is encrypted at rest with the deployment’s keyring, decrypted for the duration of one clone, and:- never placed in the clone URL. A URL with a userinfo section is copied
into the checkout’s own
.git/configand echoed in every git error message. - never put in the process environment. The environment is inherited by every subprocess of the worker, including credential helpers that a repository being scanned can configure.
- never passed as a command-line argument.
argvis world-readable on a shared worker. - never sent to a redirect target on another host. Go’s own redirect rule
keeps an
Authorizationheader across a redirect to a subdomain, so a302fromexample.comtoattacker.example.comwould carry the token. TRUSTIVAN drops it on any host change, exact match only. - never logged. Errors name the credential’s id — which identifies a row, not a value — so a failed clone still tells you which token to rotate.
git, the token is written to a 0600
credential-store file whose path is on the command line and whose contents
are not, and the inherited helper list is cleared first so no helper on the
worker can answer before it.
When a credential goes missing
A scan that was told to use a credential and cannot resolve it — deleted, disabled, or the wrong kind — fails. It does not fall back to an anonymous clone. An anonymous clone of a name that also exists publicly scans somebody else’s code and reports its findings as yours, which is a worse outcome than a failed scan. A repository with no stored credential for its host is cloned anonymously, which is correct: most repositories scanned are public.Rotating
Rotate in place, from the panel orPUT /git-credentials/{id}/secret. The host
cannot change during a rotation — that would be a new credential wearing an old
one’s history. The status returns to unverified until the next check or scan
proves the new token.
There is no endpoint that reads a stored token back, at any permission. A lost
token is rotated, not recovered.
