Git development
 help / color / mirror / Atom feed
* [PATCH 0/3] http: fix emptyAuth=auto for Negotiate/SPNEGO
@ 2026-04-16  9:20 Matthew John Cheetham via GitGitGadget
  2026-04-16  9:20 ` [PATCH 1/3] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-16  9:20 UTC (permalink / raw)
  To: git; +Cc: gitster, johannes.schindelin, Matthew John Cheetham

When a server advertises Negotiate (SPNEGO) authentication alongside Basic,
the "auto" mode of http.emptyAuth should allow libcurl to attempt Kerberos
authentication using the system ticket cache before falling back to
credential_fill(). Currently this never happens due to an interaction
between two older features.

The Negotiate-stripping logic from 4dbe66464b (remote-curl: fall back to
Basic auth if Negotiate fails, 2015-01-08) removes CURLAUTH_GSSNEGOTIATE on
the first 401, before the auto-detection from 40a18fc77c (http: add an
"auto" mode for http.emptyauth, 2017-02-25) gets a chance to see it as an
"exotic" method. The result is that auto mode silently degrades to the same
behavior as emptyAuth=false for any server whose only non-Basic/Digest
method is Negotiate, forcing Kerberos users to manually set
http.emptyAuth=true to get seamless ticket-based authentication.

This series fixes the interaction by delaying the Negotiate stripping in
auto mode by one round-trip, giving empty auth a chance to use the system
Kerberos ticket. If there is no valid ticket, Negotiate is stripped on the
second 401 and we fall through to credential_fill() as before. The true and
false modes are unchanged.

Patch 1: Extract a http_reauth_prepare() helper from the three retry paths
that call credential_fill() on HTTP_REAUTH. Pure refactor, no behavior
change.

Patch 2: Delay the GSSNEGOTIATE stripping in auto mode and teach
http_reauth_prepare() to skip credential_fill() when empty auth should be
attempted first.

Patch 3: Add tests verifying that auto mode produces an extra round-trip
(empty auth attempt) compared to false mode, using the existing
nph-custom-auth.sh CGI infrastructure.

There is a trade-off in auto mode: when a server advertises Negotiate but
the client has no valid Kerberos ticket, there is one extra round-trip
compared to the current behavior. This matches the trade-off already
documented in 40a18fc77c. Users who want to avoid it can set
http.emptyAuth=false.

Note: this patch series was taken early into Git for Windows for the
2.54.0-rc2 release.
https://github.com/git-for-windows/git/commit/8e94b65c003783d7d7b09d9fccdf06a1363e347c

Matthew John Cheetham (3):
  http: extract http_reauth_prepare() from retry paths
  http: attempt Negotiate auth in http.emptyAuth=auto mode
  t5563: add tests for http.emptyAuth with Negotiate

 http.c                      | 32 +++++++++++++++-
 http.h                      |  6 +++
 remote-curl.c               |  4 +-
 t/t5563-simple-http-auth.sh | 74 +++++++++++++++++++++++++++++++++++++
 4 files changed, 112 insertions(+), 4 deletions(-)


base-commit: 2b39a27d40682c09ac1c031f099ee602061597cd
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2087%2Fmjcheetham%2Fspnego-fix-upstream-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2087/mjcheetham/spnego-fix-upstream-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2087
-- 
gitgitgadget

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

* [PATCH 1/3] http: extract http_reauth_prepare() from retry paths
  2026-04-16  9:20 [PATCH 0/3] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
@ 2026-04-16  9:20 ` Matthew John Cheetham via GitGitGadget
  2026-04-16 16:21   ` Junio C Hamano
  2026-04-16  9:20 ` [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-16  9:20 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

All three HTTP retry paths (http_request_recoverable, post_rpc,
probe_rpc) call credential_fill() directly when handling
HTTP_REAUTH. Extract this into a helper function so that a
subsequent commit can add pre-fill logic (such as attempting
empty-auth before prompting) in one place.

No functional change.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 http.c        | 7 ++++++-
 http.h        | 6 ++++++
 remote-curl.c | 4 ++--
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/http.c b/http.c
index d8d016891b..f208e0ad82 100644
--- a/http.c
+++ b/http.c
@@ -665,6 +665,11 @@ static void init_curl_http_auth(CURL *result)
 	}
 }
 
+void http_reauth_prepare(int all_capabilities)
+{
+	credential_fill(the_repository, &http_auth, all_capabilities);
+}
+
 /* *var must be free-able */
 static void var_override(char **var, char *value)
 {
@@ -2398,7 +2403,7 @@ static int http_request_recoverable(const char *url,
 				sleep(retry_delay);
 			}
 		} else if (ret == HTTP_REAUTH) {
-			credential_fill(the_repository, &http_auth, 1);
+			http_reauth_prepare(1);
 		}
 
 		ret = http_request(url, result, target, options);
diff --git a/http.h b/http.h
index f9ee888c3e..729c51904d 100644
--- a/http.h
+++ b/http.h
@@ -76,6 +76,12 @@ extern int http_is_verbose;
 extern ssize_t http_post_buffer;
 extern struct credential http_auth;
 
+/**
+ * Prepare for an HTTP re-authentication retry. This fills credentials
+ * via credential_fill() so the next request can include them.
+ */
+void http_reauth_prepare(int all_capabilities);
+
 extern char curl_errorstr[CURL_ERROR_SIZE];
 
 enum http_follow_config {
diff --git a/remote-curl.c b/remote-curl.c
index aba60d5712..affdb880f7 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -946,7 +946,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
 		do {
 			err = probe_rpc(rpc, &results);
 			if (err == HTTP_REAUTH)
-				credential_fill(the_repository, &http_auth, 0);
+				http_reauth_prepare(0);
 		} while (err == HTTP_REAUTH);
 		if (err != HTTP_OK)
 			return -1;
@@ -1068,7 +1068,7 @@ retry:
 	rpc->any_written = 0;
 	err = run_slot(slot, NULL);
 	if (err == HTTP_REAUTH && !large_request) {
-		credential_fill(the_repository, &http_auth, 0);
+		http_reauth_prepare(0);
 		curl_slist_free_all(headers);
 		goto retry;
 	}
-- 
gitgitgadget


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

* [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode
  2026-04-16  9:20 [PATCH 0/3] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
  2026-04-16  9:20 ` [PATCH 1/3] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
@ 2026-04-16  9:20 ` Matthew John Cheetham via GitGitGadget
  2026-04-16 16:40   ` Junio C Hamano
  2026-04-16  9:20 ` [PATCH 3/3] t5563: add tests for http.emptyAuth with Negotiate Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
  3 siblings, 1 reply; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-16  9:20 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

When a server advertises Negotiate (SPNEGO) authentication, the
"auto" mode of http.emptyAuth should detect this as an "exotic"
method and proactively send empty credentials, allowing libcurl to
use the system Kerberos ticket without prompting the user.

However, two features interact to prevent this from working:

The Negotiate-stripping logic, introduced in 4dbe66464b
(remote-curl: fall back to Basic auth if Negotiate fails,
2015-01-08), removes CURLAUTH_GSSNEGOTIATE from the allowed
methods on the first 401 response. The empty-auth auto-detection,
introduced in 40a18fc77c (http: add an "auto" mode for
http.emptyauth, 2017-02-25), then checks the remaining methods
for anything "exotic" -- but Negotiate has already been removed,
so auto mode never activates for servers whose only non-Basic/Digest
method is Negotiate (e.g., Apache with mod_auth_kerb offering
Basic + Negotiate).

Fix this by delaying the Negotiate stripping in auto mode: on the
first 401, keep Negotiate in the allowed methods so that auto mode
can detect it and retry with empty credentials. If that attempt
fails (no valid Kerberos ticket), strip Negotiate on the second 401
and fall through to credential_fill() as usual.

To support this, also teach http_reauth_prepare() to skip
credential_fill() when empty auth is about to be attempted, since
filling real credentials would bypass the empty-auth mechanism.

The true and false modes are unchanged: true sends empty credentials
on the very first request (before any 401), and false never sends
them.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 http.c | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/http.c b/http.c
index f208e0ad82..1c7ea32ef2 100644
--- a/http.c
+++ b/http.c
@@ -138,6 +138,7 @@ static unsigned long empty_auth_useless =
 	CURLAUTH_BASIC
 	| CURLAUTH_DIGEST_IE
 	| CURLAUTH_DIGEST;
+static int empty_auth_try_negotiate;
 
 static struct curl_slist *pragma_header;
 static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;
@@ -667,6 +668,17 @@ static void init_curl_http_auth(CURL *result)
 
 void http_reauth_prepare(int all_capabilities)
 {
+	/*
+	 * If we deferred stripping Negotiate to give empty auth a
+	 * chance (auto mode), skip credential_fill on this retry so
+	 * that init_curl_http_auth() sends empty credentials and
+	 * libcurl can attempt Negotiate with the system ticket cache.
+	 */
+	if (empty_auth_try_negotiate &&
+	    !http_auth.password && !http_auth.credential &&
+	    (http_auth_methods & CURLAUTH_GSSNEGOTIATE))
+		return;
+
 	credential_fill(the_repository, &http_auth, all_capabilities);
 }
 
@@ -1895,7 +1907,18 @@ static int handle_curl_result(struct slot_results *results)
 				http_proactive_auth = PROACTIVE_AUTH_NONE;
 			return HTTP_NOAUTH;
 		} else {
-			http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
+			if (curl_empty_auth == -1 &&
+			    !empty_auth_try_negotiate &&
+			    (results->auth_avail & CURLAUTH_GSSNEGOTIATE)) {
+				/*
+				 * In auto mode, give Negotiate a chance via
+				 * empty auth before stripping it. If it fails,
+				 * we will strip it on the next 401.
+				 */
+				empty_auth_try_negotiate = 1;
+			} else {
+				http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
+			}
 			if (results->auth_avail) {
 				http_auth_methods &= results->auth_avail;
 				http_auth_methods_restricted = 1;
-- 
gitgitgadget


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

* [PATCH 3/3] t5563: add tests for http.emptyAuth with Negotiate
  2026-04-16  9:20 [PATCH 0/3] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
  2026-04-16  9:20 ` [PATCH 1/3] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
  2026-04-16  9:20 ` [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
@ 2026-04-16  9:20 ` Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
  3 siblings, 0 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-16  9:20 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

Add tests exercising the interaction between http.emptyAuth and
servers that advertise Negotiate (SPNEGO) authentication.

Verify that auto mode gives Negotiate a chance via empty auth
(resulting in two 401 responses before falling through to
credential_fill with Basic credentials), and that false mode
strips Negotiate immediately (only one 401 response).

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 t/t5563-simple-http-auth.sh | 74 +++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh
index 0063581615..a7d475dd68 100755
--- a/t/t5563-simple-http-auth.sh
+++ b/t/t5563-simple-http-auth.sh
@@ -719,4 +719,78 @@ test_expect_success 'access using three-legged auth' '
 	EOF
 '
 
+test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"'
+
+test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' '
+	test_when_finished "per_test_cleanup" &&
+
+	set_credential_reply get <<-EOF &&
+	username=alice
+	password=secret-passwd
+	EOF
+
+	# Basic base64(alice:secret-passwd)
+	cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
+	id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
+	EOF
+
+	cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
+	id=1 status=200
+	id=default response=WWW-Authenticate: Negotiate
+	id=default response=WWW-Authenticate: Basic realm="example.com"
+	EOF
+
+	test_config_global credential.helper test-helper &&
+	GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \
+		git -c http.emptyAuth=auto \
+		ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
+
+	# In auto mode with a Negotiate+Basic server, there should be
+	# three 401 responses: (1) initial no-auth request, (2) empty-auth
+	# retry where Negotiate fails (no Kerberos ticket), (3) libcurl
+	# internal Negotiate retry. The fourth attempt uses Basic
+	# credentials from credential_fill and succeeds.
+	grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s &&
+	test_line_count = 3 actual_401s &&
+
+	expect_credential_query get <<-EOF
+	capability[]=authtype
+	capability[]=state
+	protocol=http
+	host=$HTTPD_DEST
+	wwwauth[]=Negotiate
+	wwwauth[]=Basic realm="example.com"
+	EOF
+'
+
+test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' '
+	test_when_finished "per_test_cleanup" &&
+
+	set_credential_reply get <<-EOF &&
+	username=alice
+	password=secret-passwd
+	EOF
+
+	# Basic base64(alice:secret-passwd)
+	cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
+	id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
+	EOF
+
+	cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
+	id=1 status=200
+	id=default response=WWW-Authenticate: Negotiate
+	id=default response=WWW-Authenticate: Basic realm="example.com"
+	EOF
+
+	test_config_global credential.helper test-helper &&
+	GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \
+		git -c http.emptyAuth=false \
+		ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
+
+	# With emptyAuth=false, Negotiate is stripped immediately and
+	# credential_fill is called right away. Only one 401 response.
+	grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s &&
+	test_line_count = 1 actual_401s
+'
+
 test_done
-- 
gitgitgadget

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

* Re: [PATCH 1/3] http: extract http_reauth_prepare() from retry paths
  2026-04-16  9:20 ` [PATCH 1/3] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
@ 2026-04-16 16:21   ` Junio C Hamano
  0 siblings, 0 replies; 13+ messages in thread
From: Junio C Hamano @ 2026-04-16 16:21 UTC (permalink / raw)
  To: Matthew John Cheetham via GitGitGadget
  Cc: git, johannes.schindelin, Matthew John Cheetham

"Matthew John Cheetham via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Matthew John Cheetham <mjcheetham@outlook.com>
>
> All three HTTP retry paths (http_request_recoverable, post_rpc,
> probe_rpc) call credential_fill() directly when handling
> HTTP_REAUTH. Extract this into a helper function so that a
> subsequent commit can add pre-fill logic (such as attempting
> empty-auth before prompting) in one place.
>
> No functional change.
>
> Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
> ---
>  http.c        | 7 ++++++-
>  http.h        | 6 ++++++
>  remote-curl.c | 4 ++--
>  3 files changed, 14 insertions(+), 3 deletions(-)

Neat.

>
> diff --git a/http.c b/http.c
> index d8d016891b..f208e0ad82 100644
> --- a/http.c
> +++ b/http.c
> @@ -665,6 +665,11 @@ static void init_curl_http_auth(CURL *result)
>  	}
>  }
>  
> +void http_reauth_prepare(int all_capabilities)
> +{
> +	credential_fill(the_repository, &http_auth, all_capabilities);
> +}
> +
>  /* *var must be free-able */
>  static void var_override(char **var, char *value)
>  {
> @@ -2398,7 +2403,7 @@ static int http_request_recoverable(const char *url,
>  				sleep(retry_delay);
>  			}
>  		} else if (ret == HTTP_REAUTH) {
> -			credential_fill(the_repository, &http_auth, 1);
> +			http_reauth_prepare(1);
>  		}
>  
>  		ret = http_request(url, result, target, options);
> diff --git a/http.h b/http.h
> index f9ee888c3e..729c51904d 100644
> --- a/http.h
> +++ b/http.h
> @@ -76,6 +76,12 @@ extern int http_is_verbose;
>  extern ssize_t http_post_buffer;
>  extern struct credential http_auth;
>  
> +/**
> + * Prepare for an HTTP re-authentication retry. This fills credentials
> + * via credential_fill() so the next request can include them.
> + */
> +void http_reauth_prepare(int all_capabilities);
> +
>  extern char curl_errorstr[CURL_ERROR_SIZE];
>  
>  enum http_follow_config {
> diff --git a/remote-curl.c b/remote-curl.c
> index aba60d5712..affdb880f7 100644
> --- a/remote-curl.c
> +++ b/remote-curl.c
> @@ -946,7 +946,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
>  		do {
>  			err = probe_rpc(rpc, &results);
>  			if (err == HTTP_REAUTH)
> -				credential_fill(the_repository, &http_auth, 0);
> +				http_reauth_prepare(0);
>  		} while (err == HTTP_REAUTH);
>  		if (err != HTTP_OK)
>  			return -1;
> @@ -1068,7 +1068,7 @@ retry:
>  	rpc->any_written = 0;
>  	err = run_slot(slot, NULL);
>  	if (err == HTTP_REAUTH && !large_request) {
> -		credential_fill(the_repository, &http_auth, 0);
> +		http_reauth_prepare(0);
>  		curl_slist_free_all(headers);
>  		goto retry;
>  	}

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

* Re: [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode
  2026-04-16  9:20 ` [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
@ 2026-04-16 16:40   ` Junio C Hamano
  2026-04-28 14:38     ` Matthew John Cheetham
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2026-04-16 16:40 UTC (permalink / raw)
  To: Matthew John Cheetham via GitGitGadget
  Cc: git, johannes.schindelin, Matthew John Cheetham

"Matthew John Cheetham via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> From: Matthew John Cheetham <mjcheetham@outlook.com>
>
> When a server advertises Negotiate (SPNEGO) authentication, the
> "auto" mode of http.emptyAuth should detect this as an "exotic"
> method and proactively send empty credentials, allowing libcurl to
> use the system Kerberos ticket without prompting the user.
>
> However, two features interact to prevent this from working:
>
> The Negotiate-stripping logic, introduced in 4dbe66464b
> (remote-curl: fall back to Basic auth if Negotiate fails,
> 2015-01-08), removes CURLAUTH_GSSNEGOTIATE from the allowed
> methods on the first 401 response. The empty-auth auto-detection,
> introduced in 40a18fc77c (http: add an "auto" mode for
> http.emptyauth, 2017-02-25), then checks the remaining methods
> for anything "exotic" -- but Negotiate has already been removed,
> so auto mode never activates for servers whose only non-Basic/Digest
> method is Negotiate (e.g., Apache with mod_auth_kerb offering
> Basic + Negotiate).

Well explained.

> Fix this by delaying the Negotiate stripping in auto mode: on the
> first 401, keep Negotiate in the allowed methods so that auto mode
> can detect it and retry with empty credentials. If that attempt
> fails (no valid Kerberos ticket), strip Negotiate on the second 401
> and fall through to credential_fill() as usual.

OK, succeeding after two attempts is much better than failing after
only one attempt.

> To support this, also teach http_reauth_prepare() to skip
> credential_fill() when empty auth is about to be attempted, since
> filling real credentials would bypass the empty-auth mechanism.

And that is why the previous step shines.  Very neat.

> The true and false modes are unchanged: true sends empty credentials
> on the very first request (before any 401), and false never sends
> them.

OK.  This is a tangent, but "git config --help" on "http.emptyAuth"
is horrible.  It does not say what the allowed values are, so I had
to first write "There are million other things in the system that
this patch does not modify, so what's the point of singling out
these two settings and saying that this patch does not change
them?", before realizing that 'auto' the patch (and the explanation
of the "empty-autho auto-detction" above) is about the third
possiblity of the same variable and take it back.

> Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
> ---
>  http.c | 25 ++++++++++++++++++++++++-
>  1 file changed, 24 insertions(+), 1 deletion(-)
>
> diff --git a/http.c b/http.c
> index f208e0ad82..1c7ea32ef2 100644
> --- a/http.c
> +++ b/http.c
> @@ -138,6 +138,7 @@ static unsigned long empty_auth_useless =
>  	CURLAUTH_BASIC
>  	| CURLAUTH_DIGEST_IE
>  	| CURLAUTH_DIGEST;
> +static int empty_auth_try_negotiate;
>  
>  static struct curl_slist *pragma_header;
>  static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;

I guess the existing code already assumes that we connect to a
single destination, run a single "session", and then die, so it is
in line with the existing design to have a file-scope global keep
track of our "state".  In the longer run we may want to move these
things to a struct so that we can run multiple sessions without
having to kill ourselves and restart, but that is totally outside
the topic of these patches to fix the negotiate auth.

> @@ -667,6 +668,17 @@ static void init_curl_http_auth(CURL *result)
>  
>  void http_reauth_prepare(int all_capabilities)
>  {
> +	/*
> +	 * If we deferred stripping Negotiate to give empty auth a
> +	 * chance (auto mode), skip credential_fill on this retry so
> +	 * that init_curl_http_auth() sends empty credentials and
> +	 * libcurl can attempt Negotiate with the system ticket cache.
> +	 */
> +	if (empty_auth_try_negotiate &&
> +	    !http_auth.password && !http_auth.credential &&
> +	    (http_auth_methods & CURLAUTH_GSSNEGOTIATE))
> +		return;
> +
>  	credential_fill(the_repository, &http_auth, all_capabilities);
>  }
>  
> @@ -1895,7 +1907,18 @@ static int handle_curl_result(struct slot_results *results)
>  				http_proactive_auth = PROACTIVE_AUTH_NONE;
>  			return HTTP_NOAUTH;
>  		} else {
> -			http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
> +			if (curl_empty_auth == -1 &&
> +			    !empty_auth_try_negotiate &&
> +			    (results->auth_avail & CURLAUTH_GSSNEGOTIATE)) {
> +				/*
> +				 * In auto mode, give Negotiate a chance via
> +				 * empty auth before stripping it. If it fails,
> +				 * we will strip it on the next 401.
> +				 */
> +				empty_auth_try_negotiate = 1;
> +			} else {
> +				http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
> +			}
>  			if (results->auth_avail) {
>  				http_auth_methods &= results->auth_avail;
>  				http_auth_methods_restricted = 1;

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

* Re: [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode
  2026-04-16 16:40   ` Junio C Hamano
@ 2026-04-28 14:38     ` Matthew John Cheetham
       [not found]       ` <xmqqse8dz4pi.fsf@gitster.g>
  0 siblings, 1 reply; 13+ messages in thread
From: Matthew John Cheetham @ 2026-04-28 14:38 UTC (permalink / raw)
  To: Junio C Hamano, Matthew John Cheetham via GitGitGadget
  Cc: git, johannes.schindelin

On 2026-04-16 17:40, Junio C Hamano wrote:
>> The true and false modes are unchanged: true sends empty credentials
>> on the very first request (before any 401), and false never sends
>> them.
> 
> OK.  This is a tangent, but "git config --help" on "http.emptyAuth"
> is horrible.  It does not say what the allowed values are, so I had
> to first write "There are million other things in the system that
> this patch does not modify, so what's the point of singling out
> these two settings and saying that this patch does not change
> them?", before realizing that 'auto' the patch (and the explanation
> of the "empty-autho auto-detction" above) is about the third
> possiblity of the same variable and take it back.

Agreed - the existing description is pretty opaque about what values it
actually takes. Should I add another patch to this series to spell out
the three values explicitly? How about something like this:

      http.emptyAuth::
              Attempt authentication without seeking a username or
              password.  This can be used to attempt GSS-Negotiate
              authentication without specifying a username in the URL,
              as libcurl normally requires a username for
              authentication. Possible values are:
      +
      --
      * `auto` (default) - Send empty credentials only if the server's
        401 response advertises an authentication mechanism that
        requires them (such as GSS-Negotiate); otherwise fall back to
        prompting via the credential helper.
      * `true` - Always send empty credentials on the very first
        request, before receiving any 401 response from the server.
      * `false` - Never send empty credentials. Mechanisms that
        require empty credentials, such as GSS-Negotiate, will not
        work.
      --

  Does that read better?

  Thanks,
  Matthew


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

* Re: [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode
       [not found]       ` <xmqqse8dz4pi.fsf@gitster.g>
@ 2026-04-30 10:53         ` Matthew John Cheetham
  0 siblings, 0 replies; 13+ messages in thread
From: Matthew John Cheetham @ 2026-04-30 10:53 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

[re-cc:ing the accidentially dropped mailing list]

On 2026-04-30 01:12, Junio C Hamano wrote:

> Matthew John Cheetham <mjcheetham@outlook.com> writes:
> 
>> Agreed - the existing description is pretty opaque about what values it
>> actually takes. Should I add another patch to this series to spell out
>> the three values explicitly? How about something like this:
>>
>>        http.emptyAuth::
>>                Attempt authentication without seeking a username or
>>                password.  This can be used to attempt GSS-Negotiate
>>                authentication without specifying a username in the URL,
>>                as libcurl normally requires a username for
>>                authentication. Possible values are:
>>        +
>>        --
>>        * `auto` (default) - Send empty credentials only if the server's
>>          401 response advertises an authentication mechanism that
>>          requires them (such as GSS-Negotiate); otherwise fall back to
>>          prompting via the credential helper.
>>        * `true` - Always send empty credentials on the very first
>>          request, before receiving any 401 response from the server.
>>        * `false` - Never send empty credentials. Mechanisms that
>>          require empty credentials, such as GSS-Negotiate, will not
>>          work.
>>        --
>>
>>    Does that read better?
> 
> Surely.  Thanks.


Submitted as v2

Thanks,
Matthew


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

* [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO
  2026-04-16  9:20 [PATCH 0/3] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
                   ` (2 preceding siblings ...)
  2026-04-16  9:20 ` [PATCH 3/3] t5563: add tests for http.emptyAuth with Negotiate Matthew John Cheetham via GitGitGadget
@ 2026-04-30 10:54 ` Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 1/4] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
                     ` (3 more replies)
  3 siblings, 4 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-30 10:54 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham

When a server advertises Negotiate (SPNEGO) authentication alongside Basic,
the "auto" mode of http.emptyAuth should allow libcurl to attempt Kerberos
authentication using the system ticket cache before falling back to
credential_fill(). Currently this never happens due to an interaction
between two older features.

The Negotiate-stripping logic from 4dbe66464b (remote-curl: fall back to
Basic auth if Negotiate fails, 2015-01-08) removes CURLAUTH_GSSNEGOTIATE on
the first 401, before the auto-detection from 40a18fc77c (http: add an
"auto" mode for http.emptyauth, 2017-02-25) gets a chance to see it as an
"exotic" method. The result is that auto mode silently degrades to the same
behavior as emptyAuth=false for any server whose only non-Basic/Digest
method is Negotiate, forcing Kerberos users to manually set
http.emptyAuth=true to get seamless ticket-based authentication.

This series fixes the interaction by delaying the Negotiate stripping in
auto mode by one round-trip, giving empty auth a chance to use the system
Kerberos ticket. If there is no valid ticket, Negotiate is stripped on the
second 401 and we fall through to credential_fill() as before. The true and
false modes are unchanged.

Patch 1: Extract a http_reauth_prepare() helper from the three retry paths
that call credential_fill() on HTTP_REAUTH. Pure refactor, no behavior
change.

Patch 2: Delay the GSSNEGOTIATE stripping in auto mode and teach
http_reauth_prepare() to skip credential_fill() when empty auth should be
attempted first.

Patch 3: Add tests verifying that auto mode produces an extra round-trip
(empty auth attempt) compared to false mode, using the existing
nph-custom-auth.sh CGI infrastructure.

Patch 4: Update http.emptyAuth documentation to clarify possible values
(true, false, and auto).

There is a trade-off in auto mode: when a server advertises Negotiate but
the client has no valid Kerberos ticket, there is one extra round-trip
compared to the current behavior. This matches the trade-off already
documented in 40a18fc77c. Users who want to avoid it can set
http.emptyAuth=false.

Note: this patch series was taken early into Git for Windows for the
2.54.0-rc2 release.
https://github.com/git-for-windows/git/commit/8e94b65c003783d7d7b09d9fccdf06a1363e347c

----------------------------------------------------------------------------

Update in v2:

 * Add patch 4 to clarify the available options for http.emptyAuth in the
   config documentation.

Matthew John Cheetham (4):
  http: extract http_reauth_prepare() from retry paths
  http: attempt Negotiate auth in http.emptyAuth=auto mode
  t5563: add tests for http.emptyAuth with Negotiate
  doc: clarify http.emptyAuth values

 Documentation/config/http.adoc | 13 +++++-
 http.c                         | 32 ++++++++++++++-
 http.h                         |  6 +++
 remote-curl.c                  |  4 +-
 t/t5563-simple-http-auth.sh    | 74 ++++++++++++++++++++++++++++++++++
 5 files changed, 124 insertions(+), 5 deletions(-)


base-commit: 2b39a27d40682c09ac1c031f099ee602061597cd
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2087%2Fmjcheetham%2Fspnego-fix-upstream-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2087/mjcheetham/spnego-fix-upstream-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2087

Range-diff vs v1:

 1:  49488cc7d4 = 1:  49488cc7d4 http: extract http_reauth_prepare() from retry paths
 2:  f175294459 = 2:  f175294459 http: attempt Negotiate auth in http.emptyAuth=auto mode
 3:  650acab79e = 3:  650acab79e t5563: add tests for http.emptyAuth with Negotiate
 -:  ---------- > 4:  e0f236767f doc: clarify http.emptyAuth values

-- 
gitgitgadget

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

* [PATCH v2 1/4] http: extract http_reauth_prepare() from retry paths
  2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
@ 2026-04-30 10:54   ` Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 2/4] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-30 10:54 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham, Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

All three HTTP retry paths (http_request_recoverable, post_rpc,
probe_rpc) call credential_fill() directly when handling
HTTP_REAUTH. Extract this into a helper function so that a
subsequent commit can add pre-fill logic (such as attempting
empty-auth before prompting) in one place.

No functional change.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 http.c        | 7 ++++++-
 http.h        | 6 ++++++
 remote-curl.c | 4 ++--
 3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/http.c b/http.c
index d8d016891b..f208e0ad82 100644
--- a/http.c
+++ b/http.c
@@ -665,6 +665,11 @@ static void init_curl_http_auth(CURL *result)
 	}
 }
 
+void http_reauth_prepare(int all_capabilities)
+{
+	credential_fill(the_repository, &http_auth, all_capabilities);
+}
+
 /* *var must be free-able */
 static void var_override(char **var, char *value)
 {
@@ -2398,7 +2403,7 @@ static int http_request_recoverable(const char *url,
 				sleep(retry_delay);
 			}
 		} else if (ret == HTTP_REAUTH) {
-			credential_fill(the_repository, &http_auth, 1);
+			http_reauth_prepare(1);
 		}
 
 		ret = http_request(url, result, target, options);
diff --git a/http.h b/http.h
index f9ee888c3e..729c51904d 100644
--- a/http.h
+++ b/http.h
@@ -76,6 +76,12 @@ extern int http_is_verbose;
 extern ssize_t http_post_buffer;
 extern struct credential http_auth;
 
+/**
+ * Prepare for an HTTP re-authentication retry. This fills credentials
+ * via credential_fill() so the next request can include them.
+ */
+void http_reauth_prepare(int all_capabilities);
+
 extern char curl_errorstr[CURL_ERROR_SIZE];
 
 enum http_follow_config {
diff --git a/remote-curl.c b/remote-curl.c
index aba60d5712..affdb880f7 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -946,7 +946,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece
 		do {
 			err = probe_rpc(rpc, &results);
 			if (err == HTTP_REAUTH)
-				credential_fill(the_repository, &http_auth, 0);
+				http_reauth_prepare(0);
 		} while (err == HTTP_REAUTH);
 		if (err != HTTP_OK)
 			return -1;
@@ -1068,7 +1068,7 @@ retry:
 	rpc->any_written = 0;
 	err = run_slot(slot, NULL);
 	if (err == HTTP_REAUTH && !large_request) {
-		credential_fill(the_repository, &http_auth, 0);
+		http_reauth_prepare(0);
 		curl_slist_free_all(headers);
 		goto retry;
 	}
-- 
gitgitgadget


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

* [PATCH v2 2/4] http: attempt Negotiate auth in http.emptyAuth=auto mode
  2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 1/4] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
@ 2026-04-30 10:54   ` Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 3/4] t5563: add tests for http.emptyAuth with Negotiate Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 4/4] doc: clarify http.emptyAuth values Matthew John Cheetham via GitGitGadget
  3 siblings, 0 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-30 10:54 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham, Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

When a server advertises Negotiate (SPNEGO) authentication, the
"auto" mode of http.emptyAuth should detect this as an "exotic"
method and proactively send empty credentials, allowing libcurl to
use the system Kerberos ticket without prompting the user.

However, two features interact to prevent this from working:

The Negotiate-stripping logic, introduced in 4dbe66464b
(remote-curl: fall back to Basic auth if Negotiate fails,
2015-01-08), removes CURLAUTH_GSSNEGOTIATE from the allowed
methods on the first 401 response. The empty-auth auto-detection,
introduced in 40a18fc77c (http: add an "auto" mode for
http.emptyauth, 2017-02-25), then checks the remaining methods
for anything "exotic" -- but Negotiate has already been removed,
so auto mode never activates for servers whose only non-Basic/Digest
method is Negotiate (e.g., Apache with mod_auth_kerb offering
Basic + Negotiate).

Fix this by delaying the Negotiate stripping in auto mode: on the
first 401, keep Negotiate in the allowed methods so that auto mode
can detect it and retry with empty credentials. If that attempt
fails (no valid Kerberos ticket), strip Negotiate on the second 401
and fall through to credential_fill() as usual.

To support this, also teach http_reauth_prepare() to skip
credential_fill() when empty auth is about to be attempted, since
filling real credentials would bypass the empty-auth mechanism.

The true and false modes are unchanged: true sends empty credentials
on the very first request (before any 401), and false never sends
them.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 http.c | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/http.c b/http.c
index f208e0ad82..1c7ea32ef2 100644
--- a/http.c
+++ b/http.c
@@ -138,6 +138,7 @@ static unsigned long empty_auth_useless =
 	CURLAUTH_BASIC
 	| CURLAUTH_DIGEST_IE
 	| CURLAUTH_DIGEST;
+static int empty_auth_try_negotiate;
 
 static struct curl_slist *pragma_header;
 static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;
@@ -667,6 +668,17 @@ static void init_curl_http_auth(CURL *result)
 
 void http_reauth_prepare(int all_capabilities)
 {
+	/*
+	 * If we deferred stripping Negotiate to give empty auth a
+	 * chance (auto mode), skip credential_fill on this retry so
+	 * that init_curl_http_auth() sends empty credentials and
+	 * libcurl can attempt Negotiate with the system ticket cache.
+	 */
+	if (empty_auth_try_negotiate &&
+	    !http_auth.password && !http_auth.credential &&
+	    (http_auth_methods & CURLAUTH_GSSNEGOTIATE))
+		return;
+
 	credential_fill(the_repository, &http_auth, all_capabilities);
 }
 
@@ -1895,7 +1907,18 @@ static int handle_curl_result(struct slot_results *results)
 				http_proactive_auth = PROACTIVE_AUTH_NONE;
 			return HTTP_NOAUTH;
 		} else {
-			http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
+			if (curl_empty_auth == -1 &&
+			    !empty_auth_try_negotiate &&
+			    (results->auth_avail & CURLAUTH_GSSNEGOTIATE)) {
+				/*
+				 * In auto mode, give Negotiate a chance via
+				 * empty auth before stripping it. If it fails,
+				 * we will strip it on the next 401.
+				 */
+				empty_auth_try_negotiate = 1;
+			} else {
+				http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
+			}
 			if (results->auth_avail) {
 				http_auth_methods &= results->auth_avail;
 				http_auth_methods_restricted = 1;
-- 
gitgitgadget


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

* [PATCH v2 3/4] t5563: add tests for http.emptyAuth with Negotiate
  2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 1/4] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 2/4] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
@ 2026-04-30 10:54   ` Matthew John Cheetham via GitGitGadget
  2026-04-30 10:54   ` [PATCH v2 4/4] doc: clarify http.emptyAuth values Matthew John Cheetham via GitGitGadget
  3 siblings, 0 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-30 10:54 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham, Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

Add tests exercising the interaction between http.emptyAuth and
servers that advertise Negotiate (SPNEGO) authentication.

Verify that auto mode gives Negotiate a chance via empty auth
(resulting in two 401 responses before falling through to
credential_fill with Basic credentials), and that false mode
strips Negotiate immediately (only one 401 response).

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 t/t5563-simple-http-auth.sh | 74 +++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh
index 0063581615..a7d475dd68 100755
--- a/t/t5563-simple-http-auth.sh
+++ b/t/t5563-simple-http-auth.sh
@@ -719,4 +719,78 @@ test_expect_success 'access using three-legged auth' '
 	EOF
 '
 
+test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"'
+
+test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' '
+	test_when_finished "per_test_cleanup" &&
+
+	set_credential_reply get <<-EOF &&
+	username=alice
+	password=secret-passwd
+	EOF
+
+	# Basic base64(alice:secret-passwd)
+	cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
+	id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
+	EOF
+
+	cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
+	id=1 status=200
+	id=default response=WWW-Authenticate: Negotiate
+	id=default response=WWW-Authenticate: Basic realm="example.com"
+	EOF
+
+	test_config_global credential.helper test-helper &&
+	GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \
+		git -c http.emptyAuth=auto \
+		ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
+
+	# In auto mode with a Negotiate+Basic server, there should be
+	# three 401 responses: (1) initial no-auth request, (2) empty-auth
+	# retry where Negotiate fails (no Kerberos ticket), (3) libcurl
+	# internal Negotiate retry. The fourth attempt uses Basic
+	# credentials from credential_fill and succeeds.
+	grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s &&
+	test_line_count = 3 actual_401s &&
+
+	expect_credential_query get <<-EOF
+	capability[]=authtype
+	capability[]=state
+	protocol=http
+	host=$HTTPD_DEST
+	wwwauth[]=Negotiate
+	wwwauth[]=Basic realm="example.com"
+	EOF
+'
+
+test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' '
+	test_when_finished "per_test_cleanup" &&
+
+	set_credential_reply get <<-EOF &&
+	username=alice
+	password=secret-passwd
+	EOF
+
+	# Basic base64(alice:secret-passwd)
+	cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
+	id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
+	EOF
+
+	cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
+	id=1 status=200
+	id=default response=WWW-Authenticate: Negotiate
+	id=default response=WWW-Authenticate: Basic realm="example.com"
+	EOF
+
+	test_config_global credential.helper test-helper &&
+	GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \
+		git -c http.emptyAuth=false \
+		ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
+
+	# With emptyAuth=false, Negotiate is stripped immediately and
+	# credential_fill is called right away. Only one 401 response.
+	grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s &&
+	test_line_count = 1 actual_401s
+'
+
 test_done
-- 
gitgitgadget


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

* [PATCH v2 4/4] doc: clarify http.emptyAuth values
  2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
                     ` (2 preceding siblings ...)
  2026-04-30 10:54   ` [PATCH v2 3/4] t5563: add tests for http.emptyAuth with Negotiate Matthew John Cheetham via GitGitGadget
@ 2026-04-30 10:54   ` Matthew John Cheetham via GitGitGadget
  3 siblings, 0 replies; 13+ messages in thread
From: Matthew John Cheetham via GitGitGadget @ 2026-04-30 10:54 UTC (permalink / raw)
  To: git
  Cc: gitster, johannes.schindelin, Matthew John Cheetham,
	Matthew John Cheetham, Matthew John Cheetham

From: Matthew John Cheetham <mjcheetham@outlook.com>

The existing description of http.emptyAuth explains the purpose of the
setting but never says what values it accepts. Readers have to infer
from context (or read the source) that it takes 'true', 'false', or
'auto', and what each one means.

Document the three accepted values explicitly:

* 'auto' (the default) only sends empty credentials when the server's
  401 response advertises a mechanism that requires them, such as
  GSS-Negotiate. This matches the long-standing auto-detection
  behaviour added in 40a18fc77c (http: add an "auto" mode for
  http.emptyauth, 2017-02-25).

* 'true' unconditionally sends empty credentials on the very first
  request, before any 401 response, for callers that know they want
  this behaviour up front.

* 'false' disables the feature entirely; mechanisms that depend on
  empty credentials, such as GSS-Negotiate, will not work in this
  mode.

Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
---
 Documentation/config/http.adoc | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc
index 849c89f36c..792a71b413 100644
--- a/Documentation/config/http.adoc
+++ b/Documentation/config/http.adoc
@@ -59,7 +59,18 @@ http.emptyAuth::
 	Attempt authentication without seeking a username or password.  This
 	can be used to attempt GSS-Negotiate authentication without specifying
 	a username in the URL, as libcurl normally requires a username for
-	authentication.
+	authentication. Possible values are:
++
+--
+* `auto` (default) - Send empty credentials only if the server's 401 response
+  advertises an authentication mechanism that requires them (such as
+  GSS-Negotiate); otherwise fall back to prompting via the credential helper.
+* `true` - Always send empty credentials on the very first request, before
+  receiving any 401 response from the server.
+* `false` - Never send empty credentials. Mechanisms that require
+  empty credentials or an explicit username, such as GSS-Negotiate, will not
+  work.
+--
 
 http.proactiveAuth::
 	Attempt authentication without first making an unauthenticated attempt and
-- 
gitgitgadget

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

end of thread, other threads:[~2026-04-30 10:54 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-16  9:20 [PATCH 0/3] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
2026-04-16  9:20 ` [PATCH 1/3] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
2026-04-16 16:21   ` Junio C Hamano
2026-04-16  9:20 ` [PATCH 2/3] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
2026-04-16 16:40   ` Junio C Hamano
2026-04-28 14:38     ` Matthew John Cheetham
     [not found]       ` <xmqqse8dz4pi.fsf@gitster.g>
2026-04-30 10:53         ` Matthew John Cheetham
2026-04-16  9:20 ` [PATCH 3/3] t5563: add tests for http.emptyAuth with Negotiate Matthew John Cheetham via GitGitGadget
2026-04-30 10:54 ` [PATCH v2 0/4] http: fix emptyAuth=auto for Negotiate/SPNEGO Matthew John Cheetham via GitGitGadget
2026-04-30 10:54   ` [PATCH v2 1/4] http: extract http_reauth_prepare() from retry paths Matthew John Cheetham via GitGitGadget
2026-04-30 10:54   ` [PATCH v2 2/4] http: attempt Negotiate auth in http.emptyAuth=auto mode Matthew John Cheetham via GitGitGadget
2026-04-30 10:54   ` [PATCH v2 3/4] t5563: add tests for http.emptyAuth with Negotiate Matthew John Cheetham via GitGitGadget
2026-04-30 10:54   ` [PATCH v2 4/4] doc: clarify http.emptyAuth values Matthew John Cheetham via GitGitGadget

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