* Push Certificates: Privacy Concerns Regarding the "pushee" Header
@ 2026-02-15 21:58 Lorenz Leutgeb
2026-02-17 19:42 ` Junio C Hamano
0 siblings, 1 reply; 5+ messages in thread
From: Lorenz Leutgeb @ 2026-02-15 21:58 UTC (permalink / raw)
To: git
Dear recipients,
I am working on an application built on top of Git, which wants to
ingest push certificates in order to keep (something similar to) a
transparency log (see
<https://people.kernel.org/monsieuricon/signed-git-pushes>).
For reasons that I will only go into detail upon request, in the context
of the application, logical "pushes to the network" are split into two
parts: The *first part* is always local (from one repository in one
directory on the filesystem, to another). Bundled with the application
is a daemon. The daemon process then takes over the *second part* of
the push, for further propagation of over the network in a peer-to-peer
manner. However, one desires the *first* part already to be certified.
This would result in push certificates of the following form (hashes
abbreviated, signature omitted):
certificate version 0.1
pusher SHA256:xX6bp…T0 1771188983 +0100
pushee /home/lorenz/.example/storage/foo
nonce 1771188983-345389c
0000000 ccae4e0 refs/heads/main
As you can see, this push certificate leaks a path on the application
users' filesystem. Here it is quite obviously my home directory, but
actually the "storage path", is user-configurable at the application
level, and considered private.
I do realize that the leak is in part due to the weird application
architecture. Who would have guessed that I want to certify a push *on
my own filesystem*? I am convinced the main motivation for this feature
were pushes (directly) over the network. However, I also believe that
it could be of more general interest to allow the Git user to control
which/whether the pushee header is emitted.
Handling of the pushee header was introduced to `send-pack.c` in
9be89160e7382a88e56a02bcf38f4694dd6542d6, over 11 years ago, and was not
touched since. Remarking on the current implementation, I do realize
that `transport_anonymize_url` is used to sanitize, in order not to leak
usernames, passwords, etc., which is appreciated. However, file paths
are not removed. This makes a great deal of sense as the same function
is used in other places where indeed the path on the filesystem is
expected. I think the current implementation is sensible. However,
allowing the Git user to ultimately control which URL is used allows for
the greatest flexibility.
Note that the receiving end might inspect the push certificate in their
`pre-receive` handler, and reject to receive if the pushee is malformed.
I would like to provide a patch that adds an option `git send-pack`,
which allows the Git user to specify the pushee or indicate that the
header should not be emitted. My proposal is as follows. Because of
the connection to signed pushes, and therefore to the the already
existing `--[no-]-signed=…` option, I would add
`--signed-pushee=<string>` as well as `--no-signed-pushee`.
One edge case that I would like to get clarification on is how the empty
string should be handled. I would consider `--signed-pushee=""`
invalid, making it impossible to specify the empty string as pushee,
erroring out and potentially hinting at `--no-signed-pushee`.
Before I do so, I would like to ask for your feedback. Would you accept
such patch if implemented cleanly? What do you think, how should the
option be named and interpreted? Further, if this option is added to
`git send-pack`, one natural question is whether `git push` should also
"inherit" it, as is the case with `--signed`?
Kind regards,
Lorenz Leutgeb
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Push Certificates: Privacy Concerns Regarding the "pushee" Header
2026-02-15 21:58 Push Certificates: Privacy Concerns Regarding the "pushee" Header Lorenz Leutgeb
@ 2026-02-17 19:42 ` Junio C Hamano
2026-02-17 20:31 ` Lorenz Leutgeb
0 siblings, 1 reply; 5+ messages in thread
From: Junio C Hamano @ 2026-02-17 19:42 UTC (permalink / raw)
To: Lorenz Leutgeb; +Cc: git
Lorenz Leutgeb <lorenz.leutgeb@posteo.eu> writes:
> This would result in push certificates of the following form (hashes
> abbreviated, signature omitted):
>
> certificate version 0.1
> pusher SHA256:xX6bp…T0 1771188983 +0100
> pushee /home/lorenz/.example/storage/foo
> nonce 1771188983-345389c
>
> 0000000 ccae4e0 refs/heads/main
>
> As you can see, this push certificate leaks a path on the application
> users' filesystem. Here it is quite obviously my home directory, but
> actually the "storage path", is user-configurable at the application
> level, and considered private.
Stepping back a bit, the most important question is what the signer
is trying to certify, isn't it?
"At this point in time, I am pushing to update these refs because I
want these objects to be sitting at the tip of them in the
repository listed as pushee" is the statement the pusher is
certifying. The auditors can point at the certificate that the
hosting site having the object ccae4e0 is not because insiders of
the hosting site rewounded the tip to point at a stale object that
the pusher did not want to see at the ref, but with that signature,
this pusher expressed their desire to have it at the tip of the main
branch.
Making it possible to lift that certification and reusing it for a
push to different repository smells like opening a door for replay
attacks, doesn't it? The pusher did not say anything about updating
these refs in other repositories that are different from the pushee
repository.
I am not sure what the implication of optionally allowing the pusher
to sign a certificate that lacks "pushee" is. Would that be a
reasonable way to say "at this point in time, I want this object to
be at the tip of main branch in _any_ and _all_ clones of this
project anywhere in the world?" Is that the kind of statement the
pusher would want to make in the first place? Would such a
statement even make sense? When you are dealing with two related
repositories of a project (e.g., one may be the main development
repository, the other being the repository for the maintenance
track), I may fully stand behind putting this commit at the tip of
'main' on the development track, but that no way means I would be OK
to put the same commit at the tip of 'main' on the maintenance
track. Not naming "pushee" does not sound like it would fly well.
What implication does it have to allow the pusher to sign a
certificate that points at a "pushee" that is different from the
repository the signer directly pushed into? You give the history
together with a signed "I want the object ccae4e0 at the tip of the
main branch" statement to your middleman, and your middleman does
the actual update and forwards your certificate. I offhand do not
see a huge security problem in that arrangement. Anybody can check
the certificate and the resulting history and verify the chain of
hashes to the same degree as you would trust SHA-1 (or SHA-256) for
the object integrity and GPG (or whatever you used to sign the push
certificate) for the certificate integrity.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Push Certificates: Privacy Concerns Regarding the "pushee" Header
2026-02-17 19:42 ` Junio C Hamano
@ 2026-02-17 20:31 ` Lorenz Leutgeb
2026-02-18 6:00 ` Junio C Hamano
0 siblings, 1 reply; 5+ messages in thread
From: Lorenz Leutgeb @ 2026-02-17 20:31 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
You point out that a push certificate without a pushee is questionable
(to say the least) and opens the door to replay attacks. I understand
and agree. Thank you.
So let me cross out the option `--no-signed-pushee` and empty values for
`--signed-pushee=<url>` from my previous proposal, and let us continue
our discussion under the assumption that the pushee header is always set
to a URL(-like) string.
On 2026-02-17 11:42-0800, Junio C Hamano wrote:
> What implication does it have to allow the pusher to sign a
> certificate that points at a "pushee" that is different from the
> repository the signer directly pushed into? [...] I offhand do not
> see a huge security problem in that arrangement. Anybody can check
> the certificate and the resulting history and verify the chain of
> hashes to the same degree as you would trust SHA-1 (or SHA-256) for
> the object integrity and GPG (or whatever you used to sign the push
> certificate) for the certificate integrity.
Exactly. And this is very much the situation I am in. The middleman is
actually the daemon that comes bundled with the application, which wants
share the push certificate over the network. The pusher only has to
trust the middleman insofar as the middleman will actually forward the
push to its best ability. In my application, distribution of the
certificates and syncing of objects is the main purpose of the daemon.
But I digress...
Let me zoom in a bit more why I want to override the pushee: The way the
application works is by means of a remote helper. That is, the user
executes `git push example://foo ccae4e0:main`. This is makes sense in
the context of the application, as the user wants to carry out a logical
"push to the network", and "foo" is an identifier that also has a
well-defined meaning within the context of the application. As you
know, this will lead to execution of `git-remote-example`. The remote
helper is where the hand-off between "plain Git" and the application
happens. The remote helper knows that the push will be split in two
parts. It knows that it must carry out the first part immediately,
which is a push to `home/lorenz.example/storage/foo`. The repository at
that path is the local view of the repository `example://foo` on the
users filesystem. This local repository then gets updated in the
background by the daemon, and the daemon also notifies other peers on
the network about the push. This is the part where it must act as the
middleman.
Now, in the context of the application, the global identifier of the
repository across the network, and thus the pushee that I would like to
see, is `example://foo`. The path `home/lorenz.example/storage/foo` is
merely a local name for it, like a cached copy if you will.
Perhaps this even raises a question on how remote helpers interact with
signed pushes: If Git allows to register URLs via remote helpers, how
should remote helpers that do not have the "connect" capability control
the pushee URL, which they likely are concerned with?
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Push Certificates: Privacy Concerns Regarding the "pushee" Header
2026-02-17 20:31 ` Lorenz Leutgeb
@ 2026-02-18 6:00 ` Junio C Hamano
2026-02-18 9:56 ` Lorenz Leutgeb
0 siblings, 1 reply; 5+ messages in thread
From: Junio C Hamano @ 2026-02-18 6:00 UTC (permalink / raw)
To: Lorenz Leutgeb; +Cc: git
Lorenz Leutgeb <lorenz.leutgeb@posteo.eu> writes:
> Now, in the context of the application, the global identifier of the
> repository across the network, and thus the pushee that I would like to
> see, is `example://foo`. The path `home/lorenz.example/storage/foo` is
> merely a local name for it, like a cached copy if you will.
"The repo appears as X to me, but it is known as Y to others" is an
issue that already exists. "git pull" records from which repository
the changes were merged but it uses the repository from the point of
the view of the user who ran "git pull", for example. While one of
my public repositories are known as https://github.com/gitster/git",
the URL I use to push there may be "git@github.com:gitster/git.git",
so if they were recording push certificates, the latter would be the
pushee in them, but that is not a URL random people can normally use
to clone from.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Push Certificates: Privacy Concerns Regarding the "pushee" Header
2026-02-18 6:00 ` Junio C Hamano
@ 2026-02-18 9:56 ` Lorenz Leutgeb
0 siblings, 0 replies; 5+ messages in thread
From: Lorenz Leutgeb @ 2026-02-18 9:56 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
So, imagine a world where push certificates are more "end-to-end".
Think transparency log meets Git (see https://transparency.dev/ for more
context). Not only `git push --signed`, but also `git pull --signed`
exists. The remote being fetched from must provide evidence for the
puller verify the range being pulled, e.g. 0000000..ccae4e0. It does so
by sending along the blob that contains the corresponding push
certificates[^1].
In this bright future, where any puller is an auditor, you would run
into an issue if you want your repository to be pulled from different
places (pullees? fetchees?). However, there is a way out: Let the
pusher specify with which locations they are happy to have their push
end up at. In your case, since you are happy for others to pull from
https://github.com/gitster/git, you add to your push certificate:
certificate version 0.1
pusher SHA256:xX6bp…T0 1771188983 +0100
pushee https://example.com/repo.git
pushee git@github.com:gitster/git.git
nonce 1771188983-345389c
0000000 ccae4e0 refs/heads/main
I will also give you the counter arguments: Firstly, with repositories
that have many mirrors, the number of headers might become
problematically large. One remedy would be to allow configuration of
aliases on the side of the puller/verifier. One could accept all values
of `remote.<name>.url` as valid. One could introduce
`remote.<name>.alias`. Secondly, for repositories with exactly one
canonical location, no configuration would be necessary and the value
being used today would be correct.
---
[1]: In general, it would have to send multiple push certificates.
Firstly because there might be multiple refs being pulled over multiple
ranges. Secondly because that range might have been established over
multiple pushes.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-02-18 9:56 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-15 21:58 Push Certificates: Privacy Concerns Regarding the "pushee" Header Lorenz Leutgeb
2026-02-17 19:42 ` Junio C Hamano
2026-02-17 20:31 ` Lorenz Leutgeb
2026-02-18 6:00 ` Junio C Hamano
2026-02-18 9:56 ` Lorenz Leutgeb
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox