Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.1.0]

### Changed

- **`strict` preset**: added `'unsafe-inline'` and `https:` as CSP Level 1/2 fallbacks alongside `'strict-dynamic'` (modern browsers ignore them when `'strict-dynamic'` is set), and added `form-action 'self'`.
- **`permissive` preset**: added `'unsafe-eval'` to `script-src` (most legacy apps that need `unsafe-inline` also need `eval`), `connect-src 'self' https:` for XHR/fetch/WebSocket calls, and `form-action 'self'`.

### Fixed

- `csp:check` no longer reports `'unsafe-inline'` as an error when `'strict-dynamic'` is present in the same directive (CSP Level 3 browsers ignore `'unsafe-inline'` in that case, so it's a CSP1/2 fallback, not a vulnerability).

## [2.0.0]

### Breaking changes
Expand Down Expand Up @@ -49,4 +62,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `symfony/twig-bundle` is now optional: install it explicitly if you use nonce/hash Twig helpers

[2.1.0]: https://github.com/aubes/csp-bundle/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aubes/csp-bundle/compare/v1.0.0...v2.0.0
56 changes: 48 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,24 @@ csp:

### Presets

Three built-in presets provide sensible defaults:
Three built-in presets provide sensible defaults. Preset policies are merged with your custom policies: your policies extend the preset, they don't replace it.

| Preset | Description |
|---|---|
| `strict` | Nonce-based with `strict-dynamic`, `object-src 'none'`, `base-uri 'none'` |
| `permissive` | `'self'` + `'unsafe-inline'`, suitable for legacy apps that cannot use nonces |
| `api` | `default-src 'none'`, no framing, no forms |
#### `strict`

Preset policies are merged with your custom policies. Your policies extend the preset, they don't replace it.
Nonce-based policy with `strict-dynamic`. Recommended starting point when your templates can inject nonces on inline scripts and styles.

> **Note:** The `strict` preset uses `strict-dynamic` which requires nonces to work. Without nonces, all scripts will be blocked. Make sure you use the Twig nonce helpers in your templates:
```text
default-src 'self'
script-src 'strict-dynamic' 'unsafe-inline' https:
style-src 'self'
object-src 'none'
base-uri 'none'
form-action 'self'
frame-ancestors 'self'
upgrade-insecure-requests
```

> **Important:** `strict-dynamic` requires nonces to work. Modern browsers (CSP Level 3) ignore the `'unsafe-inline'` / `https:` fallbacks once `'strict-dynamic'` is enforced, so without nonces all your inline scripts will be blocked there. Use the Twig nonce helpers in your templates:
>
> ```twig
> {# Block tag (recommended) #}
Expand All @@ -94,6 +101,39 @@ Preset policies are merged with your custom policies. Your policies extend the p
> </script>
> ```

#### `permissive`

Allows `unsafe-inline` and `unsafe-eval`. Designed for legacy apps that cannot adopt nonces yet, but still want defense in depth.

```text
default-src 'self'
script-src 'self' 'unsafe-inline' 'unsafe-eval'
style-src 'self' 'unsafe-inline'
img-src 'self' data:
font-src 'self'
connect-src 'self' https:
object-src 'none'
base-uri 'self'
form-action 'self'
frame-ancestors 'self'
upgrade-insecure-requests
```

This preset is **less secure than `strict`** (no XSS protection from inline scripts), but it's a reasonable baseline while you incrementally add nonces and migrate to `strict`. Pair it with a [gradual rollout](#gradual-rollout).

#### `api`

Locks everything down. Designed for JSON APIs and other endpoints that don't render HTML.

```text
default-src 'none'
frame-ancestors 'none'
base-uri 'none'
form-action 'none'
```

Apply this preset to your API controllers via `#[CSPGroup('api')]` or a route default.

### Directive names

Use underscore-separated names in YAML configuration:
Expand Down
4 changes: 4 additions & 0 deletions src/Command/CSPCheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ private function checkUnsafeInline(string $group, array $directives): void
{
foreach (['script-src', 'script-src-elem', 'script-src-attr'] as $directive) {
if (isset($directives[$directive]) && \in_array("'unsafe-inline'", $directives[$directive], true)) {
if (\in_array("'strict-dynamic'", $directives[$directive], true)) {
continue;
}

if (!$this->hasNonceOrHash($directives[$directive])) {
$this->finding('error', $group, $directive, "'unsafe-inline' allows execution of arbitrary inline scripts.");
}
Expand Down
7 changes: 5 additions & 2 deletions src/Preset/CSPPreset.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,24 @@ public function policies(): array
return match ($this) {
self::Strict => [
'default-src' => ['self'],
'script-src' => ['strict-dynamic'],
'script-src' => ['strict-dynamic', 'unsafe-inline', 'https:'],
'style-src' => ['self'],
'object-src' => ['none'],
'base-uri' => ['none'],
'form-action' => ['self'],
'frame-ancestors' => ['self'],
'upgrade-insecure-requests' => [],
],
self::Permissive => [
'default-src' => ['self'],
'script-src' => ['self', 'unsafe-inline'],
'script-src' => ['self', 'unsafe-inline', 'unsafe-eval'],
'style-src' => ['self', 'unsafe-inline'],
'img-src' => ['self', 'data:'],
'font-src' => ['self'],
'connect-src' => ['self', 'https:'],
'object-src' => ['none'],
'base-uri' => ['self'],
'form-action' => ['self'],
'frame-ancestors' => ['self'],
'upgrade-insecure-requests' => [],
],
Expand Down
Loading