Skip to content

Enhance CSP documentation #9959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ document.cookie = "originURI=/login.html" + (window.location.protocol === "https

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/).

{{% alert color="info" %}}
For full CSP support including nonce-based CSP, use the [Headers](/refguide/configuration/#headers) custom runtime setting instead of the HTTP Headers UI. The custom runtime setting method is recommended as it provides more comprehensive CSP capabilities. For detailed implementation guidance, see [Content Security Policy](/howto/security/csp/).
{{% /alert %}}

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:

```text
Expand Down
158 changes: 156 additions & 2 deletions content/en/docs/howto/security/csp.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,160 @@ After redeploying your app locally, it should function as normal. If your app do

After you finish testing locally, remember to remove the line of code in the `head` tag.

### Enabling the Header in the Cloud
## CSP Support in Java Request Handlers

To enable the header in the cloud, follow the instructions in the [HTTP Headers](/developerportal/deploy/environments-details/#http-headers) section of *Environment Details*.
If you are 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 like nonces for inline scripts and styles.

{{% alert color="info" %}}
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.
{{% /alert %}}

This section describes how to properly handle CSP headers in your Java request handlers when serving HTML content.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am missing a small explanation about what nonces are and how they work (and maybe a link to mdn would be nice)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure where I would put a link to MDN in this documentation? I don't want to elaborate on nonces too much to be honest. If you need to know what they are when reading this, you likely shouldn't be reading this documentation since that is an advanced topic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that an explanation is too much, the point I was trying to make is that nonces are not introduced at all. Maybe the sentence at line 179 can be extended with:

If you are 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. Nonces can be implemented to securely allow necessary inline scripts and styles while maintaining robust content security.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a short introduction to nonces referencing CSP level 2


### Available CSP APIs

Mendix provides two APIs for CSP support in Java request handlers:

#### IMxRuntimeResponse Methods

The `IMxRuntimeResponse` interface provides basic CSP methods:

* `addContentSecurityPolicyHeader()` - Adds the Content-Security-Policy header as configured in the application
* `getNonce()` - Returns a uniquely generated secure nonce for the response that can be used in CSP directives
* `addHeader(String key, String value)` - Adds a custom header to the response

#### CspHelper Interface (Recommended)

The `CspHelper` interface provides additional utility methods for more sophisticated CSP handling:

* `getTemplate()` - Get the template used for the Content-Security-Policy header value
* `getNonce(IMxRuntimeResponse response)` - Get the generated nonce of the current HTTP response
* `hasNonce(IMxRuntimeResponse response)` - Returns true if the configured CSP template contains the `{{ NONCE }}` placeholder, for example: `Content-Security-Policy: script-src 'nonce-{{ NONCE }}'`
* `addHeader(IMxRuntimeResponse response)` - Add Content-Security-Policy header to the response using the configured template

### Example Implementation

Here's how to implement CSP support in a Java request handler using the `CspHelper`:

```java
package your.module.requesthandlers;

import com.mendix.externalinterface.connector.RequestHandler;
import com.mendix.m2ee.api.IMxRuntimeRequest;
import com.mendix.m2ee.api.IMxRuntimeResponse;
import com.mendix.http.CspHelper;
import com.mendix.core.Core;

public class YourRequestHandler extends RequestHandler {

@Override
protected void processRequest(IMxRuntimeRequest request, IMxRuntimeResponse response, String path) throws Exception {
try {
// Add the configured CSP header from the application
Core.csp().addHeader(response);

// Set response content type
response.setContentType("text/html");

// Generate your response content with conditional nonce support
String htmlContent = generateHtmlWithCSP(response);

// Write the response
response.getWriter().write(htmlContent);

} catch (Exception e) {
logger.error("Error processing request: " + e.getMessage(), e);
response.setStatus(IMxRuntimeResponse.INTERNAL_SERVER_ERROR);
response.sendError("Internal server error");
}
}

private String generateHtmlWithCSP(IMxRuntimeResponse response) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n");
html.append("<head>\n");
html.append(" <title>Your Module</title>\n");

// Only use nonce if it's configured in the CSP template
if (Core.csp().hasNonce(response)) {
String nonce = Core.csp().getNonce(response);
html.append(" <script nonce=\"").append(nonce).append("\">\n");
html.append(" // Your inline JavaScript here\n");
html.append(" console.log('This script is CSP-compliant with nonce');\n");
html.append(" </script>\n");
} else {
// Alternative approach when nonce is not configured
html.append(" <script src=\"/path/to/external/script.js\"></script>\n");
}

html.append("</head>\n");
html.append("<body>\n");
html.append(" <h1>Your Module Content</h1>\n");
html.append(" <!-- Your content here -->\n");
html.append("</body>\n");
html.append("</html>\n");

return html.toString();
}
}
```

### Best Practices for CSP in Request Handlers

When implementing CSP support in your request handlers, follow these best practices:

1. **Use CspHelper for conditional nonce support** - Always check if nonce is configured before using it:
```java
if (Core.csp().hasNonce(response)) {
String nonce = Core.csp().getNonce(response);
// Use nonce for inline content
} else {
// Use external resources or alternative approach
}
```

2. **Always add CSP headers** - Use `Core.csp().addHeader(response)` to ensure your module respects the application's CSP configuration when serving HTML content.

3. **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.

4. **Avoid inline scripts and styles when possible** - Prefer external files that can be loaded via `'self'` directive.

5. **Test with strict CSP** - Test your request handlers with `default-src: 'self'` to ensure they work with the strictest CSP settings.

### Common CSP Issues in Request Handlers

When working with CSP in request handlers, you may encounter these common issues:

#### Base64 Images
If your request handler generates inline Base64 images, these will be blocked by strict CSP. Consider these alternatives:
- Serving images as separate endpoints
- Using external image hosting
- Adding `data:` to `img-src` directive (less secure)

#### Dynamic Script Generation
Avoid generating `<script>` tags dynamically without nonces. Instead:
- Use the provided nonce for any inline scripts
- Move logic to external JavaScript files
- Use data attributes and external scripts to handle dynamic behavior

#### Third-party Resources
If your module loads external resources, ensure they're allowed by the CSP or provide configuration options for developers to whitelist them.

#### Error Handling
When CSP violations occur, implement proper error handling:
```java
// Log CSP-related errors for debugging
if (Core.csp().hasNonce(response)) {
logger.debug("Using CSP with nonce: " + Core.csp().getNonce(response));
} else {
logger.debug("CSP configured without nonce support");
}
```

## Enabling the Header in the Cloud

There are two ways to enable the header in the Cloud:

1. Using the [Headers](/refguide/configuration/#headers) custom runtime setting (Recommended ✅). Use this if you need nonce-based CSP support. You can configure this in the Developer Portal under [Custom Runtime Settings](/developerportal/deploy/environments-details/#custom-runtime-settings).
2. Using the [HTTP Headers](/developerportal/deploy/environments-details/#http-headers) UI in the *Environment Details* section. Can be used for basic CSP support.
1 change: 1 addition & 0 deletions content/en/docs/refguide/runtime/custom-settings/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ The following custom settings can be configured:
| <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 |
| <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) |
| <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) |
| <a id="Headers" href="#Headers">Headers</a> | A JSON object containing custom headers (key-value). For example, can be used to define a `Content-Security-Policy`, e.g.: `{ "Content-Security-Policy": "script-src 'nonce-{{ NONCE }}'" }`. | `{}` |
| <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) |
| <a id="TempPath" href="#TempPath">TempPath</a> | The location of the temporary files. | [deployment folder]\data\tmp |
| <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 |
Expand Down