Skip to content

fix: split large Content-Security-Policy headers over multiple HTTP headers#60337

Open
n-iv wants to merge 5 commits into
nextcloud:masterfrom
n-iv:fix/csp-header-split-for-large-policies
Open

fix: split large Content-Security-Policy headers over multiple HTTP headers#60337
n-iv wants to merge 5 commits into
nextcloud:masterfrom
n-iv:fix/csp-header-split-for-large-policies

Conversation

@n-iv
Copy link
Copy Markdown

@n-iv n-iv commented May 12, 2026

Summary

When the Content-Security-Policy header exceeds 7800 bytes, split it into multiple header() calls. Apache mod_proxy_fcgi otherwise fails with AH01070 Error parsing script headers and returns HTTP 500 because
HUGE_STRING_LEN is hardcoded at 8192 bytes in httpd.h and 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

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

…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>
Copilot AI review requested due to automatic review settings May 12, 2026 21:18
@n-iv n-iv requested a review from a team as a code owner May 12, 2026 21:18
@n-iv n-iv requested review from ArtificialOwl, icewind1991, leftybournes and provokateurin and removed request for a team May 12, 2026 21:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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-Policy and Feature-Policy headers 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.

Comment thread lib/private/AppFramework/Http/Output.php Outdated
Comment thread lib/private/AppFramework/Http/Output.php Outdated
Comment thread lib/private/AppFramework/Http/Output.php Outdated
Comment thread lib/private/AppFramework/Http/Output.php Outdated
n-iv and others added 2 commits May 12, 2026 23:24
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>
@kesselb
Copy link
Copy Markdown
Contributor

kesselb commented May 12, 2026

cc @skjnldsv could be related to nextcloud/documentation#13803

@skjnldsv
Copy link
Copy Markdown
Member

cc @skjnldsv could be related to nextcloud/documentation#13803

Was thinking the same!!

Comment thread lib/private/AppFramework/Http/Output.php Outdated
Comment thread lib/private/AppFramework/Http/Output.php Outdated
Comment on lines +58 to +72
$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);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This logic is way too messy and I'm pretty sure it's also not doing its job correctly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Comment on lines +49 to +52
// 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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I really don't like this magic value, it should very likely just be 8192 or 8191.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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

n-iv and others added 2 commits May 13, 2026 09:13
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants