## Description The push-subscription endpoint stores an attacker-controlled delivery URL, and the notification send path becomes an outbound-request sink when VAPID delivery is enabled. The practical result is an authenticated, stored, mostly blind SSRF primitive to arbitrary HTTPS endpoints reachable from the app server. ## Technical description When VAPID delivery is enabled, the notification subscription flow stores the client-supplied push endpoint without checking that it belongs to an approved push service. The send path later passes that stored URL to `WebPush.payload_send`, turning the subscription into an attacker-controlled outbound request destination. This is the source-to-sink chain: 1. Source: attacker-controlled `subscription.endpoint` in the JSON body posted to `POST /notifications_subscriptions`. 2. Persistence: `params[:endpoint]` is stored under `user.notification_settings["subscriptions"]`. 3. Retrieval: `user.notifications_subscriptions.values` returns that stored endpoint later. 4. Sink: `build_payload` sets `endpoint: subscription["endpoint"]`. 5. Outbound request: `WebPush.payload_send(**payload)` uses the attacker-supplied endpoint as the destination. One spec asserts that the endpoint is persisted exactly as supplied: ```ruby # decidim-core/spec/services/decidim/notifications_subscriptions_persistor_spec.rb expect(user.notifications_subscriptions["auth_code_121"]["endpoint"]).to eq(params[:endpoint]) ``` Another spec asserts that `SendPushNotification` passes the stored endpoint into `WebPush.payload_send`: ```ruby # decidim-core/spec/services/decidim/send_push_notification_spec.rb first_notification_payload = { message:, endpoint: subscriptions["auth_key_1"]["endpoint"], p256dh: subscriptions["auth_key_1"]["p256dh"], auth: subscriptions["auth_key_1"]["auth"], vapid: a_hash_including(...) } expect(WebPush).to receive(:payload_send).with(first_notification_payload) ``` ### Impact - In a configured deployment, an authenticated user can register an attacker-controlled or otherwise unauthorized HTTPS URL. - The server then sends outbound `POST` requests there whenever a notification is pushed. - This is a stored, mostly blind SSRF primitive: useful for outbound interaction with attacker infrastructure and, where routable, internal HTTPS services. - Notification metadata is disclosed to the supplied endpoint through the encrypted web push request path. ### Patches See https://github.com/decidim/decidim/pull/16714. ### Workarounds Disable the push notifications feature by removing the VAPID keys in the server. ### Resource SSRF ### Credits This issue was discovered in a security audit organized by the [Decidim Association](https://decidim.org) and made by [Radically Open Security](https://www.radicallyopensecurity.com/) against Decidim financed by [NGI](https://ngi.eu/).
## Description The push-subscription endpoint stores an attacker-controlled delivery URL, and the notification send path becomes an outbound-request sink when VAPID delivery is enabled. The practical result is an authenticated, stored, mostly blind SSRF primitive to arbitrary HTTPS endpoints reachable from the app server. ## Technical description When VAPID delivery is enabled, the notification subscription flow stores the client-supplied push endpoint without checking that it belongs to an approved push service. The send path later passes that stored URL to `WebPush.payload_send`, turning the subscription into an attacker-controlled outbound request destination. This is the source-to-sink chain: 1. Source: attacker-controlled `subscription.endpoint` in the JSON body posted to `POST /notifications_subscriptions`. 2. Persistence: `params[:endpoint]` is stored under `user.notification_settings["subscriptions"]`. 3. Retrieval: `user.notifications_subscriptions.values` returns that stored endpoint later. 4. Sink: `build_payload` sets `endpoint: subscription["endpoint"]`. 5. Outbound request: `WebPush.payload_send(**payload)` uses the attacker-supplied endpoint as the destination. One spec asserts that the endpoint is persisted exactly as supplied: ```ruby # decidim-core/spec/services/decidim/notifications_subscriptions_persistor_spec.rb expect(user.notifications_subscriptions["auth_code_121"]["endpoint"]).to eq(params[:endpoint]) ``` Another spec asserts that `SendPushNotification` passes the stored endpoint into `WebPush.payload_send`: ```ruby # decidim-core/spec/services/decidim/send_push_notification_spec.rb first_notification_payload = { message:, endpoint: subscriptions["auth_key_1"]["endpoint"], p256dh: subscriptions["auth_key_1"]["p256dh"], auth: subscriptions["auth_key_1"]["auth"], vapid: a_hash_including(...) } expect(WebPush).to receive(:payload_send).with(first_notification_payload) ``` ### Impact - In a configured deployment, an authenticated user can register an attacker-controlled or otherwise unauthorized HTTPS URL. - The server then sends outbound `POST` requests there whenever a notification is pushed. - This is a stored, mostly blind SSRF primitive: useful for outbound interaction with attacker infrastructure and, where routable, internal HTTPS services. - Notification metadata is disclosed to the supplied endpoint through the encrypted web push request path. ### Patches See https://github.com/decidim/decidim/pull/16714. ### Workarounds Disable the push notifications feature by removing the VAPID keys in the server. ### Resource SSRF ### Credits This issue was discovered in a security audit organized by the [Decidim Association](https://decidim.org) and made by [Radically Open Security](https://www.radicallyopensecurity.com/) against Decidim financed by [NGI](https://ngi.eu/).
Update decidim-core to 0.30.9; decidim-core to 0.31.5; decidim-core to 0.32.0 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanDecidim: Push subscriptions can be abused for server-side requests affects decidim-core (rubygems), decidim-core (rubygems), decidim-core (rubygems). Severity is medium. ## Description The push-subscription endpoint stores an attacker-controlled delivery URL, and the notification send path becomes an outbound-request sink when VAPID delivery is enabled. The practical result is an authenticated, stored, mostly blind SSRF primitive to arbitrary HTTPS endpoints reachable from the app server. ## Technical description When VAPID delivery is enabled, the notification subscription flow stores the client-supplied push endpoint without checking that it belongs to an approved push service. The send path later passes that stored URL to `WebPush.payload_send`, turning the subscription into an attacker-controlled outbound request destination. This is the source-to-sink chain: 1. Source: attacker-controlled `subscription.endpoint` in the JSON body posted to `POST /notifications_subscriptions`. 2. Persistence: `params[:endpoint]` is stored under `user.notification_settings["subscriptions"]`. 3. Retrieval: `user.notifications_subscriptions.values` returns that stored endpoint later. 4. Sink: `build_payload` sets `endpoint: subscription["endpoint"]`. 5. Outbound request: `WebPush.payload_send(**payload)` uses the attacker-supplied endpoint as the destination. One spec asserts that the endpoint is persisted exactly as supplied: ```ruby # decidim-core/spec/services/decidim/notifications_subscriptions_persistor_spec.rb expect(user.notifications_subscriptions["auth_code_121"]["endpoint"]).to eq(params[:endpoint]) ``` Another spec asserts that `SendPushNotification` passes the stored endpoint into `WebPush.payload_send`: ```ruby # decidim-core/spec/services/decidim/send_push_notification_spec.rb first_notification_payload = { message:, endpoint: subscriptions["auth_key_1"]["endpoint"], p256dh: subscriptions["auth_key_1"]["p256dh"], auth: subscriptions["auth_key_1"]["auth"], vapid: a_hash_including(...) } expect(WebPush).to receive(:payload_send).with(first_notification_payload) ``` ### Impact - In a configured deployment, an authenticated user can register an attacker-controlled or otherwise unauthorized HTTPS URL. - The server then sends outbound `POST` requests there whenever a notification is pushed. - This is a stored, mostly blind SSRF primitive: useful for outbound interaction with attacker infrastructure and, where routable, internal HTTPS services. - Notification metadata is disclosed to the supplied endpoint through the encrypted web push request path. ### Patches See https://github.com/decidim/decidim/pull/16714. ### Workarounds Disable the push notifications feature by removing the VAPID keys in the server. ### Resource SSRF ### Credits This issue was discovered in a security audit organized by the [Decidim Association](https://decidim.org) and made by [Radically Open Security](https://www.radicallyopensecurity.com/) against Decidim financed by [NGI](https://ngi.eu/).
AI coding agents often install or upgrade packages automatically in rubygems. A medium vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| decidim-corerubygems | <0.30.9 | 0.30.9 |
| decidim-corerubygems | >=0.31.0.rc1,<0.31.5 | 0.31.5 |
| decidim-corerubygems | >=0.32.0.rc1,<0.32.0 | 0.32.0 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL GuardUpdate decidim-core to 0.30.9; decidim-core to 0.31.5; decidim-core to 0.32.0 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanDecidim: Push subscriptions can be abused for server-side requests affects decidim-core (rubygems), decidim-core (rubygems), decidim-core (rubygems). Severity is medium. ## Description The push-subscription endpoint stores an attacker-controlled delivery URL, and the notification send path becomes an outbound-request sink when VAPID delivery is enabled. The practical result is an authenticated, stored, mostly blind SSRF primitive to arbitrary HTTPS endpoints reachable from the app server. ## Technical description When VAPID delivery is enabled, the notification subscription flow stores the client-supplied push endpoint without checking that it belongs to an approved push service. The send path later passes that stored URL to `WebPush.payload_send`, turning the subscription into an attacker-controlled outbound request destination. This is the source-to-sink chain: 1. Source: attacker-controlled `subscription.endpoint` in the JSON body posted to `POST /notifications_subscriptions`. 2. Persistence: `params[:endpoint]` is stored under `user.notification_settings["subscriptions"]`. 3. Retrieval: `user.notifications_subscriptions.values` returns that stored endpoint later. 4. Sink: `build_payload` sets `endpoint: subscription["endpoint"]`. 5. Outbound request: `WebPush.payload_send(**payload)` uses the attacker-supplied endpoint as the destination. One spec asserts that the endpoint is persisted exactly as supplied: ```ruby # decidim-core/spec/services/decidim/notifications_subscriptions_persistor_spec.rb expect(user.notifications_subscriptions["auth_code_121"]["endpoint"]).to eq(params[:endpoint]) ``` Another spec asserts that `SendPushNotification` passes the stored endpoint into `WebPush.payload_send`: ```ruby # decidim-core/spec/services/decidim/send_push_notification_spec.rb first_notification_payload = { message:, endpoint: subscriptions["auth_key_1"]["endpoint"], p256dh: subscriptions["auth_key_1"]["p256dh"], auth: subscriptions["auth_key_1"]["auth"], vapid: a_hash_including(...) } expect(WebPush).to receive(:payload_send).with(first_notification_payload) ``` ### Impact - In a configured deployment, an authenticated user can register an attacker-controlled or otherwise unauthorized HTTPS URL. - The server then sends outbound `POST` requests there whenever a notification is pushed. - This is a stored, mostly blind SSRF primitive: useful for outbound interaction with attacker infrastructure and, where routable, internal HTTPS services. - Notification metadata is disclosed to the supplied endpoint through the encrypted web push request path. ### Patches See https://github.com/decidim/decidim/pull/16714. ### Workarounds Disable the push notifications feature by removing the VAPID keys in the server. ### Resource SSRF ### Credits This issue was discovered in a security audit organized by the [Decidim Association](https://decidim.org) and made by [Radically Open Security](https://www.radicallyopensecurity.com/) against Decidim financed by [NGI](https://ngi.eu/).
AI coding agents often install or upgrade packages automatically in rubygems. A medium vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| decidim-corerubygems | <0.30.9 | 0.30.9 |
| decidim-corerubygems | >=0.31.0.rc1,<0.31.5 | 0.31.5 |
| decidim-corerubygems | >=0.32.0.rc1,<0.32.0 | 0.32.0 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL Guard