laravel-debugbar
laravel-debugbar copied to clipboard
Add CSP header if available
TODO; get the source of the CSP in a callable? Counterpart of https://github.com/maximebf/php-debugbar/pull/439
Also need to modify the VarDumper to inject CSP attributes..
Any news on?
@savage-eagle This is merged on the php-debugbar. Still need to see how we're going to add the actual nonce though.
@barryvdh the stylesheet needs a nonce as well.
Haven't properly dug into the matter yet, but here's some first thoughts for adding the nonce. First I thought to add it to the config like this "'nonce' => csp_nonce(),". That would give flexibility to devs for implementing other packages as well. But that throws a BindingResolutionException on app('csp-nonce'). I did manage to set that config from the middleware I wrote to programmatically enable Debugbar like this "config(['debugbar.nonce' => csp_nonce()]);".
Hope to find some time to dig a bit deeper later on.
Got a working proof-of-concept.
config/debugbar.php:
'csp_callback' => 'csp_nonce',
src/JavascriptRenderer.php > renderHead():
$cspString = '';
$cspCallback = config('debugbar.csp_callback');
if ($cspCallback) {
$cspString = sprintf('nonce="%s"', call_user_func($cspCallback));
}
$html = "<link {$cspString} rel='stylesheet' type='text/css' property='stylesheet' href='{$cssRoute}'>";
$html .= "<script {$cspString} type='text/javascript' src='{$jsRoute}'></script>";
if ($this->isJqueryNoConflictEnabled()) {
$html .= '<script ' . $cspString . 'type="text/javascript">jQuery.noConflict(true);</script>' . "\n";
}
However, I ran into the next issue with the inline script tag of the Symfony HtmlDumper. This raises the following question: should vendor packages even implement this kind of change? I am leaning towards this being the responsibility of the package that implements CSP (i.e. spatie/laravel-csp) or the local developer. It should propably be hooked somewhere in the view renderer to add it everywhere it has not been set yet.
Regards, Richard
PS Thanks for your awesome work!
Addendum to my previous statement: ... unless the package injects content through middleware added by an auto-discovered ServiceProvider.
I could not find a way to modify the response content after the middlewares and middlewarePriority wasn't sufficient either.
The inline html also needs to be modified.
src/JavascriptRenderer.php > renderHead():
$html .= preg_replace_callback('/<(script|style)(.*?)>/', function ($matches) {
if (strpos($matches[2], 'nonce=') !== false) {
return $matches[0];
}
return sprintf('<%s nonce="%s"%s>', $matches[1], csp_nonce(), $matches[2]);
}, $this->getInlineHtml());
And the render method.
/**
* {@inheritdoc}
*/
public function render($initialize = true, $renderStackedData = true)
{
return str_replace('<script ', '<script nonce="'.csp_nonce().'" ', parent::render());
}
But that still isn't enough. The inline script creates a style element without a nonce. And assets/javascripts creates a script element without a nonce. What a can of worms...
Yeah now you mention it, I guess this is why I didn't merge this sooner :P Easiest is just to set CSP to warnings on dev, but not ideal.
Yeah. I managed to scotchtape the inline scipt to work as well with another preg_replace by injecting a setAttribute, but assets/javascript gave me a headache. Also experimented a bit with strict-dynamic [1], but could not get that to work.
I have to park this for now.
[1] https://content-security-policy.com/strict-dynamic/
@barryvdh @rdevroede Will there by any more work done on this to get it to work with a CSP header?
@barryvdh @rdevroede Will there by any more work done on this to get it to work with a CSP header?
Sadly, I have not made any progress. I ended up following Barry's suggestion of setting it to warnings on dev and moved on to things with higher priorities.
It feels like this is close. Is it possible to get this working without falling back to the less ideal warnings mode in dev? Making CSP failures work in dev the same as prod are really important to help prevent issues from being accidentally released. Any thoughts, @barryvdh?
Building up to @rdevroede codes, I've managed to put this one up:
src/JavascriptRenderer.php > renderHead():
public function renderHead()
{
$cssRoute = route('debugbar.assets.css', [
'v' => $this->getModifiedTime('css'),
'theme' => config('debugbar.theme', 'auto'),
]);
$jsRoute = route('debugbar.assets.js', [
'v' => $this->getModifiedTime('js')
]);
$cssRoute = preg_replace('/\Ahttps?:/', '', $cssRoute);
$jsRoute = preg_replace('/\Ahttps?:/', '', $jsRoute);
$cspString = '';
$cspCallback = config('debugbar.csp_callback');
if ($cspCallback) {
$this->setCspNonce(call_user_func($cspCallback));
}
$nonce = isset($this->cspNonce) ? $this->getNonceAttribute() : '';
$html = "<link{$nonce} rel='stylesheet' type='text/css' property='stylesheet' href='{$cssRoute}' data-turbolinks-eval='false' data-turbo-eval='false'>";
$html .= "<script{$nonce} src='{$jsRoute}' data-turbolinks-eval='false' data-turbo-eval='false'></script>";
if ($this->isJqueryNoConflictEnabled()) {
$html .= "<script{$nonce} data-turbo-eval='false'>jQuery.noConflict(true);</script>\n";
}
$inlineHtml = $this->getInlineHtml();
if (isset($this->cspNonce)) {
$inlineHtml = preg_replace("/(<(script|style))/", "$1 {$nonce}", $inlineHtml);
$inlineHtml = preg_replace("/(doc\.head\.appendChild\(refStyle\);)/", "refStyle.setAttribute('nonce', '{$this->cspNonce}');\n", $inlineHtml);
}
$html .= $inlineHtml;
return $html;
}
config/debugbar.php:
/*
|--------------------------------------------------------------------------
| CSP Setter
|--------------------------------------------------------------------------
|
| DebugBar injects scripts and styles to show the debug bar. However, this causes lots of policy violations
| when CSP is used. To prevent these violations, a callback will be identified to apply a nonce or hash to the
| elements. A good example of this would be the Laravel CSP by spatie, utilizing the `csp_nonce()` function.
*/
'csp_callback' => env('DEBUGBAR_CSP_CALLBACK', null),
.env:
CSP_ENABLED=true
DEBUGBAR_INJECT=true
DEBUGBAR_CSP_CALLBACK=csp_nonce
The inline part was brute forced, but it did manage to run my debug bar with CSP on since the dumper was only injecting style
elements. The script part was already fixed by rdevroede and that's where I built it from.
I also want to add that the PHP Debugbar from maximebf is using jQuery 3.3.1 which also does not support CSP when creating or appending elements. This was discussed in this jQuery 3.1.1 Issue.
- #1465
- #1464