Git development
 help / color / mirror / Atom feed
* [BUG] git-credential-libsecret writes secret to stdout on store
@ 2026-04-21 11:03 Lutz-Christian Quander
  2026-04-21 11:37 ` Mantas Mikulėnas
  0 siblings, 1 reply; 4+ messages in thread
From: Lutz-Christian Quander @ 2026-04-21 11:03 UTC (permalink / raw)
  To: git; +Cc: grawity

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


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-04-22 13:13 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-21 11:03 [BUG] git-credential-libsecret writes secret to stdout on store Lutz-Christian Quander
2026-04-21 11:37 ` Mantas Mikulėnas
     [not found]   ` <60cf5f7c-9ccb-4dfe-82e4-9b6e54b3c2c0@wateringcan.de>
2026-04-22  5:49     ` Mantas Mikulėnas
2026-04-22 13:13       ` Phillip Wood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox