csp-builder turns a JSON policy file into a real Content-Security-Policy header
Build Content-Security-Policy headers from a JSON file (or build them programmatically)
At a glance
- What is it?
- CSP Builder is a PHP library from Paragon Initiative that builds Content-Security-Policy headers from a JSON configuration file or programmatically, with nonce and hash generation for inline scripts, PSR-7 header injection and saved Apache and nginx snippets. It suits a PHP developer who wants a policy in version control rather than in string concatenation, and it makes the unsafe keywords explicit in the config.
- Who is it for?
- CSP Builder fits a PHP team that wants its Content-Security-Policy in a reviewed JSON file rather than assembled as a string in middleware, and that needs nonces or hashes for inline scripts without hand-rolling the base64 and hash encoding. It does not fit a project that wants a policy generated from live application routes, since the configuration is static.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 66 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The policy lives in JSON, not in a string
CSP Builder is a PHP library for integrating Content-Security-Policy headers into a web application, either from a JSON configuration file or programmatically, and it was created by Paragon Initiative Enterprises as part of an effort to encourage better application security practices.
The JSON approach is the point. A Content-Security-Policy header is a long string with quoting rules and directive names that are easy to get subtly wrong, and a string literal in a middleware class is not something a reviewer can check at a glance. A JSON file can be diffed, can be reviewed as a change, and can be tested.
The three ways to load a policy are shown in the README, and they differ only in how the data arrives.
<?php
use ParagonIE\CSPBuilder\CSPBuilder;
$csp = CSPBuilder::fromFile('/path/to/source.json');
$csp->sendCSPHeader();From a JSON string, with the file read and checked first.
<?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();And from an array, with the decode and the type check done by you.
<?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();The pattern in the last two examples is worth copying regardless of whether you use this library. A failed file read and a failed JSON decode are both checked explicitly and turned into exceptions, rather than being allowed to produce a silently empty policy, which is the failure mode that matters most for a security header.
The config format makes unsafe keywords explicit
The example configuration in the README is a complete policy and it is instructive mostly for what it insists on being stated.
{
"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
}Every directive is present, including the empty ones. `base-uri`, `default-src`, `connect-src`, `media-src`, `object-src`, `plugin-types` and `frame-ancestors` are all empty arrays rather than omitted, so a reader can see that they were considered.
The unsafe keywords are explicit booleans that are set to false. `unsafe-inline` and `unsafe-eval` appear in script-src as false, and `self` appears as false in child-src and true elsewhere. That is the design decision worth noting: the dangerous options cannot be added by accident, because you have to write them down.
The reporting options are there too, with report-only false, a report-to group name, and a report-uri endpoint. The repository has a schema/ directory, so the JSON shape is defined formally rather than by example alone.
Nonces, hashes and the programmatic API
The programmatic side exists for the parts of a policy that change per request, which is exactly the inline script problem.
<?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();The nonce call takes a directive name and returns a value you put on the script tag, and it also adds the matching nonce source to the header, which is the step people forget when doing this by hand. The hash call takes a directive, the script content and an algorithm, sha256 in the example, so a static inline script can be allowed by hash instead of by nonce.
Most of the methods chain, which is what makes the library pleasant to use.
$csp = CSPBuilder::fromFile('/path/to/source.json');
$csp->addSource('image', 'https://ytimg.com')
->addSource('frame', 'https://youtube.com')
->addDirective('upgrade-insecure-requests', true)
->sendCSPHeader();The README lists the full set of chainable methods: addSource(), addDirective(), disableOldBrowserSupport(), enableOldBrowserSupport(), hash(), preHash(), setDirective(), setBlobAllowed(), setDataAllowed(), setFileSystemAllowed(), setMediaStreamAllowed(), setReportUri(), setSelfAllowed(), setAllowUnsafeEval() and setAllowUnsafeInline().
The pair of old browser support methods is worth a note. CSP2 introduced hash-based sources, and browsers before that do not understand them, so a policy that relies on hashes breaks on them. Being able to disable that support deliberately, rather than discovering the problem on an old browser, is a real operational concern.
PSR-7 injection and saved server snippets
Sending a header on every request is not always the right shape, and the library offers two alternatives.
The first is PSR-7. Instead of calling sendCSPHeader(), you can inject the headers into your PSR-7 message object.
/**
* $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);That matters for anyone on a PSR-7 stack, since building the header into the response object is the framework-agnostic way and avoids a direct call to the header function.
The second is saving a snippet for the webserver itself, which the README recommends instead of sending the header on every request.
$policy = CSPBuilder::fromFile('/path/to/source.json');
$policy->saveSnippet(
'/etc/nginx/snippets/my-csp.conf',
CSPBuilder::FORMAT_NGINX
);The reminder afterwards is to reload the webserver. There is a FORMAT_NGINX constant, so Apache is a format constant too.
And there is a processing hook for the output before it is written, which the README calls 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)
);A callable receives the generated output and returns the modified text, so the output changes before the file is saved. That is a slightly unusual signature, since the callable is passed as a third argument rather than as an option, and the arrow function in the example has no comma before it.
Packaging, checks and the support contract
The library is distributed on Packagist as paragonie/csp-builder, and installation is one Composer command.
composer require paragonie/csp-builderThe repository is set up for continuous integration on several fronts. There is a build status badge from a ci.yml workflow, and a separate Psalm status badge from a psalm.yml workflow, so static analysis is a first class part of the build rather than an afterthought. The badges for latest stable and latest unstable versions, the licence and the download count all come from Packagist.
The repository contents are conventional for a well-maintained PHP library: src/ for the code, test/ with a phpunit.xml.dist, a psalm.xml for the static analyser configuration, a phpcs.xml for coding standards, a bin/ directory, a composer.json, and the schema/ directory that defines the JSON configuration shape.
The licence is MIT, with the LICENSE file at the root.
The release history shows a steady cadence: v3.1.0 on 2026-08-18, v3.0.2 on 2025-01-03 and v3.0.1 on 2024-05-08. The last push to the repository was also on 2026-08-18.
One note on the business model. The README ends with a support contracts section, which says that if your company uses this library in your products or services you may be interested in purchasing a support contract from Paragon Initiative Enterprises. That is a commercial offering layered on top of an MIT-licensed library, not a restriction on the licence.
There is also a related project worth knowing: a CSP middleware by geggelto that uses this library, if you would rather not call the builder yourself on each request.
Where CSP Builder is the wrong choice
Three cases argue against it.
The first is a policy that has to be generated from the application's own routes and assets. The configuration here is static JSON. If your policy depends on which page is being served, for example a per-route script-src for a page that loads a particular vendor bundle, this library does not compute that, and you would need to build the policy array yourself and construct the builder from it.
The second is a non-PHP project. This is a PHP library with no service or CLI component, so a Node or Go application has nothing to call. A static site generator or a reverse proxy setup would configure the header in nginx or a CDN configuration instead, which is a different tool for the same job.
The alternative in the same direction is to write the header by hand in your framework's middleware. The difference in approach is that CSP Builder contributes the encoding and the format details: the nonce generation, the hash computation, the escaping of source expressions, and the assembly of the directive list into a valid header string. Hand-writing is a few lines for a static policy and gets error prone as soon as a nonce is involved, since the nonce must be both in the header and on the tag. If your policy is static and simple, a literal string in your configuration is genuinely fine and adds no dependency. The case for the library is a policy with nonces, hashes or many directives, where the string becomes long enough that a mistake in it is likely.
Editorial conclusion
CSP Builder fits a PHP team that wants its Content-Security-Policy in a reviewed JSON file rather than assembled as a string in middleware, and that needs nonces or hashes for inline scripts without hand-rolling the base64 and hash encoding. It does not fit a project that wants a policy generated from live application routes, since the configuration is static. Install it with `composer require paragonie/csp-builder`, build the policy once with `CSPBuilder::fromFile`, and if you serve through nginx or Apache, save a snippet with `saveSnippet` and reload the webserver rather than sending the header on every request.
Frequently asked questions
What does paragonie/csp-builder do?
It is a PHP library for integrating Content-Security-Policy headers into a web application, either from a JSON configuration file or programmatically. It builds and sends the header, injects it into a PSR-7 message, or saves it as an Apache or nginx snippet.
How do I install CSP Builder?
Get Composer and run `composer require paragonie/csp-builder`. The package is published on Packagist as paragonie/csp-builder, and the latest release listed is v3.1.0 from 2026-08-18.
How do I allow an inline script with a nonce using CSP Builder?
Call `$csp->nonce('script-src')`, which returns the nonce value to place on the script tag and adds the matching source to the header. For a static script you can use `$csp->hash('script-src', $someScript, 'sha256')` instead, which allows it by hash.
What JSON keys does the CSP Builder configuration use?
Directives are keys such as default-src, script-src, style-src, img-src, connect-src, font-src, media-src, object-src, frame-ancestors, base-uri, form-action and plugin-types. Each holds an object with booleans such as self, unsafe-inline, unsafe-eval, blob and data, plus an allow array of source expressions. There are also top-level keys report-only, report-to and report-uri.
Can CSP Builder write a config file for nginx instead of sending a header?
Yes. Call `saveSnippet` with a path and `CSPBuilder::FORMAT_NGINX`, and reload the webserver afterwards. You can pass a callable as a third argument to transform the output before it is written to disk.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/paragonie-csp-builder)Was this useful?