fix: split large Content-Security-Policy headers over multiple HTTP headers#60337
fix: split large Content-Security-Policy headers over multiple HTTP headers#60337n-iv wants to merge 5 commits into
Conversation
…eaders When the Content-Security-Policy header exceeds 7800 bytes (which happens with federation across ~20+ trusted servers), split it into multiple header() calls. Apache mod_proxy_fcgi otherwise fails with AH01070 because HUGE_STRING_LEN is hardcoded at 8192 bytes. Per CSP Level 3 §3.1, multiple CSP headers are enforced as the intersection of policies, so client behavior is unchanged. Signed-off-by: Nicolas Varlot <nicolas.varlot@ac-versailles.fr>
There was a problem hiding this comment.
Pull request overview
This PR addresses Apache mod_proxy_fcgi failures caused by oversized Content-Security-Policy response headers by splitting long CSP/Feature-Policy values into multiple header() calls (splitting only between directives) to stay under a safe per-header length threshold.
Changes:
- Add logic in the HTTP output layer to split large
Content-Security-PolicyandFeature-Policyheaders into multiple header lines. - Keep splitting boundaries at directive separators (
;) to avoid splitting inside directives.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nicolas Varlot <nicolas.varlot@ac-versailles.fr>
Added comments to explain the hardcoded value Signed-off-by: Nicolas Varlot <nicolas.varlot@ac-versailles.fr>
|
cc @skjnldsv could be related to nextcloud/documentation#13803 |
Was thinking the same!! |
| $segment = ''; | ||
| $first = true; | ||
| foreach ($directives as $directive) { | ||
| $candidate = $segment === '' ? $directive : $segment . ';' . $directive; | ||
| if (strlen($prefix . ' ' . $candidate . ';') > $maxLen && $segment !== '') { | ||
| header($prefix . ' ' . $segment . ';', $first); | ||
| $first = false; | ||
| $segment = $directive; | ||
| } else { | ||
| $segment = $candidate; | ||
| } | ||
| } | ||
| if ($segment !== '') { | ||
| header($prefix . ' ' . $segment . ';', $first); | ||
| } |
There was a problem hiding this comment.
This logic is way too messy and I'm pretty sure it's also not doing its job correctly.
There was a problem hiding this comment.
The splitter has been running in production for quite some time on a NC32 instance with ~23 trusted servers (CSP ~6.3 kB, splits into two policies), with no observed regression on federated shares, iframes, or form submissions. The output of the production curl is available if useful.
| // Apache mod_proxy_fcgi parses FCGI response headers with a buffer | ||
| // hardcoded at HUGE_STRING_LEN (8192 bytes in httpd.h); 7800 leaves | ||
| // a safety margin for the status line and surrounding header bytes. | ||
| $maxLen = 7800; |
There was a problem hiding this comment.
I really don't like this magic value, it should very likely just be 8192 or 8191.
There was a problem hiding this comment.
7800 is indeed empirical. Just using 8192 isn't quite right though: HUGE_STRING_LEN is the buffer Apache reserves for the full header line (name + ": " + value + CRLF), so the value alone has to fit in roughly 8192 - 27 = 8165 bytes to stay safely under the parser's threshold.
We can express the intent in the code with:
private const HEADER_LINE_MAX = 8192; // Apache HUGE_STRING_LEN
private const HEADER_NAME_OVERHEAD = 32; // name + ": " + CRLF + margin
Or we could keep a single constant CSP_VALUE_MAX = 8000 (or any round number below 8165) with a comment pointing at HUGE_STRING_LEN
Co-authored-by: Kate <26026535+provokateurin@users.noreply.github.com> Signed-off-by: Nicolas Varlot <nicolas.varlot@ac-versailles.fr>
Co-authored-by: Kate <26026535+provokateurin@users.noreply.github.com> Signed-off-by: Nicolas Varlot <nicolas.varlot@ac-versailles.fr>
Summary
When the
Content-Security-Policyheader exceeds 7800 bytes, split it into multipleheader()calls. Apachemod_proxy_fcgiotherwise fails withAH01070 Error parsing script headersand returns HTTP 500 becauseHUGE_STRING_LENis hardcoded at 8192 bytes inhttpd.hand is used to parse FCGI response headers. Raising this limit by recompiling Apache does not fix the issue (other related FCGI buffer limits sit at the same value).The split is performed only between directives, never inside one. Per CSP Level 3 §3.1, multiple CSP headers are enforced as the intersection of the conveyed policies, so behavior is unchanged for compliant clients.
Checklist
3. to review, feature component)stable32)AI (if applicable)