-
Notifications
You must be signed in to change notification settings - Fork 169
Sanitize sideband channel messages #1853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
7addb9c
2005853
919111f
ec48f1c
692d1a6
8b8244e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -523,6 +523,8 @@ include::config/sequencer.adoc[] | |
|
|
||
| include::config/showbranch.adoc[] | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Patrick Steinhardt wrote (reply to this): On Wed, Dec 17, 2025 at 02:23:40PM +0000, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> The preceding commit fixed the vulnerability whereas sideband messages
> (that are under the control of the remote server) could contain ANSI
> escape sequences that would be sent to the terminal verbatim.
>
> However, this fix may not be desirable under all circumstances, e.g.
> when remote servers deliberately add coloring to their messages to
> increase their urgency.
>
> To help with those use cases, give users a way to opt-out of the
> protections: `sideband.allowControlCharacters`.
I wonder whether this is a bit too broad. The only escape sequences that
I can see a valid use case for are color codes. So wouldn't it make
sense to discern color escape sequences from all other escape sequences
and allow users to only enable colors without also enabling all the
other, potentially more dangerous ones?
Edit: aha, you address this concern in the next commit. Nice :)
Patrick |
||
| include::config/sideband.adoc[] | ||
|
|
||
| include::config/sparse.adoc[] | ||
|
|
||
| include::config/splitindex.adoc[] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| sideband.allowControlCharacters:: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Patrick Steinhardt wrote (reply to this): On Wed, Dec 17, 2025 at 02:23:41PM +0000, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> The preceding two commits introduced special handling of the sideband
> channel to neutralize ANSI escape sequences before sending the payload
> to the terminal, and `sideband.allowControlCharacters` to override that
> behavior.
>
> However, as reported by brian m. carlson, some `pre-receive` hooks that
> are actively used in practice want to color their messages and therefore
> rely on the fact that Git passes them through to the terminal, even
> though they have no way to determine whether the receiving side can
> actually handle Escape sequences (think e.g. about the practice
> recommended by Git that third-party applications wishing to use Git
> functionality parse the output of Git commands).
>
> In contrast to other ANSI escape sequences, it is highly unlikely that
> coloring sequences can be essential tools in attack vectors that mislead
> Git users e.g. by hiding crucial information.
The worst that they can do is to set up both fore- and background color
to be the same so that text isn't visible. But I think that's an okay
tradeoff.
> Therefore we can have both: Continue to allow ANSI coloring sequences to
> be passed to the terminal by default, and neutralize all other ANSI
> Escape sequences.
Makes sense.
> diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt
> index 3fb5045cd7..e5b7383c7a 100644
> --- a/Documentation/config/sideband.txt
> +++ b/Documentation/config/sideband.txt
> @@ -1,5 +1,17 @@
> sideband.allowControlCharacters::
> By default, control characters that are delivered via the sideband
> - are masked, to prevent potentially unwanted ANSI escape sequences
> - from being sent to the terminal. Use this config setting to override
> - this behavior.
> + are masked, except ANSI color sequences. This prevents potentially
> + unwanted ANSI escape sequences from being sent to the terminal. Use
> + this config setting to override this behavior:
> ++
> +--
> + default::
> + color::
> + Allow ANSI color sequences, line feeds and horizontal tabs,
> + but mask all other control characters. This is the default.
> + false::
> + Mask all control characters other than line feeds and
> + horizontal tabs.
> + true::
> + Allow all control characters to be sent to the terminal.
> +--
Nit: I think that our modern doc style requires the values to use
backticks. E.g. "`default`::".
> diff --git a/sideband.c b/sideband.c
> index 997430f2ea..fb43008ab7 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -40,8 +45,26 @@ static int use_sideband_colors(void)
> if (use_sideband_colors_cached >= 0)
> return use_sideband_colors_cached;
>
> - git_config_get_bool("sideband.allowcontrolcharacters",
> - &allow_control_characters);
> + switch (git_config_get_maybe_bool("sideband.allowcontrolcharacters", &i)) {
> + case 0: /* Boolean value */
> + allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS :
> + ALLOW_NO_CONTROL_CHARACTERS;
> + break;
> + case -1: /* non-Boolean value */
> + if (git_config_get_string_tmp("sideband.allowcontrolcharacters",
> + &value))
> + ; /* huh? `get_maybe_bool()` returned -1 */
This case is something that shouldn't happen in practice because we know
that the config ought to exist. I guess it _could_ indicate a race
condition, even though it's extremely unlikely to ever happen. So I was
thinking about whether we want to `BUG()` here, but I guess just
ignoring this is fine, as well.
> @@ -70,9 +93,41 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
> list_config_item(list, prefix, keywords[i].keyword);
> }
>
> +static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n)
> +{
> + int i;
> +
> + /*
> + * Valid ANSI color sequences are of the form
> + *
> + * ESC [ [<n> [; <n>]*] m
> + *
> + * These are part of the Select Graphic Rendition sequences which
> + * contain more than just color sequences, for more details see
> + * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
> + */
> +
> + if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES ||
> + n < 3 || src[0] != '\x1b' || src[1] != '[')
> + return 0;
This would break in case `allow_control_characters` allows _all_ ANSI
sequences. But that doesn't matter right now because the function is
only called via `strbuf_add_sanitized()` when we're sanitizing at least
some characters.
Might be worth though to add a call to `BUG()` in case we see an
unsupported value for `allow_control_characters`.
> + for (i = 2; i < n; i++) {
> + if (src[i] == 'm') {
> + strbuf_add(dest, src, i + 1);
> + return i;
> + }
> + if (!isdigit(src[i]) && src[i] != ';')
> + break;
> + }
Okay, so this loop scans until we find the final "m" character that
terminates the sequence. Looks good to me.
PatrickThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Johannes Schindelin wrote on the Git mailing list (how to reply to this email): Hi Patrick,
On Fri, 9 Jan 2026, Patrick Steinhardt wrote:
> On Wed, Dec 17, 2025 at 02:23:41PM +0000, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > The preceding two commits introduced special handling of the sideband
> > channel to neutralize ANSI escape sequences before sending the payload
> > to the terminal, and `sideband.allowControlCharacters` to override that
> > behavior.
> >
> > However, as reported by brian m. carlson, some `pre-receive` hooks that
> > are actively used in practice want to color their messages and therefore
> > rely on the fact that Git passes them through to the terminal, even
> > though they have no way to determine whether the receiving side can
> > actually handle Escape sequences (think e.g. about the practice
> > recommended by Git that third-party applications wishing to use Git
> > functionality parse the output of Git commands).
> >
> > In contrast to other ANSI escape sequences, it is highly unlikely that
> > coloring sequences can be essential tools in attack vectors that mislead
> > Git users e.g. by hiding crucial information.
>
> The worst that they can do is to set up both fore- and background color
> to be the same so that text isn't visible. But I think that's an okay
> tradeoff.
Indeed.
The major concern here is to hide the fact from the user that Git already
exited and that what they see in their terminal is not actually Git asking
them to input something.
Technically, this would be possible by setting the text to "invisible"
(which would be a fine thing when pretending to ask for a password,
anyway). But without the ability to move the cursor, attackers will have a
much harder time to cover their tracks.
> > Therefore we can have both: Continue to allow ANSI coloring sequences to
> > be passed to the terminal by default, and neutralize all other ANSI
> > Escape sequences.
>
> Makes sense.
>
> > diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt
> > index 3fb5045cd7..e5b7383c7a 100644
> > --- a/Documentation/config/sideband.txt
> > +++ b/Documentation/config/sideband.txt
> > @@ -1,5 +1,17 @@
> > sideband.allowControlCharacters::
> > By default, control characters that are delivered via the sideband
> > - are masked, to prevent potentially unwanted ANSI escape sequences
> > - from being sent to the terminal. Use this config setting to override
> > - this behavior.
> > + are masked, except ANSI color sequences. This prevents potentially
> > + unwanted ANSI escape sequences from being sent to the terminal. Use
> > + this config setting to override this behavior:
> > ++
> > +--
> > + default::
> > + color::
> > + Allow ANSI color sequences, line feeds and horizontal tabs,
> > + but mask all other control characters. This is the default.
> > + false::
> > + Mask all control characters other than line feeds and
> > + horizontal tabs.
> > + true::
> > + Allow all control characters to be sent to the terminal.
> > +--
>
> Nit: I think that our modern doc style requires the values to use
> backticks. E.g. "`default`::".
Will change.
> > diff --git a/sideband.c b/sideband.c
> > index 997430f2ea..fb43008ab7 100644
> > --- a/sideband.c
> > +++ b/sideband.c
> > @@ -40,8 +45,26 @@ static int use_sideband_colors(void)
> > if (use_sideband_colors_cached >= 0)
> > return use_sideband_colors_cached;
> >
> > - git_config_get_bool("sideband.allowcontrolcharacters",
> > - &allow_control_characters);
> > + switch (git_config_get_maybe_bool("sideband.allowcontrolcharacters", &i)) {
> > + case 0: /* Boolean value */
> > + allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS :
> > + ALLOW_NO_CONTROL_CHARACTERS;
> > + break;
> > + case -1: /* non-Boolean value */
> > + if (git_config_get_string_tmp("sideband.allowcontrolcharacters",
> > + &value))
> > + ; /* huh? `get_maybe_bool()` returned -1 */
>
> This case is something that shouldn't happen in practice because we know
> that the config ought to exist. I guess it _could_ indicate a race
> condition, even though it's extremely unlikely to ever happen. So I was
> thinking about whether we want to `BUG()` here, but I guess just
> ignoring this is fine, as well.
I don't think that we can even get into a race condition because the
config is cached after it is read.
> > @@ -70,9 +93,41 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
> > list_config_item(list, prefix, keywords[i].keyword);
> > }
> >
> > +static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n)
> > +{
> > + int i;
> > +
> > + /*
> > + * Valid ANSI color sequences are of the form
> > + *
> > + * ESC [ [<n> [; <n>]*] m
> > + *
> > + * These are part of the Select Graphic Rendition sequences which
> > + * contain more than just color sequences, for more details see
> > + * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
> > + */
> > +
> > + if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES ||
> > + n < 3 || src[0] != '\x1b' || src[1] != '[')
> > + return 0;
>
> This would break in case `allow_control_characters` allows _all_ ANSI
> sequences. But that doesn't matter right now because the function is
> only called via `strbuf_add_sanitized()` when we're sanitizing at least
> some characters.
>
> Might be worth though to add a call to `BUG()` in case we see an
> unsupported value for `allow_control_characters`.
Later patches change the logic, though, to make `allow_control_characters`
a bit field. So maybe it can be left as-is here?
>
> > + for (i = 2; i < n; i++) {
> > + if (src[i] == 'm') {
> > + strbuf_add(dest, src, i + 1);
> > + return i;
> > + }
> > + if (!isdigit(src[i]) && src[i] != ';')
> > + break;
> > + }
>
> Okay, so this loop scans until we find the final "m" character that
> terminates the sequence. Looks good to me.
Thank you for your review!
Johannes |
||
| ifdef::with-breaking-changes[] | ||
| By default, control characters that are delivered via the sideband | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Patrick Steinhardt wrote (reply to this): On Wed, Dec 17, 2025 at 02:23:42PM +0000, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> Even though control sequences that erase characters are quite juicy for
> attack scenarios, where attackers are eager to hide traces of suspicious
> activities, during the review of the side band sanitizing patch series
> concerns were raised that there might be some legimitate scenarios where
> Git server's `pre-receive` hooks use those sequences in a benign way.
>
> Control sequences to move the cursor can likewise be used to hide tracks
> by overwriting characters, and have been equally pointed out as having
> legitimate users.
>
> Let's add options to let users opt into passing through those ANSI
> Escape sequences: `sideband.allowControlCharacters` now supports also
> `cursor` and `erase`, and it parses the value as a comma-separated list.
Hm, okay. I don't really see much of a reason to allow these, but now
that the code exists already I don't see a reason why we should remove
those options again.
> diff --git a/sideband.c b/sideband.c
> index fb43008ab7..725e24db0d 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -28,9 +28,43 @@ static struct keyword_entry keywords[] = {
> static enum {
> ALLOW_NO_CONTROL_CHARACTERS = 0,
> ALLOW_ANSI_COLOR_SEQUENCES = 1<<0,
> + ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1,
> + ALLOW_ANSI_ERASE = 1<<2,
> ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES,
> - ALLOW_ALL_CONTROL_CHARACTERS = 1<<1,
> -} allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
> + ALLOW_ALL_CONTROL_CHARACTERS = 1<<3,
> +} allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
Nit, not worth addressing on its own: readability would be helped a bit
if the assignments were all aligned.
static enum {
ALLOW_NO_CONTROL_CHARACTERS = 0,
ALLOW_ANSI_COLOR_SEQUENCES = 1<<0,
ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1,
ALLOW_ANSI_ERASE = 1<<2,
ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES,
ALLOW_ALL_CONTROL_CHARACTERS = 1<<3,
} allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
> +static inline int skip_prefix_in_csv(const char *value, const char *prefix,
> + const char **out)
> +{
> + if (!skip_prefix(value, prefix, &value) ||
> + (*value && *value != ','))
> + return 0;
> + *out = value + !!*value;
> + return 1;
> +}
> +
> +static void parse_allow_control_characters(const char *value)
> +{
> + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
> + while (*value) {
> + if (skip_prefix_in_csv(value, "default", &value))
> + allow_control_characters |= ALLOW_DEFAULT_ANSI_SEQUENCES;
> + else if (skip_prefix_in_csv(value, "color", &value))
> + allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES;
> + else if (skip_prefix_in_csv(value, "cursor", &value))
> + allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS;
> + else if (skip_prefix_in_csv(value, "erase", &value))
> + allow_control_characters |= ALLOW_ANSI_ERASE;
> + else if (skip_prefix_in_csv(value, "true", &value))
> + allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS;
> + else if (skip_prefix_in_csv(value, "false", &value))
> + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
Does it really make sense to also handle "true" and "false" here? I
would expect that those values can only be passed standalone.
> + else
> + warning(_("unrecognized value for `sideband."
> + "allowControlCharacters`: '%s'"), value);
> + }
> +}
This could be simplified if we used e.g. `string_list_split()`. But on
the other hand it avoids allocations, so that's a nice benefit.
PatrickThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "brian m. carlson" wrote on the Git mailing list (how to reply to this email): On 2026-01-09 at 12:38:31, Patrick Steinhardt wrote:
> On Wed, Dec 17, 2025 at 02:23:42PM +0000, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > Even though control sequences that erase characters are quite juicy for
> > attack scenarios, where attackers are eager to hide traces of suspicious
> > activities, during the review of the side band sanitizing patch series
> > concerns were raised that there might be some legimitate scenarios where
> > Git server's `pre-receive` hooks use those sequences in a benign way.
> >
> > Control sequences to move the cursor can likewise be used to hide tracks
> > by overwriting characters, and have been equally pointed out as having
> > legitimate users.
> >
> > Let's add options to let users opt into passing through those ANSI
> > Escape sequences: `sideband.allowControlCharacters` now supports also
> > `cursor` and `erase`, and it parses the value as a comma-separated list.
>
> Hm, okay. I don't really see much of a reason to allow these, but now
> that the code exists already I don't see a reason why we should remove
> those options again.
The reason these sequences, along with other sequences not mentioned in
this series, are useful is because people run tools like build tools
(e.g., Cargo) or linters in pre-receive hooks and print the output and
those use a substantial portion of possible escape sequences. I did a
brief survey sometime back of pre-receive hooks on GitHub to see what
escape sequences were in use.
I think Heroku has a push-to-deploy technique that leverages this
approach to build and deploy your app, for instance.
This is one of the reasons that I was opposed to this series: it tends
to break what is a very common use case. Certainly it is not as common
for cloud-based forge environments, but it is very common for people to
do these kinds of things in self-hosted forge environments (where custom
pre-receive hooks are commonly used) or in non-forge environments like
push-to-deploy.
--
brian m. carlson (they/them)
Toronto, Ontario, CAThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Jeff King wrote on the Git mailing list (how to reply to this email): On Sat, Jan 10, 2026 at 05:26:04PM +0000, brian m. carlson wrote:
> The reason these sequences, along with other sequences not mentioned in
> this series, are useful is because people run tools like build tools
> (e.g., Cargo) or linters in pre-receive hooks and print the output and
> those use a substantial portion of possible escape sequences. I did a
> brief survey sometime back of pre-receive hooks on GitHub to see what
> escape sequences were in use.
>
> I think Heroku has a push-to-deploy technique that leverages this
> approach to build and deploy your app, for instance.
>
> This is one of the reasons that I was opposed to this series: it tends
> to break what is a very common use case. Certainly it is not as common
> for cloud-based forge environments, but it is very common for people to
> do these kinds of things in self-hosted forge environments (where custom
> pre-receive hooks are commonly used) or in non-forge environments like
> push-to-deploy.
I also share your concern that real-world cases may be relying on these.
But I am also sympathetic that some people may prefer to risk breakage
(or ugliness) if it might protect them from misleading or mischievous
terminal trickery.
Is there any reason we cannot introduce the new functionality as a
config option but _not_ enable it by default?
That gives people the tools to protect themselves if they want to bear
the potential cost. It just feels a shame to deny them the tool because
we can't agree on the default.
-PeffThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): Jeff King <peff@peff.net> writes:
> Is there any reason we cannot introduce the new functionality as a
> config option but _not_ enable it by default?
>
> That gives people the tools to protect themselves if they want to bear
> the potential cost. It just feels a shame to deny them the tool because
> we can't agree on the default.
Yeah, I like the suggestion---making it opt-in would have much less
chance of breaking set-up people are relying on all of a sudden.
Thanks.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "brian m. carlson" wrote on the Git mailing list (how to reply to this email): On 2026-01-15 at 21:14:48, Jeff King wrote:
> Is there any reason we cannot introduce the new functionality as a
> config option but _not_ enable it by default?
>
> That gives people the tools to protect themselves if they want to bear
> the potential cost. It just feels a shame to deny them the tool because
> we can't agree on the default.
Yes, I think that would be a fine and reasonable approach.
--
brian m. carlson (they/them)
Toronto, Ontario, CAThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> I disagree with making sideband sanitization opt-in or weakening it based
> on a "trusted remote" heuristic. In this context, emitting untrusted bytes
> to a terminal without proper sanitization is a security-relevant bug;
> safe-by-default should be the baseline.
> ...
> If the goal is to mitigate terminal escape injection from
> remote-controlled output, then shipping it disabled by default does not
> mitigate the default case. Most users will not discover or enable a
> hardening knob until after an incident.
I think we already know we disagree on this point already. I am
simply agreeing with what brian recommended, based on his findings
at GitHub hosted public projects [*], and what Ondrej says they have
been doing in Fedora, CentOS and RHEL [*].
> I don't think we can safely infer "trusted enough to write to my terminal"
> from "I fetch from there often".
It was mostly an attempt to offer an idea: "Even if we make it off
by default, we may want to protect the initial clone, and here is
one thing you could do...". If it would not help in practice, I am
fine if we ditch it (meaning: default off everywhere, even for the
initial contact with an unknown repository).
> If the proposal is "full pass-through of all control characters is
> opt-in", or "full sanitizing of all control characters is opt-in", I
> whole-heartedly agree: That is already opt-in via setting
> `sideband.allowControlCharacters` to `false` or `true`, respectively.
>
> If the proposal is "keep the historical behavior (verbatim sideband
> payload, no sanitization) as the default, and make sanitization opt-in", I
> am firmly opposed: This makes the sideband payload remote-controlled; A
> security hardening that is off by default will not protect the default
> user population.
>
> Can you confirm which of these two meanings you intend when you say
> "opt-in" here? Once that's clarified, we can discuss whether the default
> should remain at "color-only" (today's default) with explicit opt-in for
> riskier sequences, or whether you're arguing for no filtering at all by
> default.
The latter.
I wouldn't be surprised if people, who usually do not participate in
the discussion around here, are highly inconvenienced when we
suddenly filter out IEC/ISO 2022:1994, for example. Not that I
suspect that these character encodings are still popular in some
parts of the world, but that I fundamentally disagree with the
attitude "we explicitly allow colors to be passed so it is perfectly
fine if we filter everything else out".
[References]
* https://lore.kernel.org/git/aWKLrIefrcSwReu2@fruit.crustytoothpaste.net/
* https://lore.kernel.org/git/CA+B51BEs7kuJ7s+K2vbZLSoaq3krGrqVncQAaTjSSNazFLY3tw@mail.gmail.com/There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): "brian m. carlson" <sandals@crustytoothpaste.net> writes:
> On 2026-01-15 at 21:14:48, Jeff King wrote:
>> Is there any reason we cannot introduce the new functionality as a
>> config option but _not_ enable it by default?
>>
>> That gives people the tools to protect themselves if they want to bear
>> the potential cost. It just feels a shame to deny them the tool because
>> we can't agree on the default.
>
> Yes, I think that would be a fine and reasonable approach.
Absolutely.
After a few weeks, however, nobody seems to have stepped up to help
us move forward (unless I missed a patch or two, of course), so here
is my attempt. To be applied on top of Dscho's 5-patch series (v3)
that ends at c5b95e19 (sideband: offer to configure sanitizing on a
per-URL basis, 2026-01-16).
Thanks.
--- >8 ---
From: Junio C Hamano <gitster@pobox.com>
Date: Mon, 2 Feb 2026 17:06:03 -0800
Subject: [PATCH 5/4] sideband: neuter the sideband filtering
To prevent breaking settings that are working well for existing
users, tone down the sideband filtering feature and turn it off by
default. This hopefully matches the way how distros like Fedora and
RHEL are shipping this feature in theirs.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
Documentation/config/sideband.txt | 14 +++++++-------
sideband.c | 6 ++----
t/t5409-colorize-remote-messages.sh | 12 ++++++++----
3 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt
index 32088bbf2f..d2cd86fa60 100644
--- a/Documentation/config/sideband.txt
+++ b/Documentation/config/sideband.txt
@@ -1,15 +1,15 @@
sideband.allowControlCharacters::
- By default, control characters that are delivered via the sideband
- are masked, except ANSI color sequences. This prevents potentially
- unwanted ANSI escape sequences from being sent to the terminal. Use
- this config setting to override this behavior (the value can be
- a comma-separated list of the following keywords):
+ By default, control characters that are delivered via the
+ sideband are all passed through. To prevent potentially
+ unwanted ANSI escape sequences from being sent to the
+ terminal, use this config setting to override this behavior
+ (the value can be a comma-separated list of the following
+ keywords):
+
--
- `default`::
`color`::
Allow ANSI color sequences, line feeds and horizontal tabs,
- but mask all other control characters. This is the default.
+ but mask all other control characters.
`cursor:`:
Allow control sequences that move the cursor. This is
disabled by default.
diff --git a/sideband.c b/sideband.c
index a8cd142cd7..3d8534671e 100644
--- a/sideband.c
+++ b/sideband.c
@@ -61,9 +61,7 @@ int sideband_allow_control_characters_config(const char *var, const char *value)
allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
while (*value) {
- if (skip_prefix_in_csv(value, "default", &value))
- allow_control_characters |= ALLOW_DEFAULT_ANSI_SEQUENCES;
- else if (skip_prefix_in_csv(value, "color", &value))
+ if (skip_prefix_in_csv(value, "color", &value))
allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES;
else if (skip_prefix_in_csv(value, "cursor", &value))
allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS;
@@ -125,7 +123,7 @@ static int use_sideband_colors(void)
sideband_allow_control_characters_config("sideband.allowcontrolcharacters", value);
if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET)
- allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
+ allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS;
}
if (!git_config_get_string_tmp(key, &value))
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
index 1d039cbdaf..47bc8bbef2 100755
--- a/t/t5409-colorize-remote-messages.sh
+++ b/t/t5409-colorize-remote-messages.sh
@@ -107,7 +107,8 @@ test_expect_success 'disallow (color) control sequences in sideband' '
test_config_global uploadPack.packObjectsHook ./color-me-surprised &&
test_commit need-at-least-one-commit &&
- git clone --no-local . throw-away 2>stderr &&
+ git -c sideband.allowControlCharacters=color \
+ clone --no-local . throw-away 2>stderr &&
test_decode_color <stderr >decoded &&
test_grep RED decoded &&
test_grep "\\^G" stderr &&
@@ -122,7 +123,8 @@ test_expect_success 'disallow (color) control sequences in sideband' '
test_grep "\\^G" stderr &&
rm -rf throw-away &&
- git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr &&
+ git -c sideband.allowControlCharacters \
+ clone --no-local . throw-away 2>stderr &&
test_decode_color <stderr >decoded &&
test_grep RED decoded &&
tr -dc "\\007" <stderr >actual &&
@@ -148,7 +150,8 @@ test_expect_success 'control sequences in sideband allowed by default' '
test_commit need-at-least-one-commit-at-least &&
rm -rf throw-away &&
- git clone --no-local . throw-away 2>stderr &&
+ git -c sideband.allowControlCharacters=color \
+ clone --no-local . throw-away 2>stderr &&
test_decode_color <stderr >color-decoded &&
test_decode_csi <color-decoded >decoded &&
test_grep ! "CSI \\[K" decoded &&
@@ -176,7 +179,8 @@ test_expect_success 'allow all control sequences for a specific URL' '
test_commit one-more-please &&
rm -rf throw-away &&
- git clone --no-local . throw-away 2>stderr &&
+ git -c sideband.allowControlCharacters=color \
+ clone --no-local . throw-away 2>stderr &&
test_decode_color <stderr >color-decoded &&
test_decode_csi <color-decoded >decoded &&
test_grep ! "CSI \\[K" decoded &&
--
2.53.0-162-gcda875bd0b
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Johannes Schindelin wrote on the Git mailing list (how to reply to this email): Hi Junio,
On Tue, 3 Feb 2026, Junio C Hamano wrote:
> "brian m. carlson" <sandals@crustytoothpaste.net> writes:
>
> > On 2026-01-15 at 21:14:48, Jeff King wrote:
> >> Is there any reason we cannot introduce the new functionality as a
> >> config option but _not_ enable it by default?
> >>
> >> That gives people the tools to protect themselves if they want to bear
> >> the potential cost. It just feels a shame to deny them the tool because
> >> we can't agree on the default.
> >
> > Yes, I think that would be a fine and reasonable approach.
>
> Absolutely.
I disagree with neutering this security fix. Let me explain why, and then
propose a compromise.
CVE-2024-52005 exists because Git passes untrusted payload to the terminal
without sanitization. The terminal interprets control sequences sent to it
(it has no way to distinguish between sequences Git _intended_ to send and
sequences a malicious remote slipped into the sideband). That
responsibility falls squarely on Git. Shifting it to terminal emulators is
not viable: they _cannot_ make that distinction.
This is not about one specific vulnerability (OSC 8 or otherwise). It is
about the principle that programs must sanitize untrusted input before
passing it to an interpreter. Terminal emulators are interpreters. The set
of exploitable sequences changes over time as terminals add features; the
only durable fix is sanitization at the source.
Now, about breaking existing users.
The patches I submitted _do not_ break pre-receive hooks that emit color
sequences. Color sequences are allowed by default. What is disabled by
default are sequences that set the window title, query terminal state,
move the cursor, etc. (functionality that legitimate hooks have no
business using, and functionality that is ripe for exploitation).
The concern about Japanese ISO encodings colliding with control bytes is
theoretical at best: sideband messages are prefixed with the ASCII string
"remote: ", so any such encoding would already be broken today.
Here is the problem with "off by default".
Turning the sanitization off by default means CVE-2024-52005 remains
unaddressed for the vast majority of users. Providing an opt-in config is
security theater: users who do not know about the vulnerability will not
enable the protection. That is the opposite of defense in depth.
Fedora's decision to ship with sanitization disabled does not validate the
approach; it reflects their reluctance to diverge from upstream defaults,
not a security analysis concluding that the default is safe.
That said, I can see a path forward.
1. Turn sanitization _off_ by default in 2.x.
2. Document clearly that this default will change.
3. In Git 3.0 (the next breaking-changes release), flip the default so
that `sideband.allowControlCharacters=color` is the baseline, i.e.
color sequences pass through, but nothing else does.
This gives users and tooling (and support engineers) time to adapt while
committing to secure-by-default behavior in the near future.
I would appreciate hearing from anyone on the list with security
expertise. The principle that untrusted input must be sanitized before
reaching an interpreter is foundational; I am not aware of any credible
security guidance that says otherwise.
Ciao,
DschoThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Just this point, as I do not have time to deal with this topic right
now.
> The concern about Japanese ISO encodings colliding with control bytes is
> theoretical at best: sideband messages are prefixed with the ASCII string
> "remote: ", so any such encoding would already be broken today.
This is false.
ISO/IEC 2022 is designed to allow mixing different encodings
(including ASCII), and it is a common practice to mix in a Japanese
string (or any of your choice) enclosed in a pair of "ESC $ B"
(note: 'B' is for Japanese, but other character encoding can be
specified in the same string by using a different letter here) and
"ESC ( B" to go back to ASCII. So if you throw "remote: " at the
beginning, that comes out in ASCII. The payload may have ISO/IEC
2022 encoded "foreign" letters, but again, they are closed with "ESC
( B" to switch back to ASCII, if you add random junk (like "..."
perhaps) after them in ASCII, your random junk will come out in
ASCII just fine.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Junio C Hamano wrote on the Git mailing list (how to reply to this email): Junio C Hamano <gitster@pobox.com> writes:
> diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
> index 1d039cbdaf..47bc8bbef2 100755
> --- a/t/t5409-colorize-remote-messages.sh
> +++ b/t/t5409-colorize-remote-messages.sh
> @@ -107,7 +107,8 @@ test_expect_success 'disallow (color) control sequences in sideband' '
> test_config_global uploadPack.packObjectsHook ./color-me-surprised &&
> test_commit need-at-least-one-commit &&
>
> - git clone --no-local . throw-away 2>stderr &&
> + git -c sideband.allowControlCharacters=color \
> + clone --no-local . throw-away 2>stderr &&
> test_decode_color <stderr >decoded &&
> test_grep RED decoded &&
> test_grep "\\^G" stderr &&
While I was mucking with this part of the test, this test piece
reminded me that I myself often use a control sequence
ESC ] 0; <my string> BEL
in a time-consuming program to say which step of the whole thing it
is currently running.
This sequence updates the terminal's title, so in one of my terminal
tab, I start such a time-consuming program, switch to another tab
that is showing another terminal, and let it run. It will report me
its progress by changing the terminal's title every once in a while.
I would not frown at people who want to do the same over the network
between the servers they control and their desktop client. Even
though the server is not friendly to those who do not run terminal
that support such a control sequence, that is strictly between the
server and the end-user who talks with the server.
And neutering BEL of course will break such a user, unless the user
says "ok, if I need to pass everything in order to pass BEL, then so
be it". That is a bit sad.
|
||
| are masked, except ANSI color sequences. This prevents potentially | ||
| endif::with-breaking-changes[] | ||
| ifndef::with-breaking-changes[] | ||
| By default, no control characters delivered via the sideband | ||
| are masked. This is unsafe and will change in Git v3.* to only | ||
| allow ANSI color sequences by default, preventing potentially | ||
| endif::with-breaking-changes[] | ||
| unwanted ANSI escape sequences from being sent to the terminal. Use | ||
| this config setting to override this behavior (the value can be | ||
| a comma-separated list of the following keywords): | ||
| + | ||
| -- | ||
| `default`:: | ||
| ifndef::with-breaking-changes[] | ||
| Allow any control sequence. This default is unsafe and will | ||
| change to `color` in Git v3.*. | ||
| endif::with-breaking-changes[] | ||
| `color`:: | ||
| Allow ANSI color sequences, line feeds and horizontal tabs, | ||
| but mask all other control characters. This is the default. | ||
| `cursor:`: | ||
| Allow control sequences that move the cursor. This is | ||
| disabled by default. | ||
| `erase`:: | ||
| Allow control sequences that erase charactrs. This is | ||
| disabled by default. | ||
| `false`:: | ||
| Mask all control characters other than line feeds and | ||
| horizontal tabs. | ||
| `true`:: | ||
| Allow all control characters to be sent to the terminal. | ||
| -- | ||
|
|
||
| sideband.<url>.*:: | ||
| Apply the `sideband.*` option selectively to specific URLs. The | ||
| same URL matching logic applies as for `http.<url>.*` settings. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| #include "help.h" | ||
| #include "pkt-line.h" | ||
| #include "write-or-die.h" | ||
| #include "urlmatch.h" | ||
|
|
||
| struct keyword_entry { | ||
| /* | ||
|
|
@@ -26,6 +27,91 @@ static struct keyword_entry keywords[] = { | |
| { "error", GIT_COLOR_BOLD_RED }, | ||
| }; | ||
|
|
||
| static enum { | ||
| ALLOW_CONTROL_SEQUENCES_UNSET = -1, | ||
| ALLOW_NO_CONTROL_CHARACTERS = 0, | ||
| ALLOW_ANSI_COLOR_SEQUENCES = 1<<0, | ||
| ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1, | ||
| ALLOW_ANSI_ERASE = 1<<2, | ||
| ALLOW_ALL_CONTROL_CHARACTERS = 1<<3, | ||
| #ifdef WITH_BREAKING_CHANGES | ||
| ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES, | ||
| #else | ||
| ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ALL_CONTROL_CHARACTERS, | ||
| #endif | ||
| } allow_control_characters = ALLOW_CONTROL_SEQUENCES_UNSET; | ||
|
|
||
| static inline int skip_prefix_in_csv(const char *value, const char *prefix, | ||
| const char **out) | ||
| { | ||
| if (!skip_prefix(value, prefix, &value) || | ||
| (*value && *value != ',')) | ||
| return 0; | ||
| *out = value + !!*value; | ||
| return 1; | ||
| } | ||
|
|
||
| int sideband_allow_control_characters_config(const char *var, const char *value) | ||
| { | ||
| switch (git_parse_maybe_bool(value)) { | ||
| case 0: | ||
| allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; | ||
| return 0; | ||
| case 1: | ||
| allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS; | ||
| return 0; | ||
| default: | ||
| break; | ||
| } | ||
|
|
||
| allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; | ||
| while (*value) { | ||
| if (skip_prefix_in_csv(value, "default", &value)) | ||
| allow_control_characters |= ALLOW_DEFAULT_ANSI_SEQUENCES; | ||
| else if (skip_prefix_in_csv(value, "color", &value)) | ||
| allow_control_characters |= ALLOW_ANSI_COLOR_SEQUENCES; | ||
| else if (skip_prefix_in_csv(value, "cursor", &value)) | ||
| allow_control_characters |= ALLOW_ANSI_CURSOR_MOVEMENTS; | ||
| else if (skip_prefix_in_csv(value, "erase", &value)) | ||
| allow_control_characters |= ALLOW_ANSI_ERASE; | ||
| else if (skip_prefix_in_csv(value, "true", &value)) | ||
| allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS; | ||
| else if (skip_prefix_in_csv(value, "false", &value)) | ||
| allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS; | ||
| else | ||
| warning(_("unrecognized value for '%s': '%s'"), var, value); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| static int sideband_config_callback(const char *var, const char *value, | ||
| const struct config_context *ctx UNUSED, | ||
| void *data UNUSED) | ||
| { | ||
| if (!strcmp(var, "sideband.allowcontrolcharacters")) | ||
| return sideband_allow_control_characters_config(var, value); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| void sideband_apply_url_config(const char *url) | ||
| { | ||
| struct urlmatch_config config = URLMATCH_CONFIG_INIT; | ||
| char *normalized_url; | ||
|
|
||
| if (!url) | ||
| BUG("must not call sideband_apply_url_config(NULL)"); | ||
|
|
||
| config.section = "sideband"; | ||
| config.collect_fn = sideband_config_callback; | ||
|
|
||
| normalized_url = url_normalize(url, &config.url); | ||
| repo_config(the_repository, urlmatch_config_entry, &config); | ||
| free(normalized_url); | ||
| string_list_clear(&config.vars, 1); | ||
| urlmatch_config_release(&config); | ||
| } | ||
|
|
||
| /* Returns a color setting (GIT_COLOR_NEVER, etc). */ | ||
| static enum git_colorbool use_sideband_colors(void) | ||
| { | ||
|
|
@@ -39,6 +125,14 @@ static enum git_colorbool use_sideband_colors(void) | |
| if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN) | ||
| return use_sideband_colors_cached; | ||
|
|
||
| if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) { | ||
| if (!repo_config_get_value(the_repository, "sideband.allowcontrolcharacters", &value)) | ||
| sideband_allow_control_characters_config("sideband.allowcontrolcharacters", value); | ||
|
|
||
| if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) | ||
| allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES; | ||
| } | ||
|
|
||
| if (!repo_config_get_string_tmp(the_repository, key, &value)) | ||
| use_sideband_colors_cached = git_config_colorbool(key, value); | ||
| else if (!repo_config_get_string_tmp(the_repository, "color.ui", &value)) | ||
|
|
@@ -66,6 +160,93 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref | |
| list_config_item(list, prefix, keywords[i].keyword); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Phillip Wood wrote (reply to this): Hi Dscho
Just a couple of small comments
On 14/01/2025 18:19, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
> > +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
> +{
> + strbuf_grow(dest, n);
> + for (; n && *src; src++, n--) {
> + if (!iscntrl(*src) || *src == '\t' || *src == '\n')
Isn't it a bug to pass '\n' to maybe_colorize_sideband() ?
> + strbuf_addch(dest, *src);
> + else {
> + strbuf_addch(dest, '^');
> + strbuf_addch(dest, 0x40 + *src);
This will escape DEL ('\x7f') as "^\xbf" which is invalid in utf-8 locales. Perhaps we could use "^?" for that instead.
> +test_expect_success 'disallow (color) control sequences in sideband' '
> + write_script .git/color-me-surprised <<-\EOF &&
> + printf "error: Have you \\033[31mread\\033[m this?\\n" >&2
> + exec "$@"
> + EOF
> + test_config_global uploadPack.packObjectshook ./color-me-surprised &&
> + test_commit need-at-least-one-commit &&
> + git clone --no-local . throw-away 2>stderr &&
> + test_decode_color <stderr >decoded &&
> + test_grep ! RED decoded
I'd be happier if we used test_cmp() here so that we check that the sanitized version matches what we expect and the test does not pass if there a typo in the script above stops it from writing the SGR code for red.
Best Wishes
Phillip
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Johannes Schindelin wrote (reply to this): Hi Phillip,
On Wed, 15 Jan 2025, Phillip Wood wrote:
> Just a couple of small comments
>
> On 14/01/2025 18:19, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int
> > n)
> > +{
> > + strbuf_grow(dest, n);
> > + for (; n && *src; src++, n--) {
> > + if (!iscntrl(*src) || *src == '\t' || *src == '\n')
>
> Isn't it a bug to pass '\n' to maybe_colorize_sideband() ?
While a band 2 message is indeed split by newlines and fed to this
function line by line, which is the case for a long time already: since
ed1902ef5c6 (cope with multiple line breaks within sideband progress
messages, 2007-10-16), the same is not true for band 3 messages: They pass
the entire message in one go (and for multi-line payload, only the first
line is prefixed with `remote:`, which is arguably a bug, but not one that
is within this here patch series' scope).
See
https://gitlab.com/git-scm/git/-/blob/v2.52.0/sideband.c#L191 and
https://gitlab.com/git-scm/git/-/blob/v2.52.0/sideband.c#L176,
respectively.
So no, I don't think that we can currently consider it a bug to pass `\n`
as part of the `src` parameter to `maybe_colorize_sideband()`.
> > + strbuf_addch(dest, *src);
> > + else {
> > + strbuf_addch(dest, '^');
> > + strbuf_addch(dest, 0x40 + *src);
>
> This will escape DEL ('\x7f') as "^\xbf" which is invalid in utf-8 locales.
> Perhaps we could use "^?" for that instead.
Good point! This seems to be the historical way to escape DEL, probably
because 0x3f ('?') is 0x7f + 0x40 truncated to 7 bits. I'll do this in the
next iteration:
-- snip --
diff --git a/sideband.c b/sideband.c
index f613d4d6cc3..684621579fd 100644
--- a/sideband.c
+++ b/sideband.c
@@ -175,7 +175,7 @@ static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
n -= i;
} else {
strbuf_addch(dest, '^');
- strbuf_addch(dest, 0x40 + *src);
+ strbuf_addch(dest, *src == 0x7f ? '?' : 0x40 + *src);
}
}
}
-- snap --
>
> > +test_expect_success 'disallow (color) control sequences in sideband' '
> > + write_script .git/color-me-surprised <<-\EOF &&
> > + printf "error: Have you \\033[31mread\\033[m this?\\n" >&2
> > + exec "$@"
> > + EOF
> > + test_config_global uploadPack.packObjectshook ./color-me-surprised &&
> > + test_commit need-at-least-one-commit &&
> > + git clone --no-local . throw-away 2>stderr &&
> > + test_decode_color <stderr >decoded &&
> > + test_grep ! RED decoded
>
> I'd be happier if we used test_cmp() here so that we check that the sanitized
> version matches what we expect and the test does not pass if there a typo in
> the script above stops it from writing the SGR code for red.
I often debug test failures in Git's test suite and one of the most
annoying category of test failures is when test cases expect byte-wise
exact Git output that changed for totally legitimate reasons [*1*].
Even worse: In many of those instances, the _intent_ of the check is not
even clear from that `test_cmp` and has to be reconstructed, a boring,
tedious task with little benefit to show for the effort.
I much prefer tests like this one, where a precise `test_grep` states
exactly what it expects to be present, or missing. The intent of such a
command is much clearer than that of `test_cmp expect actual`.
So, much as I appreciate your suggestion, I would prefer to keep the code
as-is.
Ciao,
Johannes
Footnote *1*: This really is not hypothetical. I had to battle quite a bit
with unstable compression sizes that are part of a `test_cmp` comparison,
https://github.com/git-for-windows/git/pull/5926#issuecomment-3486556940
shows a bit of the problems but is very shy about providing the specific
number of days I spent on addressing this issue. In hindsight, I should
have spent at most two hours on converting that from a byte-wise
comparison to a qualitative comparison.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Andreas Schwab wrote (reply to this): On Jan 14 2025, Johannes Schindelin via GitGitGadget wrote:
> diff --git a/sideband.c b/sideband.c
> index 02805573fab..c0b1cb044a3 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -65,6 +65,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
> list_config_item(list, prefix, keywords[i].keyword);
> }
>
> +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
> +{
> + strbuf_grow(dest, n);
> + for (; n && *src; src++, n--) {
> + if (!iscntrl(*src) || *src == '\t' || *src == '\n')
The argument of iscntrl needs to be converted to unsigned char.
--
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510 2552 DF73 E780 A9DA AEC1
"And now for something completely different."There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Junio C Hamano wrote (reply to this): Andreas Schwab <schwab@linux-m68k.org> writes:
> On Jan 14 2025, Johannes Schindelin via GitGitGadget wrote:
>
>> diff --git a/sideband.c b/sideband.c
>> index 02805573fab..c0b1cb044a3 100644
>> --- a/sideband.c
>> +++ b/sideband.c
>> @@ -65,6 +65,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
>> list_config_item(list, prefix, keywords[i].keyword);
>> }
>>
>> +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
>> +{
>> + strbuf_grow(dest, n);
>> + for (; n && *src; src++, n--) {
>> + if (!iscntrl(*src) || *src == '\t' || *src == '\n')
>
> The argument of iscntrl needs to be converted to unsigned char.
If this were system-provided one, you are absolutely correct.
But I think this comes from
sane-ctype.h:15:#undef iscntrl
sane-ctype.h:40:#define iscntrl(x) (sane_istest(x,GIT_CNTRL))
and sane_istest() does the casting to uchar for us, so this may be
OK (even if it may be a bit misleading).
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the Git mailing list, Patrick Steinhardt wrote (reply to this): On Wed, Dec 17, 2025 at 02:23:39PM +0000, Johannes Schindelin via GitGitGadget wrote:
> From: Johannes Schindelin <johannes.schindelin@gmx.de>
>
> The output of `git clone` is a vital component for understanding what
> has happened when things go wrong. However, these logs are partially
> under the control of the remote server (via the "sideband", which
> typically contains what the remote `git pack-objects` process sends to
> `stderr`), and is currently not sanitized by Git.
>
> This makes Git susceptible to ANSI escape sequence injection (see
> CWE-150, https://cwe.mitre.org/data/definitions/150.html), which allows
> attackers to corrupt terminal state, to hide information, and even to
> insert characters into the input buffer (i.e. as if the user had typed
> those characters).
>
> To plug this vulnerability, disallow any control character in the
> sideband, replacing them instead with the common `^<letter/symbol>`
> (e.g. `^[` for `\x1b`, `^A` for `\x01`).
>
> There is likely a need for more fine-grained controls instead of using a
> "heavy hammer" like this, which will be introduced subsequently.
Most notably color codes, I assume.
> diff --git a/sideband.c b/sideband.c
> index 02805573fa..fc1805dcf8 100644
> --- a/sideband.c
> +++ b/sideband.c
> @@ -65,6 +65,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
> list_config_item(list, prefix, keywords[i].keyword);
> }
>
> +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
Shouldn't `n` be of type `size_t`? I guess the answer is "maybe", as
`maybe_colorize_sideband()` also accepts `int n` with a big comment
explaining why that's okay. Ultimately, the reason is that we accept
pkt-lines, so every line is limited to at most 64kB anyway.
> +{
> + strbuf_grow(dest, n);
> + for (; n && *src; src++, n--) {
> + if (!iscntrl(*src) || *src == '\t' || *src == '\n')
> + strbuf_addch(dest, *src);
> + else {
Tiny nit, not worth addressing on its own: the if branch should also
have curly braces.
PatrickThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Johannes Schindelin wrote on the Git mailing list (how to reply to this email): Hi Patrick,
On Fri, 9 Jan 2026, Patrick Steinhardt wrote:
> On Wed, Dec 17, 2025 at 02:23:39PM +0000, Johannes Schindelin via GitGitGadget wrote:
> > From: Johannes Schindelin <johannes.schindelin@gmx.de>
> >
> > The output of `git clone` is a vital component for understanding what
> > has happened when things go wrong. However, these logs are partially
> > under the control of the remote server (via the "sideband", which
> > typically contains what the remote `git pack-objects` process sends to
> > `stderr`), and is currently not sanitized by Git.
> >
> > This makes Git susceptible to ANSI escape sequence injection (see
> > CWE-150, https://cwe.mitre.org/data/definitions/150.html), which allows
> > attackers to corrupt terminal state, to hide information, and even to
> > insert characters into the input buffer (i.e. as if the user had typed
> > those characters).
> >
> > To plug this vulnerability, disallow any control character in the
> > sideband, replacing them instead with the common `^<letter/symbol>`
> > (e.g. `^[` for `\x1b`, `^A` for `\x01`).
> >
> > There is likely a need for more fine-grained controls instead of using a
> > "heavy hammer" like this, which will be introduced subsequently.
>
> Most notably color codes, I assume.
Precisely.
> > diff --git a/sideband.c b/sideband.c
> > index 02805573fa..fc1805dcf8 100644
> > --- a/sideband.c
> > +++ b/sideband.c
> > @@ -65,6 +65,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
> > list_config_item(list, prefix, keywords[i].keyword);
> > }
> >
> > +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
>
> Shouldn't `n` be of type `size_t`? I guess the answer is "maybe", as
> `maybe_colorize_sideband()` also accepts `int n` with a big comment
> explaining why that's okay. Ultimately, the reason is that we accept
> pkt-lines, so every line is limited to at most 64kB anyway.
Exactly. I did not want to use a different data type for the parameter
that is essentially just passed through.
> > +{
> > + strbuf_grow(dest, n);
> > + for (; n && *src; src++, n--) {
> > + if (!iscntrl(*src) || *src == '\t' || *src == '\n')
> > + strbuf_addch(dest, *src);
> > + else {
>
> Tiny nit, not worth addressing on its own: the if branch should also
> have curly braces.
Will address in the next iteration.
Ciao,
Johannes |
||
| } | ||
|
|
||
| static int handle_ansi_sequence(struct strbuf *dest, const char *src, int n) | ||
| { | ||
| int i; | ||
|
|
||
| /* | ||
| * Valid ANSI color sequences are of the form | ||
| * | ||
| * ESC [ [<n> [; <n>]*] m | ||
| * | ||
| * These are part of the Select Graphic Rendition sequences which | ||
| * contain more than just color sequences, for more details see | ||
| * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR. | ||
| * | ||
| * The cursor movement sequences are: | ||
| * | ||
| * ESC [ n A - Cursor up n lines (CUU) | ||
| * ESC [ n B - Cursor down n lines (CUD) | ||
| * ESC [ n C - Cursor forward n columns (CUF) | ||
| * ESC [ n D - Cursor back n columns (CUB) | ||
| * ESC [ n E - Cursor next line, beginning (CNL) | ||
| * ESC [ n F - Cursor previous line, beginning (CPL) | ||
| * ESC [ n G - Cursor to column n (CHA) | ||
| * ESC [ n ; m H - Cursor position (row n, col m) (CUP) | ||
| * ESC [ n ; m f - Same as H (HVP) | ||
| * | ||
| * The sequences to erase characters are: | ||
| * | ||
| * | ||
| * ESC [ 0 J - Clear from cursor to end of screen (ED) | ||
| * ESC [ 1 J - Clear from cursor to beginning of screen (ED) | ||
| * ESC [ 2 J - Clear entire screen (ED) | ||
| * ESC [ 3 J - Clear entire screen + scrollback (ED) - xterm extension | ||
| * ESC [ 0 K - Clear from cursor to end of line (EL) | ||
| * ESC [ 1 K - Clear from cursor to beginning of line (EL) | ||
| * ESC [ 2 K - Clear entire line (EL) | ||
| * ESC [ n M - Delete n lines (DL) | ||
| * ESC [ n P - Delete n characters (DCH) | ||
| * ESC [ n X - Erase n characters (ECH) | ||
| * | ||
| * For a comprehensive list of common ANSI Escape sequences, see | ||
| * https://www.xfree86.org/current/ctlseqs.html | ||
| */ | ||
|
|
||
| if (n < 3 || src[0] != '\x1b' || src[1] != '[') | ||
| return 0; | ||
|
|
||
| for (i = 2; i < n; i++) { | ||
| if (((allow_control_characters & ALLOW_ANSI_COLOR_SEQUENCES) && | ||
| src[i] == 'm') || | ||
| ((allow_control_characters & ALLOW_ANSI_CURSOR_MOVEMENTS) && | ||
| strchr("ABCDEFGHf", src[i])) || | ||
| ((allow_control_characters & ALLOW_ANSI_ERASE) && | ||
| strchr("JKMPX", src[i]))) { | ||
| strbuf_add(dest, src, i + 1); | ||
| return i; | ||
| } | ||
| if (!isdigit(src[i]) && src[i] != ';') | ||
| break; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) | ||
| { | ||
| int i; | ||
|
|
||
| if ((allow_control_characters & ALLOW_ALL_CONTROL_CHARACTERS)) { | ||
| strbuf_add(dest, src, n); | ||
| return; | ||
| } | ||
|
|
||
| strbuf_grow(dest, n); | ||
| for (; n && *src; src++, n--) { | ||
| if (!iscntrl(*src) || *src == '\t' || *src == '\n') { | ||
| strbuf_addch(dest, *src); | ||
| } else if (allow_control_characters != ALLOW_NO_CONTROL_CHARACTERS && | ||
| (i = handle_ansi_sequence(dest, src, n))) { | ||
| src += i; | ||
| n -= i; | ||
| } else { | ||
| strbuf_addch(dest, '^'); | ||
| strbuf_addch(dest, *src == 0x7f ? '?' : 0x40 + *src); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * Optionally highlight one keyword in remote output if it appears at the start | ||
| * of the line. This should be called for a single line only, which is | ||
|
|
@@ -81,7 +262,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) | |
| int i; | ||
|
|
||
| if (!want_color_stderr(use_sideband_colors())) { | ||
| strbuf_add(dest, src, n); | ||
| strbuf_add_sanitized(dest, src, n); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -114,7 +295,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) | |
| } | ||
| } | ||
|
|
||
| strbuf_add(dest, src, n); | ||
| strbuf_add_sanitized(dest, src, n); | ||
| } | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the Git mailing list, Junio C Hamano wrote (reply to this):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the Git mailing list, Johannes Schindelin wrote (reply to this):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the Git mailing list, Junio C Hamano wrote (reply to this):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Johannes Schindelin wrote on the Git mailing list (how to reply to this email):