Disable Greenfield Basic Auth by default after 5 min of user creation (#7492)
What changed, and why it matters
This change is a hardening measure, not a direct bug fix. BTCPay Server now turns off username/password Basic authentication for its Greenfield API by default once an account is more than five minutes old. Users can re-enable it manually, but the safer default is to require API keys instead. The vendor describes this as a defense-in-depth follow-up to reduce the impact of other authentication bugs.
Treat this as a security hardening change that should be deployed promptly, especially because it is bundled with an emergency release addressing an actively exploited adjacent issue. Review whether any integrations rely on Greenfield Basic auth and migrate them to API keys; if Basic auth is still required, explicitly enable the new per-user setting.
Security signals we found
Disables a less-secure authentication method by default after a short grace period
Adds per-user opt-in flag for Basic authentication
Vendor explicitly frames the change as defense-in-depth following a related security fix
Emergency release (v2.4.2) ships this alongside a TOTP-bypass fix
No direct vulnerability patch in the diff itself
Evidence from the diff
The commit adds an AllowGreenfieldBasicAuth flag per user, exposes it in the user profile UI and Greenfield API, and changes BasicAuthenticationHandler so that Basic auth is rejected for accounts older than five minutes unless the flag is explicitly true. A five-minute grace period remains for new accounts to create an API key. The change is described by the vendor as a follow-up hardening measure to limit the blast radius of bugs such as the TOTP bypass fixed in PR #7491.
Changed components
Greenfield API Basic authentication handlerUser profile / account management UIGreenfield Users API endpointsApplication user data model and client modelsSwagger API documentationInspect captured patch +74 / −6
diff --git a/BTCPayServer.Client/Models/ApplicationUserData.cs b/BTCPayServer.Client/Models/ApplicationUserData.cs
index f1d3164..0b92cc3 100644
--- a/BTCPayServer.Client/Models/ApplicationUserData.cs
+++ b/BTCPayServer.Client/Models/ApplicationUserData.cs
@@ -70,6 +70,11 @@ namespace BTCPayServer.Client.Models
/// </summary>
public int? StoreQuota { get; set; }
+ /// <summary>
+ /// whether Basic authentication is allowed for the Greenfield API
+ /// </summary>
+ public bool AllowGreenfieldBasicAuth { get; set; }
+
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
}
diff --git a/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs b/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs
index 0a48b7e..fe063fb 100644
--- a/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs
+++ b/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs
@@ -26,5 +26,9 @@ public class UpdateApplicationUserRequest
/// new password of the user
/// </summary>
public string NewPassword { get; set; }
-}
+ /// <summary>
+ /// whether to allow Basic authentication for the Greenfield API
+ /// </summary>
+ public bool? AllowGreenfieldBasicAuth { get; set; }
+}
diff --git a/BTCPayServer.Data/Data/ApplicationUser.cs b/BTCPayServer.Data/Data/ApplicationUser.cs
index 90b12a7..578a19b 100644
--- a/BTCPayServer.Data/Data/ApplicationUser.cs
+++ b/BTCPayServer.Data/Data/ApplicationUser.cs
@@ -62,5 +62,6 @@ namespace BTCPayServer.Data
public string Name { get; set; }
public string InvitationToken { get; set; }
public int? StoreQuota { get; set; }
+ public bool AllowGreenfieldBasicAuth { get; set; }
}
}
diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs
index cf5958c..400fe2e 100644
--- a/BTCPayServer.Tests/GreenfieldAPITests.cs
+++ b/BTCPayServer.Tests/GreenfieldAPITests.cs
@@ -1587,6 +1587,27 @@ namespace BTCPayServer.Tests
WHERE "Id"=@id
""", new{ id = user.UserId });
+ var basicAuthDisabled = await AssertEx.AssertApiError(401, "unauthenticated",
+ () => clientBasic.GetCurrentUser());
+ Assert.Contains("not enabled", basicAuthDisabled.Message);
+
+ var updatedUser = await clientProfile.UpdateCurrentUser(new UpdateApplicationUserRequest
+ {
+ AllowGreenfieldBasicAuth = true
+ });
+ Assert.True(updatedUser.AllowGreenfieldBasicAuth);
+ Assert.True((await clientBasic.GetCurrentUser()).AllowGreenfieldBasicAuth);
+
+ updatedUser = await clientProfile.UpdateCurrentUser(new UpdateApplicationUserRequest
+ {
+ AllowGreenfieldBasicAuth = false
+ });
+ Assert.False(updatedUser.AllowGreenfieldBasicAuth);
+ await clientProfile.UpdateCurrentUser(new UpdateApplicationUserRequest
+ {
+ AllowGreenfieldBasicAuth = true
+ });
+
var err = await AssertEx.AssertApiError(401, "unauthenticated", async () =>
{
for (var i = 0; i < 10; i++)
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs
index 5809a55..02a5735 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs
@@ -204,6 +204,13 @@ namespace BTCPayServer.Controllers.Greenfield
}
var blob = user.GetBlob() ?? new();
+ if (request.AllowGreenfieldBasicAuth is { } allowGreenfieldBasicAuth &&
+ allowGreenfieldBasicAuth != blob.AllowGreenfieldBasicAuth)
+ {
+ blob.AllowGreenfieldBasicAuth = allowGreenfieldBasicAuth;
+ needUpdate = true;
+ }
+
if (request.Name is not null && request.Name != blob.Name)
{
blob.Name = request.Name;
diff --git a/BTCPayServer/Controllers/UIManageController.cs b/BTCPayServer/Controllers/UIManageController.cs
index 9f66e32..a7de06f 100644
--- a/BTCPayServer/Controllers/UIManageController.cs
+++ b/BTCPayServer/Controllers/UIManageController.cs
@@ -95,7 +95,8 @@ namespace BTCPayServer.Controllers
Name = blob.Name,
ImageUrl = string.IsNullOrEmpty(blob.ImageUrl) ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
EmailConfirmed = user.EmailConfirmed,
- RequiresEmailConfirmation = user.RequiresEmailConfirmation
+ RequiresEmailConfirmation = user.RequiresEmailConfirmation,
+ AllowGreenfieldBasicAuth = blob.AllowGreenfieldBasicAuth
};
return View(model);
}
@@ -174,6 +175,12 @@ namespace BTCPayServer.Controllers
}
var blob = user.GetBlob() ?? new();
+ if (blob.AllowGreenfieldBasicAuth != model.AllowGreenfieldBasicAuth)
+ {
+ blob.AllowGreenfieldBasicAuth = model.AllowGreenfieldBasicAuth;
+ needUpdate = true;
+ }
+
if (blob.Name != model.Name)
{
blob.Name = model.Name;
diff --git a/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs b/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs
index dcadaf4..10a8255 100644
--- a/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs
+++ b/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs
@@ -18,5 +18,8 @@ namespace BTCPayServer.Models.ManageViewModels
[Display(Name = "Profile Picture")]
public IFormFile ImageFile { get; set; }
public string ImageUrl { get; set; }
+
+ [Display(Name = "Allow Basic authentication for Greenfield API")]
+ public bool AllowGreenfieldBasicAuth { get; set; }
}
}
diff --git a/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs b/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs
index b5bbddd..a70def3 100644
--- a/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs
+++ b/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs
@@ -59,9 +59,10 @@ namespace BTCPayServer.Security.Greenfield
.FirstOrDefaultAsync(applicationUser =>
applicationUser.NormalizedUserName == userManager.NormalizeName(username));
- // We disable throttling for new accounts to give time to create API keys via greenfield API.
- if (user?.Created is not {} created ||
- (DateTimeOffset.UtcNow - created) > TimeSpan.FromMinutes(5))
+ // Give new accounts time to create API keys via the Greenfield API.
+ TimeSpan? accountAge = user?.Created is { } created ? DateTimeOffset.UtcNow - created : null;
+ var isNewAccount = accountAge >= TimeSpan.Zero && accountAge <= TimeSpan.FromMinutes(5);
+ if (!isNewAccount)
{
if (Context.Connection.RemoteIpAddress?.ToString() is string ip)
if (!await rateLimitService.Throttle(ZoneLimits.Login, ip))
@@ -75,6 +76,8 @@ namespace BTCPayServer.Security.Greenfield
}
if (user is null)
return Fail($"Basic authentication failed");
+ if (!isNewAccount && user.GetBlob()?.AllowGreenfieldBasicAuth is not true)
+ return Fail("Basic authentication for the Greenfield API is not enabled for this user.");
if (await signInManager.IsTwoFactorEnabledAsync(user))
{
return Fail("Cannot use Basic authentication when multi-factor is enabled.");
diff --git a/BTCPayServer/Services/UserService.cs b/BTCPayServer/Services/UserService.cs
index fb780d8..824b534 100644
--- a/BTCPayServer/Services/UserService.cs
+++ b/BTCPayServer/Services/UserService.cs
@@ -86,6 +86,7 @@ namespace BTCPayServer.Services
Roles = roles,
Disabled = data.IsDisabled,
StoreQuota = blob.StoreQuota,
+ AllowGreenfieldBasicAuth = blob.AllowGreenfieldBasicAuth,
ImageUrl = string.IsNullOrEmpty(blob.ImageUrl)
? null
: await uriResolver.Resolve(request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
diff --git a/BTCPayServer/Views/UIManage/Index.cshtml b/BTCPayServer/Views/UIManage/Index.cshtml
index 57aa82a..ae8ea88 100644
--- a/BTCPayServer/Views/UIManage/Index.cshtml
+++ b/BTCPayServer/Views/UIManage/Index.cshtml
@@ -67,6 +67,13 @@
<span asp-validation-for="ImageFile" class="text-danger"></span>
</div>
}
+ <div class="form-group">
+ <div class="form-check">
+ <input asp-for="AllowGreenfieldBasicAuth" class="form-check-input" />
+ <label asp-for="AllowGreenfieldBasicAuth" class="form-check-label"></label>
+ </div>
+ <small class="text-muted" text-translate="true">Basic authentication is less secure than API keys. New users can use it for five minutes to create an API key without enabling this setting.</small>
+ </div>
<h3 class="mt-5 mb-4" text-translate="true">Delete Account</h3>
<div id="danger-zone">
<a id="delete-user" asp-action="DeleteUserPost" class="btn btn-outline-danger mb-5" data-confirm-input="@StringLocalizer["Delete"]" data-bs-toggle="modal" data-bs-target="#ConfirmModal" text-translate="true">Delete Account</a>
diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.json
index 03c0ef8..d8b7249 100644
--- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.json
+++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.json
@@ -222,7 +222,7 @@
},
"Basic": {
"type": "http",
- "description": "BTCPay Server supports authenticating and authorizing users through the Basic HTTP authentication scheme. Send the user and password encoded in base64 with the format `Basic {base64(username:password)}`. Using this authentication method implicitly provides you with the `unrestricted` permission",
+ "description": "BTCPay Server supports authenticating and authorizing users through the Basic HTTP authentication scheme during the first five minutes after account creation or when the user explicitly enables it. Send the user and password encoded in base64 with the format `Basic {base64(username:password)}`. Using this authentication method implicitly provides you with the `unrestricted` permission",
"scheme": "Basic"
}
}
diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json
index 6486cc4..35fe1c1 100644
--- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json
+++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json
@@ -70,6 +70,11 @@
"type": "string",
"description": "The new password of the user",
"nullable": true
+ },
+ "allowGreenfieldBasicAuth": {
+ "type": "boolean",
+ "description": "Whether to allow Basic authentication for the Greenfield API",
+ "nullable": true
}
}
}
@@ -566,6 +571,10 @@
"type": "boolean",
"description": "True if an admin has disabled the user"
},
+ "allowGreenfieldBasicAuth": {
+ "type": "boolean",
+ "description": "Whether Basic authentication is allowed for the Greenfield API"
+ },
"roles": {
"type": "array",
"nullable": false,
Why this scored 60/100
Evidence and disclosure record
Verified links used to place this patch in context. External claims remain attributed to their publishers.
Greenfield: Disable Basic authentication by default
Vendor-authored defense-in-depth follow-up that disables Basic authentication by default five minutes after account creation to reduce the impact of bugs such as the TOTP bypass fixed in PR #7491.
BTCPay Server 2.4.2
The emergency release ships this hardening change alongside the TOTP-bypass fix. BTCPay's actively exploited LND credential incident is tracked separately because its technical patch details remain withheld.
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.