Skip to content

Commit 3e71690

Browse files
Merge pull request #9959 from Robinfr/feature/enhance-csp-documentation
Enhance CSP documentation
2 parents c0650e6 + a8fa8f9 commit 3e71690

File tree

3 files changed

+170
-3
lines changed

3 files changed

+170
-3
lines changed

content/en/docs/deployment/mendix-cloud-deploy/environments-details.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,11 @@ document.cookie = "originURI=/login.html" + (window.location.protocol === "https
321321

322322
#### Content Security Policy {#csp}
323323

324-
A Content Security Policy informs the client (browser) where your page loads resources from. Setting this can make your app more secure by declaring trusted sources for your resources. For more information, see the W3C recommendation [Content Security Policy Level 2](https://www.w3.org/TR/CSP2/).
324+
A Content Security Policy (CSP) informs the client (browser) where your page loads resources from. Setting this can make your app more secure by declaring trusted sources for your resources. For more information, see the W3C recommendation [Content Security Policy Level 2](https://www.w3.org/TR/CSP2/).
325+
326+
{{% alert color="info" %}}
327+
For complete CSP support, including nonce-based CSP, use the [Headers](/refguide/configuration/#headers) custom runtime setting instead of the HTTP Headers UI. For detailed implementation instructions, see [Content Security Policy](/howto/security/csp/).
328+
{{% /alert %}}
325329

326330
The process for setting a full content security policy depends on what your app does. However, a starting point that declares the content security policy that works with a basic Mendix app is given below:
327331

content/en/docs/howto/security/csp.md

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,168 @@ After redeploying your app locally, it should function as normal. If your app do
174174
175175
After you finish testing locally, remember to remove the line of code in the `head` tag.
176176
177-
### Enabling the Header in the Cloud
177+
## CSP Support in Java Request Handlers
178178
179-
To enable the header in the cloud, follow the instructions in the [HTTP Headers](/developerportal/deploy/environments-details/#http-headers) section of *Environment Details*.
179+
When developing Marketplace modules or custom Java actions that include request handlers, you may need to implement CSP support to ensure compatibility with strict CSP policies. This includes support for CSP Level 2+ features such as nonces for inline scripts and styles.
180+
181+
{{% alert color="info" %}}
182+
CSP support is only relevant for request handlers that serve static content such as HTML pages, not for API endpoints that return JSON or other data formats.
183+
{{% /alert %}}
184+
185+
This section describes how to properly handle CSP headers in Java request handlers when serving HTML content.
186+
187+
### Available CSP APIs
188+
189+
Mendix provides two APIs to support CSP in Java request handlers:
190+
191+
#### IMxRuntimeResponse Methods
192+
193+
The `IMxRuntimeResponse` interface provides these basic CSP methods:
194+
195+
* `addContentSecurityPolicyHeader()` – Adds the Content-Security-Policy header as configured in the application
196+
* `getNonce()` – Returns a secure, uniquely generated nonce for the response that you can use in CSP directives
197+
* `addHeader(String key, String value)` – Adds a custom header to the response
198+
199+
#### CspHelper Interface (Recommended)
200+
201+
The `CspHelper` interface provides additional utility methods for more advanced CSP handling:
202+
203+
* `getTemplate()` – Gets the template used for the Content-Security-Policy header value
204+
* `getNonce(IMxRuntimeResponse response)` – Gets the generated nonce of the current HTTP response
205+
* `hasNonce(IMxRuntimeResponse response)` – Returns true if the configured CSP template contains the `{{ NONCE }}` placeholder. For example: `Content-Security-Policy: script-src 'nonce-{{ NONCE }}'`
206+
* `addHeader(IMxRuntimeResponse response)` – Adds Content-Security-Policy header to the response using the configured template
207+
208+
### Example Implementation
209+
210+
Here's how to implement CSP support in a Java request handler using the `CspHelper` interface:
211+
212+
```java
213+
package your.module.requesthandlers;
214+
215+
import com.mendix.externalinterface.connector.RequestHandler;
216+
import com.mendix.m2ee.api.IMxRuntimeRequest;
217+
import com.mendix.m2ee.api.IMxRuntimeResponse;
218+
import com.mendix.http.CspHelper;
219+
import com.mendix.core.Core;
220+
221+
public class YourRequestHandler extends RequestHandler {
222+
223+
@Override
224+
protected void processRequest(IMxRuntimeRequest request, IMxRuntimeResponse response, String path) throws Exception {
225+
try {
226+
// Add the configured CSP header from the application
227+
Core.csp().addHeader(response);
228+
229+
// Set response content type
230+
response.setContentType("text/html");
231+
232+
// Generate your response content with conditional nonce support
233+
String htmlContent = generateHtmlWithCSP(response);
234+
235+
// Write the response
236+
response.getWriter().write(htmlContent);
237+
238+
} catch (Exception e) {
239+
logger.error("Error processing request: " + e.getMessage(), e);
240+
response.setStatus(IMxRuntimeResponse.INTERNAL_SERVER_ERROR);
241+
response.sendError("Internal server error");
242+
}
243+
}
244+
245+
private String generateHtmlWithCSP(IMxRuntimeResponse response) {
246+
StringBuilder html = new StringBuilder();
247+
html.append("<!DOCTYPE html>\n");
248+
html.append("<html>\n");
249+
html.append("<head>\n");
250+
html.append("<title>Your Module</title>\n");
251+
252+
// Only use nonce if it's configured in the CSP template
253+
if (Core.csp().hasNonce(response)) {
254+
String nonce = Core.csp().getNonce(response);
255+
html.append("<script nonce=\"").append(nonce).append("\">\n");
256+
html.append("// Your inline JavaScript here\n");
257+
html.append("console.log('This script is CSP-compliant with nonce');\n");
258+
html.append("</script>\n");
259+
} else {
260+
// Alternative approach when nonce is not configured
261+
html.append("<script src=\"/path/to/external/script.js\"></script>\n");
262+
}
263+
264+
html.append("</head>\n");
265+
html.append("<body>\n");
266+
html.append("<h1>Your Module Content</h1>\n");
267+
html.append("<!-- Your content here -->\n");
268+
html.append("</body>\n");
269+
html.append("</html>\n");
270+
271+
return html.toString();
272+
}
273+
}
274+
```
275+
276+
### Best Practices for CSP in Request Handlers
277+
278+
When implementing CSP support in your request handlers, follow these best practices:
279+
280+
* **Use `CspHelper` for conditional nonce support** – Always check if nonce is configured before using it:
281+
282+
```java
283+
if (Core.csp().hasNonce(response)) {
284+
String nonce = Core.csp().getNonce(response);
285+
// Use nonce for inline content
286+
} else {
287+
// Use external resources or alternative approach
288+
}
289+
```
290+
291+
* **Always add CSP headers** – Use `Core.csp().addHeader(response)` to ensure your module respects the application's CSP configuration when serving HTML content.
292+
293+
* **CSP is only needed for HTML content** – Only implement CSP support in request handlers that serve HTML pages. API endpoints returning JSON, XML, or other data formats do not need CSP headers.
294+
295+
* **Avoid inline scripts and styles when possible** – Use external files that can be loaded via `'self'` directive.
296+
297+
* **Test with strict CSP** – Test your request handlers with `default-src: 'self'` to ensure they work with the strictest CSP settings.
298+
299+
### Common CSP Issues in Request Handlers
300+
301+
When working with CSP in request handlers, you may encounter these common issues:
302+
303+
#### Base64 Images
304+
305+
Strict CSP blocks inline Base64 images that your request handler generates. Consider these alternatives:
306+
307+
* Serve images as separate endpoints
308+
* Use external image hosting
309+
* Add `data:` to `img-src` directive (less secure)
310+
311+
#### Dynamic Script Generation
312+
313+
Avoid generating `<script>` tags dynamically without nonces. Instead:
314+
315+
* Use the provided nonce for any inline scripts
316+
* Move logic to external JavaScript files
317+
* Use data attributes and external scripts to handle dynamic behavior
318+
319+
#### Third-party Resources
320+
321+
If your module loads external resources, make sure they are allowed by the CSP or provide configuration options for developers to whitelist them.
322+
323+
#### Error Handling
324+
325+
When CSP violations occur, implement proper error handling:
326+
327+
```java
328+
// Log CSP-related errors for debugging
329+
if (Core.csp().hasNonce(response)) {
330+
logger.debug("Using CSP with nonce: " + Core.csp().getNonce(response));
331+
} else {
332+
logger.debug("CSP configured without nonce support");
333+
}
334+
```
335+
336+
## Enabling the Header in the Cloud
337+
338+
There are two ways to enable the header in the cloud:
339+
340+
1. **[Headers](/refguide/configuration/#headers) custom runtime setting (Recommended)**: Use this if you need nonce-based CSP support. Configure this in the Developer Portal under [Custom Runtime Settings](/developerportal/deploy/environments-details/#custom-runtime-settings).
341+
2. **HTTP Headers UI:** This method works for basic CSP support. For more details, refer to the [HTTP Headers](/developerportal/deploy/environments-details/#http-headers) section of *Environment Details*.

content/en/docs/refguide/runtime/custom-settings/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ The following custom settings can be configured:
7272
| <a id="ScheduledEventExecution" href="#ScheduledEventExecution">ScheduledEventExecution</a> | Specify which scheduled events should be executed. Choices are `ALL`, `NONE`, or `SPECIFIED`. In the case of `SPECIFIED`, enumerate the scheduled events using the `MyScheduledEvents` configuration option described below. {{% alert color="warning" %}}This setting cannot be configured when running locally. To enable and disable scheduled events when running locally, please use the 'Enabled' setting on the [Scheduled Events execution properties](/refguide/scheduled-events/) in Studio Pro.{{% /alert %}} | NONE |
7373
| <a id="SessionKeepAliveUpdatesInterval" href="#SessionKeepAliveUpdatesInterval">SessionKeepAliveUpdatesInterval</a> | Defines how often a runtime writes session LastActive dates in its memory back to the database. | one sixth of the value configured for the `SessionTimeout` setting; if the `SessionTimeout` is not set, this value defaults to 100000 (100 seconds) |
7474
| <a id="SessionTimeout" href="#SessionTimeout">SessionTimeout</a> | Defines after how much time a session becomes invalid (in milliseconds). After that timeout, a session becomes eligible for removal. The session will not be destroyed until the next time a scheduled task runs to clean up the active sessions. <br> {{% alert color="warning" %}} Logging out can also be triggered by a query to the runtime after the session becomes eligible for removal. Navigating between pages is not enough to trigger a query to the runtime. To force a query to the runtime, use microflows. For example, create a microflow that shows the Home page, then configure your app's navigation to call this microflow rather than relying on the navigation to directly show the page itself. This will ensure the runtime is queried and the user is logged out of their session. {{% /alert %}} | 600000 (10 minutes) |
75+
| <a id="Headers" href="#Headers">Headers</a> | A JSON object containing custom headers as key/value pairs, and can be used to define a `Content-Security-Policy`. For example, `{ "Content-Security-Policy": "script-src 'nonce-{{ NONCE }}'" }`. | `{}` |
7576
| <a id="TaskQueueShutdownGracePeriod" href="#TaskQueueShutdownGracePeriod">TaskQueue.<wbr>ShutdownGracePeriod</a> | Time in ms to wait for task in a task queue to finish when shutting down. | 10000 (10 seconds) |
7677
| <a id="TempPath" href="#TempPath">TempPath</a> | The location of the temporary files. | [deployment folder]\data\tmp |
7778
| <a id="TrackWebServiceUserLastLogin" href="#TrackWebServiceUserLastLogin">TrackWebServiceUserLastLogin</a> | Defines whether to update a user's `LastLogin` field on each login when logging in to a published REST, OData, or web service. When this happens a database update query has to be sent and this can have performance consequences on heavy load systems. When this setting is set to false, no database interaction is necessary. | true |

0 commit comments

Comments
 (0)