From 4e4f34cea99bb3de982083262da47d5458043ddb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 9 Feb 2026 18:21:48 +0100 Subject: [PATCH] credential: advertise NTLM suppression and allow helpers to re-enable The previous commits disabled NTLM authentication by default due to its cryptographic weaknesses. Users can re-enable it via the config setting http..allowNTLMAuth, but this requires manual intervention. Credential helpers may have knowledge about which servers are trusted for NTLM authentication (e.g., known on-prem Azure DevOps instances). To allow them to signal this trust, introduce a simple negotiation: when NTLM is suppressed and the server offered it, Git advertises ntlm=suppressed to the credential helper. The helper can respond with ntlm=allow to re-enable NTLM for this request. This happens precisely at the point where we would otherwise warn the user about NTLM being suppressed, ensuring the capability is only advertised when relevant. Helped-by: Matthew John Cheetham Signed-off-by: Johannes Schindelin --- credential.c | 5 +++++ credential.h | 3 +++ http.c | 17 +++++++++++++++-- t/t5563-simple-http-auth.sh | 13 ++++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/credential.c b/credential.c index 2594c0c422..af96418936 100644 --- a/credential.c +++ b/credential.c @@ -360,6 +360,9 @@ int credential_read(struct credential *c, FILE *fp, credential_set_capability(&c->capa_authtype, op_type); else if (!strcmp(value, "state")) credential_set_capability(&c->capa_state, op_type); + } else if (!strcmp(key, "ntlm")) { + if (!strcmp(value, "allow")) + c->ntlm_allow = 1; } else if (!strcmp(key, "continue")) { c->multistage = !!git_config_bool("continue", value); } else if (!strcmp(key, "password_expiry_utc")) { @@ -420,6 +423,8 @@ void credential_write(const struct credential *c, FILE *fp, if (c->ephemeral) credential_write_item(c, fp, "ephemeral", "1", 0); } + if (c->ntlm_suppressed) + credential_write_item(c, fp, "ntlm", "suppressed", 0); credential_write_item(c, fp, "protocol", c->protocol, 1); credential_write_item(c, fp, "host", c->host, 1); credential_write_item(c, fp, "path", c->path, 0); diff --git a/credential.h b/credential.h index c78b72d110..95244d5375 100644 --- a/credential.h +++ b/credential.h @@ -177,6 +177,9 @@ struct credential { struct credential_capability capa_authtype; struct credential_capability capa_state; + unsigned ntlm_suppressed:1, + ntlm_allow:1; + char *username; char *password; char *credential; diff --git a/http.c b/http.c index e72adb5db6..6139b31ae3 100644 --- a/http.c +++ b/http.c @@ -660,6 +660,11 @@ static void init_curl_http_auth(CURL *result) credential_fill(the_repository, &http_auth, 1); + if (http_auth.ntlm_allow && !(http_auth_methods & CURLAUTH_NTLM)) { + http_auth_methods |= CURLAUTH_NTLM; + curl_easy_setopt(result, CURLOPT_HTTPAUTH, http_auth_methods); + } + if (http_auth.password) { if (always_auth_proactively()) { /* @@ -1891,6 +1896,8 @@ static int handle_curl_result(struct slot_results *results) } else if (missing_target(results)) return HTTP_MISSING_TARGET; else if (results->http_code == 401) { + http_auth.ntlm_suppressed = (results->auth_avail & CURLAUTH_NTLM) && + !(http_auth_any & CURLAUTH_NTLM); if ((http_auth.username && http_auth.password) ||\ (http_auth.authtype && http_auth.credential)) { if (http_auth.multistage) { @@ -1900,8 +1907,7 @@ static int handle_curl_result(struct slot_results *results) credential_reject(the_repository, &http_auth); if (always_auth_proactively()) http_proactive_auth = PROACTIVE_AUTH_NONE; - if ((results->auth_avail & CURLAUTH_NTLM) && - !(http_auth_any & CURLAUTH_NTLM)) { + if (http_auth.ntlm_suppressed) { warning(_("Due to its cryptographic weaknesses, " "NTLM authentication has been\n" "disabled in Git by default. You can " @@ -2424,6 +2430,13 @@ static int http_request_recoverable(const char *url, credential_fill(the_repository, &http_auth, 1); } + /* + * Re-enable NTLM auth if the helper allows it and we would + * otherwise suppress authentication via NTLM. + */ + if (http_auth.ntlm_suppressed && http_auth.ntlm_allow) + http_auth_methods |= CURLAUTH_NTLM; + ret = http_request(url, result, target, options); } if (ret == HTTP_RATE_LIMITED) { diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index 303f858964..35e6f4b397 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -733,8 +733,19 @@ test_expect_success NTLM 'access using NTLM auth' ' test_must_fail env GIT_TRACE_CURL=1 git \ ls-remote "$HTTPD_URL/ntlm_auth/repo.git" 2>err && test_grep "allowNTLMAuth" err && + + # Can be enabled via config GIT_TRACE_CURL=1 git -c http.$HTTPD_URL.allowNTLMAuth=true \ - ls-remote "$HTTPD_URL/ntlm_auth/repo.git" + ls-remote "$HTTPD_URL/ntlm_auth/repo.git" && + + # Or via credential helper responding with ntlm=allow + set_credential_reply get <<-EOF && + username=user + password=pwd + ntlm=allow + EOF + + git ls-remote "$HTTPD_URL/ntlm_auth/repo.git" ' test_done