Giter Site home page Giter Site logo

paragonie / csp-builder Goto Github PK

View Code? Open in Web Editor NEW
541.0 26.0 41.0 218 KB

Build Content-Security-Policy headers from a JSON file (or build them programmatically)

Home Page: https://paragonie.com/projects

License: MIT License

PHP 100.00%
csp csp-header json-configuration csp-builder content-security-policy http http-header php easy-to-use secure-by-default

csp-builder's Introduction

Content Security Policy Builder

Build Status Psalm Status Latest Stable Version Latest Unstable Version License Downloads

Easily integrate Content-Security-Policy headers into your web application, either from a JSON configuration file, or programatically.

CSP Builder was created by Paragon Initiative Enterprises as part of our effort to encourage better application security practices.

Check out our other open source projects too.

There's also a CSP middleware available that uses this library.

Installing

First, get Composer, then run:

composer require paragonie/csp-builder

Build a Content Security Policy header from a JSON configuration file

<?php

use ParagonIE\CSPBuilder\CSPBuilder;

$csp = CSPBuilder::fromFile('/path/to/source.json');
$csp->sendCSPHeader();

You can also load the configuration from a JSON string, like so:

<?php

use ParagonIE\CSPBuilder\CSPBuilder;

$configuration = file_get_contents('/path/to/source.json');
if (!is_string($configuration)) {
    throw new Error('Could not read configuration file!');
}
$csp = CSPBuilder::fromData($configuration);
$csp->sendCSPHeader();

Finally, you can just pass an array to the first argument of the constructor:

<?php

use ParagonIE\CSPBuilder\CSPBuilder;

$configuration = file_get_contents('/path/to/source.json');
if (!is_string($configuration)) {
    throw new Error('Could not read configuration file!');
}
$decoded = json_decode($configuration, true);
if (!is_array($decoded)) {
  throw new Error('Could not parse configuration!');
}
$csp = new CSPBuilder($decoded);
$csp->sendCSPHeader();

Example

{
    "report-only": false,
    "report-to": "PolicyName",
    "report-uri": "/csp_violation_reporting_endpoint",
    "base-uri": [],
    "default-src": [],    
    "child-src": {
        "allow": [
            "https://www.youtube.com",
            "https://www.youtube-nocookie.com"
        ],
        "self": false
    },
    "connect-src": [],
    "font-src": {
        "self": true
    },
    "form-action": {
        "allow": [
            "https://example.com"
        ],
        "self": true
    },
    "frame-ancestors": [],
    "img-src": {
        "blob": true,
        "self": true,
        "data": true
    },
    "media-src": [],
    "object-src": [],
    "plugin-types": [],
    "script-src": {
        "allow": [
            "https://www.google-analytics.com"
        ],
        "self": true,
        "unsafe-inline": false,
        "unsafe-eval": false
    },
    "style-src": {
        "self": true
    },
    "upgrade-insecure-requests": true
}

Build a Content Security Policy, programmatically

<?php

use ParagonIE\CSPBuilder\CSPBuilder;

$csp = CSPBuilder::fromFile('/path/to/source.json');

// Let's add a nonce for inline JS
$nonce = $csp->nonce('script-src');
$body .= "<script nonce={$nonce}>";
    $body .= $desiredJavascriptCode;
$body .= "</script>";

// Let's add a hash to the CSP header for $someScript
$hash = $csp->hash('script-src', $someScript, 'sha256');

// Add a new source domain to the whitelist
$csp->addSource('image', 'https://ytimg.com');

// Set the Report URI
$csp->setReportUri('https://example.com/csp_report.php');

// Let's turn on HTTPS enforcement
$csp->addDirective('upgrade-insecure-requests', true);

$csp->sendCSPHeader();

Note that many of these methods can be chained together:

$csp = CSPBuilder::fromFile('/path/to/source.json');
$csp->addSource('image', 'https://ytimg.com')
    ->addSource('frame', 'https://youtube.com')
    ->addDirective('upgrade-insecure-requests', true)
    ->sendCSPHeader();
  • addSource()
  • addDirective()
  • disableOldBrowserSupport()
  • enableOldBrowserSupport()
  • hash()
  • preHash()
  • setDirective()
  • setBlobAllowed()
  • setDataAllowed()
  • setFileSystemAllowed()
  • setMediaStreamAllowed()
  • setReportUri()
  • setSelfAllowed()
  • setAllowUnsafeEval()
  • setAllowUnsafeInline()

Inject a CSP header into a PSR-7 message

Instead of invoking sendCSPHeader(), you can instead inject the headers into your PSR-7 message object by calling it like so:

/**
 * $yourMessageHere is an instance of an object that implements 
 * \Psr\Http\Message\MessageInterface
 *
 * Typically, this will be a Response object that implements 
 * \Psr\Http\Message\ResponseInterface
 *
 * @ref https://github.com/guzzle/psr7/blob/master/src/Response.php
 */
$csp->injectCSPHeader($yourMessageHere);

Save a CSP header for configuring Apache/nginx

Instead of calling sendCSPHeader() on every request, you can build the CSP once and save it to a snippet for including in your server configuration:

$policy = CSPBuilder::fromFile('/path/to/source.json');
$policy->saveSnippet(
    '/etc/nginx/snippets/my-csp.conf',
    CSPBuilder::FORMAT_NGINX
);

Make sure you reload your webserver afterwards.

Processing output before save to disk through hook

$policy = CSPBuilder::fromFile('/path/to/source.json');
$policy->saveSnippet(
    '/etc/nginx/snippets/my-csp.conf',
    CSPBuilder::FORMAT_NGINX
    fn ($output) =>  \str_replace('bar','foo',$output)
);

The output will change before save to file

Support Contracts

If your company uses this library in their products or services, you may be interested in purchasing a support contract from Paragon Initiative Enterprises.

csp-builder's People

Contributors

alainwolf avatar brucegithub avatar busterneece avatar danieltott avatar firesphere avatar fritzmg avatar furgas avatar gszy avatar iangcarroll avatar kronthto avatar nenglish7 avatar ocramius avatar paragonie-scott avatar paragonie-security avatar swiffer avatar tforesti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

csp-builder's Issues

report-uri gets wrongly encoded

Since version 2.8.0, the report-uri is wrongly encoded when injecting the CSP header.

2.8.0

report-uri https%3A//

2.7.0

report-uri https://...

This leads to a 404 if a CSP violation happens, as the browser prefixes the request with the local URL.

missing self in readme example

the json example in the readme has no self in various params but the json file from the tests does have these set.
https://github.com/paragonie/csp-builder#example
https://github.com/paragonie/csp-builder/blob/e9a7560fd3f133a85f03c51de5fc051ac97630a7/test/vectors/basic-csp.json

for example i am guessing that using the example from the readme does not set self fore base-uri. but that might not be a good example then, right? the resulting csp will not work well.

unless i am misstaken please adjust the readme json example so it will have sensible defaults where self might be needed. thanks.

.htaccess support for headers

It would be nice if there was an option to generate the CSP headers in the .htaccess for Apache, removing the need to restart Apache after generating the script.

ambiguity: PSR-7 Message

What PSR object are you referring to?

I assume the Response Object, but I just want to be sure.

Support frame-src for compatibility with WebKit (CSP 1)

The frame-src directive is deprecated in CSP 2 and is replaced by child-src which is correctly supported by csp-builder. Unfortunately, this will result in WebKit based browsers which only support CSP 1 falling back to default-src for nested browsing contexts, which could cause unexpected behaviour.

https://github.com/paragonie/csp-builder/blob/master/src/CSPBuilder.php#L85

In order to support WebKit you should allow a user to specify the frame-src directive. If the user does not specify a frame-src directive, the value of the child-src directive could be duplicated in the frame-src directive. There isn't really a downside apart from some header bloat and it results in support for WebKit based browsers like Safari and Edge.

@troyhunt experienced the issue and I assisted in debugging it: http://www.troyhunt.com/2015/10/how-to-break-your-site-with-content.html

Add method for retrieving value set for a directive

The CSP builder has no method for retrieving the value that's currently set for directives. There's only a method to set the value for a directive. I have a scenario where I initialized the CSP builder from a large config array, and now I need to apply some selective additions to it, but need to get the value for some directives first.

Allow adding "blob" as source in JSON

I would like to do this:

"img-src": {
"self": true,
"data": true,
"blob": true
},

However, the "blob":true has no effect. I can resolve this by adding it in the other way:

$csp->addSource('image-src', "blob:");

but that results in having to look at annoying error messages in the console:
1

As a solution I would also accept any way to remove the automatic addition of "http://blob" and "https://blob" to the source list.

Save to JSON

Add the ability to save the generated headers to a JSON file.

Current situation

  • An engineer has to manually generate a JSON file or array to load the pre-set configuration
  • The updates to the configuration (e.g. user custom added domains) can not be saved in the JSON

Wished situation

  • Add an API point to export the generated CSP headers, minus calculated hashes, to a JSON or array
  • Enabling a much faster loading of the configuration (bypassing most of e.g. database calls)

Additional awesomeness

  • Make the loading and exporting time-based. If a change is detected compared to the latest version requested (e.g. latest change in a database), load and update the config afterwards

(I understand additional awesomeness is very project specific)

Invokable Extension

I wouldn't mind using this in my production envrionment, would you mind if I PR'd an extension that implements the __invoke method for PSR-7 middleware?

Nonce means number used once, random bytes used

In the current CSP builder the nonce consists of random bytes, while nonce means number used once. According to MDN, depending on the specific directive you are visiting, it is confirmed that the nonce should be a number. Some other directives don't specify it.

Now I don't know if the usage of random_bytes is intentionally chosen. I did not encounter any problems with it in the browser, so it seems fine. But maybe you could consider to change it, or reject it if you feel the current implementation is fine.

Unable to add "blob" value to connect-src in json file

I had download the csp-builder version 1.2.0 and configure json as required. But whenever i want to set Blob to connect-src it wont be added to headers this leads to not allowed blob data. How can i add "blob":true value to connect-src file to overcome that issue.

Add a scheme to whitelisted hosts by default if not specified

The csp-builder allows you to specify hosts such as "scotthelme.co.uk" which is a valid definition as per the specification.

https://github.com/paragonie/csp-builder/blob/master/src/CSPBuilder.php#L180

In this scenario the browser should allow the scheme that the policy was delivered over to be used for the 3rd party host. This doesn't work as expected in Safari and can cause unexpected blocking behaviour. Specifying "scotthelme.co.uk" as an allowed host in any policy directive would not allow that resource type to be loaded from "https://scotthelme.co.uk". You can view more details on the bug here: https://scotthelme.co.uk/safari-doesnt-like-csp/

If the user doesn't specify a scheme the csp-builder could default to, or have an option to, use the scheme of the current page obtained by parsing the $_SERVER['REQUEST_URI'] or $_SERVER['HTTPS'].

If the policy is served over HTTP then all hosts specified without a scheme would default to HTTP. In this scenario the browser will allow a scheme upgrade to HTTPS.

If the page is served over HTTPS then all hosts specified without a scheme would default to HTTPS. In this scenario the browser will not allow a scheme downgrade to HTTP.

DOCUMENTATION?????????????

For the love of God people, could you please document this?

I've spent an hour trying to figure out how to set report-uri and simply nothing works. CSPBuilder.php on line 107 thinks "/csp_reporting.php" is an array. Or something. I don't know, because I have no $#%*)^% idea how it's intended to work. AddDirective()? AddSource()? SacrificeFirstBornAtMidnight()?

Ability to set both "report-uri" AND "report-to"

Hi,

Recently report-uri has been deprecated - however it's still recommended to provide a URL for this field for older browsers.

However browsers like Chrome are now only using the report-to which defines a group within a new Report-To response header.

Currently the code just runs $compiled []= 'report-to ' . $this->policies['report-uri'] . '; ';

This means the value is always the same for both.

Would it be possible to allow individual control for the report-to directive? this way we can leave a URL for older browsers in the report-uri section and then have a group name in the report-to directive? e.g. something maybe like:

if (empty($this->policies['report-to'])) {
    $compiled []= 'report-to ' . $this->policies['report-uri'] . '; ';
}

Then if the developer defines a report-to it will allow it?

PSR-7 support, or else array of headers to be returned

This repo provides very useful functionality, but directly messes with global state via the header function.

Hereby I suggest one of either:

  • being able to inject CSP headers in a PSR-7 request (wouldn't become a dependency: just a dev-dependency or a mocked one)
  • provide the list of headers as an array (mainly because there is more than one header, according to
    \header($which.': '.$this->compiled);
    if ($legacy) {
    // Add deprecated headers for compatibility with old clients
    \header('X-'.$which.': '.$this->compiled);
    $which = $this->reportOnly
    ? 'X-Webkit-CSP-Report-Only'
    : 'X-Webkit-CSP';
    \header($which.': '.$this->compiled);
    )

"support older browsers" nonce fix

Older versions of iOS Safari (iOS 9 and earlier) don't understand CSP nonces. So when using nonces, if you want those browsers to work you have to add unsafe-inline as well. Of course, this is less secure again.

Firefox and Edge ignore the "unsafe-inline" directive if nonces are also called, so this is fine in those browsers; but... I can't determine if Chrome or newer versions of iOS Safari (10+) do the same. Thus, I'm not positive that just adding unsafe-inline is the correct (safe) fix. Worth investigating though.

Support for Feature-Policy header?

Feature-Policy is a new header that, in format, seems to look exactly like CSP. It uses a separate Feature-Policy HTTP header and instead of script-src, img-src, and so on as directives, it uses directives such as geolocation and vibrate.

It would be neat if csp-builder supported these. I realize that it is not strictly the same as CSP. An alternative would be to create a new project, though it would likely duplicate most efforts as the structure - and likely code - would mostly be the same.

[1] https://wicg.github.io/feature-policy/
[2] https://developers.google.com/web/updates/2018/06/feature-policy
[3] https://caniuse.com/#search=feature-policy

My browser keeps ignoring this.

  • CSP14309: Unknown directive 'upgrade-insecure-requests' in Content-Security-Policy - directive will be ignored.

Browser: Microsoft Edge

report-to directive not handled well by Chrome 76

Chrome 76 seems to not handle how csp-builder does report-to; when a report URI is set, Chrome does not send any CSP reports with v2.3.1. Only when the report-to directive is removed does Chrome send reports correctly (presumably to report-uri).

It seems like you can't just pass a normal URL as a report-to value. Did the CSP spec change between implementation and now?

Add worker-src

New Directive: worker-src

This used to be handled by the (now deprecated) child-src. Child-src was split into frame-src and worker-src. You should also mark child-src as deprecated in CSP-Builder.

HOWEVER, worker-src is a bit odd because it follows an unusual fallback pattern:

  • worker-src
  • child-src
  • script-src
  • default-src

So without setting worker-src it looks to script-src. (The current workaround with CSP-builder is to set both frame-src and child-src; then child-src serves as a de facto worker-src.)

https://bugzilla.mozilla.org/show_bug.cgi?id=1302667

Integrate or document report-to

It appears that in CSPv3 report-uri is deprecated and replaced with report-to. Report-to looks like a more complicated way to specify types and endpoints. I can see in the code mentioned, that report-to is adding report-uri annotation (for compatibility), but seem to not find a way to define the reporting string. Please either implement or document this feature.

Example:

Report-To: { "group": "csp-endpoint",
              "max_age": 10886400,
              "endpoints": [
                { "url": "https://example.com/csp-reports" }
              ] },
            { "group": "hpkp-endpoint",
              "max_age": 10886400,
              "endpoints": [
                { "url": "https://example.com/hpkp-reports" }
              ] }
Content-Security-Policy: …; report-to csp-endpoint

Suggestion: check for duplicate addSource

It would be a nice touch if the script automatically adjusted for duplicate addSource() calls.

I have a template system that uses CSP-Builder, and any time I link an external JavaScript file (via class method), it automatically runs the appropriate addSource() for the domain. But if I include http://example.com/script1.js and http://example.com/script2.js , the domain is duplicated in the CSP script-src -- e.g. "script-src http://example.com http://example.com;"

Doesn't seem to harm anything; just trying to keep clean code. :-) Thanks for you work on this class; very handy!

How to add strict-dynamic ?

How do I add a 'strict-dynamic' directive for styles/scripts? The directive is mentioned in the code, but not clear how to use it.

Support manifest-src directive

What it says in the header. Please add support for the manifest-src directive.

Wondering if you could future proof this plugin a bit by making a "plugin" mechanism that allows users to add directives and such without directly hacking your code? I realize this library doesn't get updated too often, but some sort of modularity would allow us to add new things without waiting for official updates.

Calling disableOldBrowserSupport can lead to un-expected regressions with frame-src directives

Hi there,
First of all, thanks for this great component!

We just had an issue with some frame-src directives that started to "mysteriously disappear". For example, we initialize a builder and we set some global directives,

$cspBuilder = new \ParagonIE\CSPBuilder\CSPBuilder();
$cspBuilder->addSource('frame-src', 'first-frame-src');
$cspBuilder->disableOldBrowserSupport();

And then in the code, we dynamically add some directives, depending on the requirements,

$cspBuilder->addSource('frame-src', 'second-frame-src`);

When the response is about to being sent, we generate the final header and we send it.

The problem is that calling disableOldBrowserSupport() in between 2 different addSource() for frame-src will generate different directives - see

case 'frame-src':

And in that specific scenario, since we define frame-src and child-src first and then child-src only, we end up with the second-frame-src being only defined in the child-src but the browser cannot resolve it since there is a frame-src directive for the first-frame-src

As per Mozilla doc,

If this directive is absent, the user agent will look for the child-src directive (which falls back to the default-src directive).

Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src

As a work around, we moved the disableOldBrowserSupport() call just after creating the builder and it works but I think that it should be a constructor's option instead so you cannot just change the addSource() behavior in the middle of the CSP builder usage, screwing up the following frame-src directives.

Add data: uris

Not sure if this is possible or not but is there a way to allow additional schemes? Either by an additional class method or via a json config.

Refused to load data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 because it does not appear in the img-src directive of the Content Security Policy.

Content security policy for plugin-types

Awesome idea to make a library for this! 👍

One dimension the browser looses control even with a CSP is via third-party plugins, which may have little / no enforcement.

The good people at Google via the W3C project have a CSP for white-listing allowed plugins, so a website can use plugins, that might break CSP, but restrict them, so the possible attack space is known (for example we know we don't allow swf files, we are not vulnerable to flash exploits...)

Right now it's only reported as working by chrome 40.0+ and android chrome 40.0+ source MDN

w3c spec with examples.
http://www.w3.org/TR/CSP2/#directive-plugin-types

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.