feat: add separate CanSendStoreEmail permission for store email API (#7345)
What changed, and why it matters
This commit introduces a new, more narrowly-scoped permission called 'CanSendStoreEmail' and applies it to the store email-sending API. Previously, sending store emails required the broader 'CanModifyStoreSettings' permission, which also allows changing many other store settings. This change follows the security principle of least privilege: it lets administrators grant API keys or users the ability to send emails without giving them full power to change store configuration. It is a hardening improvement rather than a fix for an active exploit.
No urgent action is required. Operators may review API keys and user roles to determine whether any previously had `CanModifyStoreSettings` only for email-sending purposes and can now be restricted to `CanSendStoreEmail`. Developers should ensure that UI/API documentation is updated to list the new permission and that existing integration tests cover both allowed and denied access cases.
Security signals we found
Principle of least privilege: separates email-sending capability from full store settings modification
Authorization policy change on a sensitive API endpoint (store email sending)
New permission registered with both unscoped and scoped display labels
Backward compatibility maintained by nesting new permission under broader store-modification policy
Evidence from the diff
The change adds a new permission constant CanSendStoreEmail (‘btcpay.store.cansendstoreemails’) in Permissions.cs, registers a scoped/unscoped policy definition for it in BTCPayServerServices.cs, and switches the GreenfieldStoreEmailController.SendEmailFromStore endpoint authorization from Policies.CanModifyStoreSettings to Policies.CanSendStoreEmail. The new permission is also included in the CanModifyStoreSettingsUnscoped parent policy, preserving backward compatibility for users/keys that already hold the broader store-modification permission. No code logic for sending email is changed.
Changed components
BTCPayServer.Client/Permissions.csBTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.csBTCPayServer/Hosting/BTCPayServerServices.csInspect captured patch +8 / −2
diff --git a/BTCPayServer.Client/Permissions.cs b/BTCPayServer.Client/Permissions.cs
index c801750..e0fbae8 100644
--- a/BTCPayServer.Client/Permissions.cs
+++ b/BTCPayServer.Client/Permissions.cs
@@ -15,6 +15,7 @@ namespace BTCPayServer.Client
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
public const string CanModifyWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
+ public const string CanSendStoreEmail = "btcpay.store.cansendstoreemails";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanViewReports = "btcpay.store.canviewreports";
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs
index 1cb0fd8..64a37d3 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreEmailController.cs
@@ -27,7 +27,7 @@ namespace BTCPayServer.Controllers.GreenField
_storeRepository = storeRepository;
}
- [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
+ [Authorize(Policy = Policies.CanSendStoreEmail, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPost("~/api/v1/stores/{storeId}/email/send")]
public async Task<IActionResult> SendEmailFromStore(string storeId,
[FromBody] SendEmailRequest request)
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index 21786af..032d95e 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -549,6 +549,10 @@ namespace BTCPayServer.Hosting
Policies.CanModifyWebhooks,
new PermissionDisplay("Modify stores webhooks", "Allows modifying the webhooks of all your stores."),
new PermissionDisplay("Modify selected stores' webhooks", "Allows modifying the webhooks of the selected stores.")),
+ new PolicyDefinition(
+ Policies.CanSendStoreEmail,
+ new PermissionDisplay("Send store emails", "Allows sending emails on behalf of all your stores."),
+ new PermissionDisplay("Send selected stores' emails", "Allows sending emails on behalf of the selected stores.")),
new PolicyDefinition(
Policies.CanModifyServerSettings,
new PermissionDisplay("Manage your server", "Grants total control on the server settings of your server."),
@@ -565,7 +569,8 @@ namespace BTCPayServer.Hosting
Policies.CanModifyWebhooks,
Policies.CanModifyPaymentRequests,
Policies.CanManagePayouts,
- Policies.CanUseLightningNodeInStore
+ Policies.CanUseLightningNodeInStore,
+ Policies.CanSendStoreEmail
}),
new PolicyDefinition(
Policies.CanViewStoreSettings,
Why this scored 32/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.