git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[BUG] git-credential-libsecret writes secret to stdout on store

From
LQLutz-Christian Quander <lcq@wateringcan.de>
Date
Apr 21, 2026, 11:03 UTC
Message-ID
<b7b6b94c-7e42-42a5-95e5-d44a54d6da0f@wateringcan.de>
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-<helper> 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
Next: Mantas Mikulėnas
Message 1 of 4 in “[BUG] git-credential-libsecret writes secret to stdout on store”
  1. Lutz-Christian QuanderApr 21, 2026
  2. Mantas MikulėnasApr 21, 2026
  3. Mantas MikulėnasApr 22, 2026
  4. Phillip WoodApr 22, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.