From: Lutz-Christian Quander Date: Tue, 21 Apr 2026 11:03:40 GMT Subject: [BUG] git-credential-libsecret writes secret to stdout on store Message-ID: Hello, I believe I've hit a bug in contrib/credential/libsecret that leaks the secret to stdout on `store` (and, by inspection of the same code path, `erase`). It reproduces on the current 2.53.0 Arch package and the relevant code path is unchanged on master as of today. Summary ------- `git-credential-libsecret store` unconditionally echoes the `username` and `password` from its parsed input back to stdout after the store operation completes. This exposes the secret to whatever consumes the helper's stdout -- terminal scrollback, shell pipelines, CI logs, or any parent-process capture -- whenever a caller feeds credentials in via pipe (the documented non-interactive seeding pattern). `get` should write credentials to stdout. `store` and `erase` should not. Affected version ---------------- - Reproduced on git 2.53.0-1.1 (Arch Linux community/git). - Source inspected from the installed package   (/usr/share/git/credential/libsecret/git-credential-libsecret.c)   and confirmed against   contrib/credential/libsecret/git-credential-libsecret.c on   origin/master. Reproduction ------------     $ printf "protocol=https\nhost=example.invalid\nusername=alice\npassword=SECRET123\n\n" | /usr/lib/git-core/git-credential-libsecret store     username=alice     password=SECRET123     $ echo $?     0 Only `username=` and `password=` are echoed (not `protocol=` / `host=`), matching the four fields `credential_write()` emits. Expected behaviour ------------------ `store` produces no stdout on success. Exit code unchanged. Root cause ---------- In main(), the write is unconditional after the op dispatch:     ret = credential_read(&cred);     if (ret)             goto out;     /* perform credential operation */     ret = (*try_op->op)(&cred);     credential_write(&cred);   /* unconditional for get/store/erase */ `credential_write()` emits `username`, `password`, `password_expiry_utc`, and `oauth_refresh_token` to stdout. That is correct for `get` (returning the looked-up credential) and incorrect for `store` / `erase`, where the struct still holds the just-read stdin input. Comparable helpers in the same tree should probably be audited for the same pattern; at least `credential-store` historically only writes on `get`. Proposed fix ------------ Minimal change -- guard the write:         ret = credential_read(&cred);         if (ret)             goto out;         /* perform credential operation */         ret = (*try_op->op)(&cred);     -    credential_write(&cred);     +    if (!strcmp(argv[1], "get"))     +        credential_write(&cred); A cleaner refactor would add a `writes_output` flag or a `write_result` callback to `struct credential_operation` so each op declares its own output contract, but the guard above is the smallest safe change. Happy to turn it into a proper patch with sign-off if that's preferred. Security impact --------------- The documented pattern for seeding credentials non-interactively is:     printf "protocol=...\nhost=...\nusername=...\npassword=...\n\n" | git-credential- store Running this at a terminal prints the secret into scrollback. Running it in a shell script whose stdout goes to a log file persists the secret in that log. Running it in CI captures the secret in the pipeline artefact. Every real-world use of the documented pattern is affected. Severity is moderate: the leak requires the user to run a legitimate command -- no attacker-controlled input path -- but the leak happens on the "correct" documented workflow, silently, with exit code 0. Workaround ---------- Redirect stdout explicitly:     printf "...\n" | /usr/lib/git-core/git-credential-libsecret store >/dev/null Environment ----------- - Distribution: CachyOS (Arch Linux derivative) - Kernel: Linux 7.0.0-1-cachyos - git: 2.53.0-1.1 - Shell: bash (invoked from a fish login shell) Happy to coordinate disclosure if preferred, but the workaround is trivial, the patch is one line, and the bug affects any scripted credential seeding -- so there's little to gain from embargo. Thanks, Lutz-Christian Quander