#605·gitingest

Access token is written to logs and to RuntimeError messages when cloning with a token (0.3.1)

Author: theflysurferCreated Sep 8, 2026Updated Sep 8, 2026

Summary

When gitingest clones a private repository with a token, the credential reaches two outputs that callers commonly capture: the INFO log line, and the message of the RuntimeError raised on clone failure. The redaction that exists targets the URL, while the credential lives in the -c http.<url>.extraheader=Authorization: Basic <base64> argument that precedes it.

Measured on 0.3.1, Python 3.13, Windows.

Where it comes from

gitingest/clone.py builds the command as:

python
clone_cmd = ["git"]
if token and is_github_host(url):
    clone_cmd += ["-c", create_git_auth_header(token, url=url)]
clone_cmd += ["clone", "--single-branch", "--no-checkout", "--depth=1"]
...
clone_cmd += [url, local_path]

logger.info("Executing git clone command",
            extra={"command": " ".join([*clone_cmd[:-1], "<url>", local_path])})

Two consequences:

  1. The redaction misses its target. clone_cmd[:-1] drops only the last element (local_path), so the auth header at index 2 is printed verbatim, and the URL — the thing <url> is meant to stand for — is still inside the slice and printed too. The line reads as redacted while carrying the credential.

  2. RuntimeError carries the same string. run_command in gitingest/utils/git_utils.py raises:

    python
    msg = f"Command failed: {' '.join(args)}\nError: {stderr.decode().strip()}"
    raise RuntimeError(msg)

    args is the full clone_cmd, header included. Any caller that stores or reports str(exc) — an API response, a job record, an agent transcript — persists the credential. Silencing the logger does not cover this path.

The header value is base64 of x-oauth-basic:<token>, so it is reversible, not a hash.

Reproduce

python
import asyncio, gitingest
asyncio.run(gitingest.ingest_async("https://github.com/<owner>/<private-repo>", token="<a PAT>"))

with logging at INFO. The Executing git clone command line contains the header. For path 2, point at a repository the token cannot reach: the RuntimeError message contains it as well.

Suggested fix

Redact by value, at both sites, rather than by position:

  • build the log/error string from a sanitised copy of clone_cmd where any element matching extraheader=Authorization: (and any https://user:pass@ form) is replaced;
  • apply the same sanitiser in run_command before interpolating args into msg, so the exception path is covered independently of the logging path.

Doing it in run_command alone would cover both, since that is where the argument list becomes text a caller can keep.

Impact

A token with repo scope can leak into logs and error stores of any downstream consumer, including ones that never touch gitingest internals. We hit it in a downstream project and had to add both a logger mute and an output scrubber; the second was only found because a real run surfaced the exception path after the logger was already silenced.

Happy to open a PR if the shape above matches what you'd want.