-
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
fix: split large Content-Security-Policy headers over multiple HTTP headers #60337
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
base: master
Are you sure you want to change the base?
Changes from all commits
04e5ce2
d31ffcc
b3101fe
5f66758
95470a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,34 @@ public function setReadfile($path) { | |
| */ | ||
| #[\Override] | ||
| public function setHeader($header) { | ||
| // 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; | ||
| if (strlen($header) > $maxLen) { | ||
| foreach (['Content-Security-Policy:', 'Feature-Policy:'] as $prefix) { | ||
| if (str_starts_with($header, $prefix)) { | ||
| $value = ltrim(substr($header, strlen($prefix))); | ||
| $directives = array_filter(array_map(trim(...), explode(';', $value))); | ||
| $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); | ||
| } | ||
|
Comment on lines
+58
to
+72
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| return; | ||
| } | ||
| } | ||
| } | ||
| header($header); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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