What changed, and why it matters
This commit marks several public methods on ASP.NET Core controllers with [NonAction], which prevents them from being exposed as web-accessible HTTP routes. Without this attribute, these helper methods could be reached directly via URL, potentially allowing users to bypass intended workflows, access internal logic, or trigger actions that were only meant to be called by other controller code. The change is a hardening fix rather than a clear-cut exploit patch, because the diff alone does not show that any of these routes were actually reachable or dangerous in practice.
Treat this as a security-hardening patch and deploy it promptly. Review routing tables and logs for any evidence that these endpoints were accessed externally before the change. Audit other controllers for public helper methods that should also be marked [NonAction] or made private/protected.
Security signals we found
Public controller methods exposed as unintended HTTP routes
Routing-level access-control hardening
Potential bypass of intended controller action flow
No explicit security advisory or CVE referenced in commit
Evidence from the diff
The patch adds [NonAction] to five helper/utility methods across four controllers and one plugin controller base class in BTCPay Server. In ASP.NET Core, any public instance method on a controller class is conventionally considered a candidate action and may be mapped as an HTTP endpoint unless decorated with [NonAction], made non-public, or otherwise excluded. The methods include redirect helpers (RedirectToList, RedirectToInvoiceCheckout, RedirectToSubscriberPortal, RedirectToPlanCheckout, RedirectToPlanCheckoutPayment), an internal edit-core routine (EmailRulesEditCore overloads), a helper that builds LNURL requests (GetLNURLRequest), and a view-model builder (GetEnabledPaymentMethodChoices). Marking them [NonAction] removes them from routing, closing the possibility that they could be invoked externally with attacker-controlled parameters. The diff does not include tests, routing tables, or exploit reproduction, so we cannot confirm any specific vulnerability was reachable before the change.
Changed components
BTCPayServer/Controllers/UILNURLAuthController.csBTCPayServer/Controllers/UILNURLController.csBTCPayServer/Controllers/UIStoresController.csBTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.csBTCPayServer/Plugins/Subscriptions/Controllers/UISubscriptionControllerBase.csInspect captured patch +9 / −0
diff --git a/BTCPayServer/Controllers/UILNURLAuthController.cs b/BTCPayServer/Controllers/UILNURLAuthController.cs
index 52be33b..1ce37e1 100644
--- a/BTCPayServer/Controllers/UILNURLAuthController.cs
+++ b/BTCPayServer/Controllers/UILNURLAuthController.cs
@@ -143,6 +143,7 @@ namespace BTCPayServer
});
}
+ [NonAction]
public ActionResult RedirectToList(string successMessage = null)
{
if (successMessage != null)
diff --git a/BTCPayServer/Controllers/UILNURLController.cs b/BTCPayServer/Controllers/UILNURLController.cs
index 5269783..a096043 100644
--- a/BTCPayServer/Controllers/UILNURLController.cs
+++ b/BTCPayServer/Controllers/UILNURLController.cs
@@ -594,6 +594,7 @@ namespace BTCPayServer
});
}
+ [NonAction]
public async Task<IActionResult> GetLNURLRequest(
string cryptoCode,
Data.StoreData store,
diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs
index 2e73c0b..4f6407b 100644
--- a/BTCPayServer/Controllers/UIStoresController.cs
+++ b/BTCPayServer/Controllers/UIStoresController.cs
@@ -185,6 +185,7 @@ public partial class UIStoresController : Controller
public StoreData CurrentStore => HttpContext.GetStoreData();
+ [NonAction]
public PaymentMethodOptionViewModel.Format[] GetEnabledPaymentMethodChoices(StoreData storeData)
{
var enabled = storeData.GetEnabledPaymentIds();
diff --git a/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs b/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs
index 78983ca..04ee62b 100644
--- a/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs
+++ b/BTCPayServer/Plugins/Emails/Controllers/UIEmailRuleControllerBase.cs
@@ -121,6 +121,7 @@ public class UIEmailRuleControllerBase(
return emailCtx.RedirectToRuleList(model.RedirectUrl);
}
+ [NonAction]
public async Task<IActionResult> EmailRulesEditCore(EmailsRuleControllerContext emailCtx, long ruleId, string? redirectUrl = null)
{
await using var ctx = DbContextFactory.CreateContext();
@@ -136,6 +137,7 @@ public class UIEmailRuleControllerBase(
});
}
+ [NonAction]
public async Task<IActionResult> EmailRulesEditCore(EmailsRuleControllerContext emailCtx, long ruleId, StoreEmailRuleViewModel model)
{
await ValidateCondition(model);
diff --git a/BTCPayServer/Plugins/Subscriptions/Controllers/UISubscriptionControllerBase.cs b/BTCPayServer/Plugins/Subscriptions/Controllers/UISubscriptionControllerBase.cs
index fcb8da1..b62d1b7 100644
--- a/BTCPayServer/Plugins/Subscriptions/Controllers/UISubscriptionControllerBase.cs
+++ b/BTCPayServer/Plugins/Subscriptions/Controllers/UISubscriptionControllerBase.cs
@@ -20,14 +20,18 @@ public class UISubscriptionControllerBase(
public SubscriptionHostedService SubsService { get; } = subsService;
protected IStringLocalizer StringLocalizer => stringLocalizer;
protected LinkGenerator LinkGenerator => linkGenerator;
+ [NonAction]
public RedirectResult RedirectToInvoiceCheckout(string invoiceId) => Redirect(linkGenerator.InvoiceCheckoutLink(invoiceId, Request.GetRequestBaseUrl()));
+ [NonAction]
public IActionResult RedirectToSubscriberPortal(string portalId, string anchor = null)
=> RedirectToAction(nameof(UISubscriberPortalController.SubscriberPortal), "UISubscriberPortal", new { portalSessionId = portalId, anchor });
+ [NonAction]
public IActionResult RedirectToPlanCheckout(string checkoutId)
=> RedirectToAction(nameof(UIPlanCheckoutController.PlanCheckout), "UIPlanCheckout", new { checkoutId });
+ [NonAction]
public async Task<IActionResult> RedirectToPlanCheckoutPayment(string checkoutId, CancellationToken cancellationToken)
{
try
Why this scored 49/100
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.