sideband: offer to configure sanitizing on a per-URL basis

The main objection against sanitizing the sideband that was raised during the review of the sideband sanitizing patches, first on the git-security mailing list, then on the public mailing list, was that there are some setups where server-side `pre-receive` hooks want to error out, giving colorful messages to the users on the client side (if they are not redirecting the output into a file, that is). To avoid breaking such setups, the default chosen by the sideband sanitizing patches is to pass through ANSI color sequences. Still, there might be some use case out there where that is not enough. Therefore the `sideband.allowControlCharacters` config setting allows for configuring levels of sanitizing. As Junio Hamano pointed out, to keep users safe by default, we need to be able to scope this to some servers because while a user may trust their company's Git server, the same might not apply to other Git servers. To allow for this, let's imitate the way `http.<url>.*` offers to scope config settings to certain URLs, by letting users override the `sideband.allowControlCharacters` setting via `sideband.<url>.allowControlCharacters`. Suggested-by: Junio Hamano <gitster@pobox.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Mar 5, 2026 at 15:34 UTC 602c83f0efed46c2e86a36273673bf8776ded04e
5 files changed +102 -24
Documentation/config/sideband.adoc
+4
@@ -22,3 +22,7 @@ sideband.allowControlCharacters::
22 `true`::
23 Allow all control characters to be sent to the terminal.
24 --
25 +
26 +sideband.<url>.*::
27 + Apply the `sideband.*` option selectively to specific URLs. The
28 + same URL matching logic applies as for `http.<url>.*` settings.
sideband.c
+57 -24
@@ -10,6 +10,7 @@
10 #include "help.h"
11 #include "pkt-line.h"
12 #include "write-or-die.h"
13 +#include "urlmatch.h"
14
15 struct keyword_entry {
16 /*
@@ -27,13 +28,14 @@ static struct keyword_entry keywords[] = {
28 };
29
30 static enum {
30 - ALLOW_NO_CONTROL_CHARACTERS = 0,
31 - ALLOW_ANSI_COLOR_SEQUENCES = 1<<0,
32 - ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1,
33 - ALLOW_ANSI_ERASE = 1<<2,
34 - ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES,
35 - ALLOW_ALL_CONTROL_CHARACTERS = 1<<3,
36 -} allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
31 + ALLOW_CONTROL_SEQUENCES_UNSET = -1,
32 + ALLOW_NO_CONTROL_CHARACTERS = 0,
33 + ALLOW_ANSI_COLOR_SEQUENCES = 1<<0,
34 + ALLOW_ANSI_CURSOR_MOVEMENTS = 1<<1,
35 + ALLOW_ANSI_ERASE = 1<<2,
36 + ALLOW_DEFAULT_ANSI_SEQUENCES = ALLOW_ANSI_COLOR_SEQUENCES,
37 + ALLOW_ALL_CONTROL_CHARACTERS = 1<<3,
38 +} allow_control_characters = ALLOW_CONTROL_SEQUENCES_UNSET;
39
40 static inline int skip_prefix_in_csv(const char *value, const char *prefix,
41 const char **out)
@@ -45,8 +47,19 @@ static inline int skip_prefix_in_csv(const char *value, const char *prefix,
47 return 1;
48 }
49
48 -static void parse_allow_control_characters(const char *value)
50 +int sideband_allow_control_characters_config(const char *var, const char *value)
51 {
52 + switch (git_parse_maybe_bool(value)) {
53 + case 0:
54 + allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
55 + return 0;
56 + case 1:
57 + allow_control_characters = ALLOW_ALL_CONTROL_CHARACTERS;
58 + return 0;
59 + default:
60 + break;
61 + }
62 +
63 allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
64 while (*value) {
65 if (skip_prefix_in_csv(value, "default", &value))
@@ -62,9 +75,37 @@ static void parse_allow_control_characters(const char *value)
75 else if (skip_prefix_in_csv(value, "false", &value))
76 allow_control_characters = ALLOW_NO_CONTROL_CHARACTERS;
77 else
65 - warning(_("unrecognized value for `sideband."
66 - "allowControlCharacters`: '%s'"), value);
78 + warning(_("unrecognized value for '%s': '%s'"), var, value);
79 }
80 + return 0;
81 +}
82 +
83 +static int sideband_config_callback(const char *var, const char *value,
84 + const struct config_context *ctx UNUSED,
85 + void *data UNUSED)
86 +{
87 + if (!strcmp(var, "sideband.allowcontrolcharacters"))
88 + return sideband_allow_control_characters_config(var, value);
89 +
90 + return 0;
91 +}
92 +
93 +void sideband_apply_url_config(const char *url)
94 +{
95 + struct urlmatch_config config = URLMATCH_CONFIG_INIT;
96 + char *normalized_url;
97 +
98 + if (!url)
99 + BUG("must not call sideband_apply_url_config(NULL)");
100 +
101 + config.section = "sideband";
102 + config.collect_fn = sideband_config_callback;
103 +
104 + normalized_url = url_normalize(url, &config.url);
105 + repo_config(the_repository, urlmatch_config_entry, &config);
106 + free(normalized_url);
107 + string_list_clear(&config.vars, 1);
108 + urlmatch_config_release(&config);
109 }
110
111 /* Returns a color setting (GIT_COLOR_NEVER, etc). */
@@ -80,20 +121,12 @@ static enum git_colorbool use_sideband_colors(void)
121 if (use_sideband_colors_cached != GIT_COLOR_UNKNOWN)
122 return use_sideband_colors_cached;
123
83 - switch (repo_config_get_maybe_bool(the_repository, "sideband.allowcontrolcharacters", &i)) {
84 - case 0: /* Boolean value */
85 - allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS :
86 - ALLOW_NO_CONTROL_CHARACTERS;
87 - break;
88 - case -1: /* non-Boolean value */
89 - if (repo_config_get_string_tmp(the_repository, "sideband.allowcontrolcharacters",
90 - &value))
91 - ; /* huh? `get_maybe_bool()` returned -1 */
92 - else
93 - parse_allow_control_characters(value);
94 - break;
95 - default:
96 - break; /* not configured */
124 + if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET) {
125 + if (!repo_config_get_value(the_repository, "sideband.allowcontrolcharacters", &value))
126 + sideband_allow_control_characters_config("sideband.allowcontrolcharacters", value);
127 +
128 + if (allow_control_characters == ALLOW_CONTROL_SEQUENCES_UNSET)
129 + allow_control_characters = ALLOW_DEFAULT_ANSI_SEQUENCES;
130 }
131
132 if (!repo_config_get_string_tmp(the_repository, key, &value))
sideband.h
+14
@@ -30,4 +30,18 @@ int demultiplex_sideband(const char *me, int status,
30
31 void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
32
33 +/*
34 + * Apply sideband configuration for the given URL. This should be called
35 + * when a transport is created to allow URL-specific configuration of
36 + * sideband behavior (e.g., sideband.<url>.allowControlCharacters).
37 + */
38 +void sideband_apply_url_config(const char *url);
39 +
40 +/*
41 + * Parse and set the sideband allow control characters configuration.
42 + * The var parameter should be the key name (without section prefix).
43 + * Returns 0 if the variable was recognized and handled, non-zero otherwise.
44 + */
45 +int sideband_allow_control_characters_config(const char *var, const char *value);
46 +
47 #endif
t/t5409-colorize-remote-messages.sh
+24
@@ -166,4 +166,28 @@ test_expect_success 'control sequences in sideband allowed by default' '
166 test_grep ! "\\^\\[\\[G" decoded
167 '
168
169 +test_expect_success 'allow all control sequences for a specific URL' '
170 + write_script .git/eraser <<-\EOF &&
171 + printf "error: Ohai!\\r\\033[K" >&2
172 + exec "$@"
173 + EOF
174 + test_config_global uploadPack.packObjectsHook ./eraser &&
175 + test_commit one-more-please &&
176 +
177 + rm -rf throw-away &&
178 + git clone --no-local . throw-away 2>stderr &&
179 + test_decode_color <stderr >color-decoded &&
180 + test_decode_csi <color-decoded >decoded &&
181 + test_grep ! "CSI \\[K" decoded &&
182 + test_grep "\\^\\[\\[K" decoded &&
183 +
184 + rm -rf throw-away &&
185 + git -c "sideband.file://.allowControlCharacters=true" \
186 + clone --no-local "file://$PWD" throw-away 2>stderr &&
187 + test_decode_color <stderr >color-decoded &&
188 + test_decode_csi <color-decoded >decoded &&
189 + test_grep "CSI \\[K" decoded &&
190 + test_grep ! "\\^\\[\\[K" decoded
191 +'
192 +
193 test_done
transport.c
+3
@@ -29,6 +29,7 @@
29 #include "object-name.h"
30 #include "color.h"
31 #include "bundle-uri.h"
32 +#include "sideband.h"
33
34 static enum git_colorbool transport_use_color = GIT_COLOR_UNKNOWN;
35 static char transport_colors[][COLOR_MAXLEN] = {
@@ -1245,6 +1246,8 @@ struct transport *transport_get(struct remote *remote, const char *url)
1246
1247 ret->hash_algo = &hash_algos[GIT_HASH_SHA1_LEGACY];
1248
1249 + sideband_apply_url_config(ret->url);
1250 +
1251 return ret;
1252 }
1253