Skip to content

feat(symfony): emit a csp nonce on the swagger ui and graphiql scripts - #8310

Merged
soyuka merged 1 commit into
api-platform:mainfrom
soyuka:fix/swagger-csp-nonce-8142-impl
Sep 11, 2026
Merged

feat(symfony): emit a csp nonce on the swagger ui and graphiql scripts#8310
soyuka merged 1 commit into
api-platform:mainfrom
soyuka:fix/swagger-csp-nonce-8142-impl

Conversation

@soyuka

@soyuka soyuka commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

Under a Content Security Policy such as script-src 'self' 'strict-dynamic', the bundled SwaggerUI and GraphiQL <script> tags were blocked because they carried no nonce. They now receive one, reusing existing mechanisms with no new config key:

  • a _csp_nonce request attribute (set by a proxy/middleware) takes precedence;
  • otherwise the nelmio csp_nonce('script') Twig function is reused when available — so the emitted nonce matches the one NelmioSecurityBundle adds to the CSP response header;
  • absent both, output is unchanged (no nonce attribute) so plain installs are unaffected.

The nonce is resolved in PHP (SwaggerUiProcessor / GraphiQlAction), not in Twig. This keeps any csp_nonce() call out of the templates: a literal csp_nonce() in a template would throw a Twig SyntaxError at parse time when the function is not registered, and a runtime {% if %} guard cannot prevent that. Resolving in PHP avoids the problem entirely — the template just emits the attribute when a nonce is present.

Scope

  • SwaggerUI (SwaggerUi/index.html.twig via SwaggerUiProcessor): inline data block, swagger/redoc/scalar bundles, init scripts.
  • GraphiQL (Graphiql/index.html.twig via GraphiQlAction): importmap, inline data block, init-graphiql.js module.

Out of scope: style-src nonces and the Scalar cdn.jsdelivr.net host (covered by strict-dynamic once a nonced script bootstraps). This PR addresses script-src only, per the issue.

Reproduction

Visiting /api or /graphql/graphiql with a strict CSP blocked the scripts (no nonce emitted).

Test plan

  • New SwaggerUiCspNonceTest and GraphiqlCspNonceTest (4 cases each): request-attribute nonce, csp_nonce() nonce, request-attr precedence over the function, and no-mechanism → no nonce attribute.
  • GraphiQlActionTest unit updated (stubs getFunction('csp_nonce')).
  • DocumentationActionTest regression green (9/9).
  • PHPStan (max) + php-cs-fixer clean on changed files.

Fixes #8142

@soyuka soyuka added OpenAPI Symfony Symfony bridge: routing/DI/eventlistener labels Jun 16, 2026
@soyuka
soyuka force-pushed the fix/swagger-csp-nonce-8142-impl branch from c2b555d to 8ef0a96 Compare June 16, 2026 14:23
@soyuka soyuka changed the title fix(symfony): emit a csp nonce on the swagger ui scripts fix(symfony): emit a csp nonce on the swagger ui and graphiql scripts Jun 16, 2026
@soyuka
soyuka force-pushed the fix/swagger-csp-nonce-8142-impl branch from 8ef0a96 to 355ce8a Compare June 16, 2026 14:29
@soyuka
soyuka changed the base branch from 4.3 to main June 16, 2026 14:29
@soyuka soyuka changed the title fix(symfony): emit a csp nonce on the swagger ui and graphiql scripts feat(symfony): emit a csp nonce on the swagger ui and graphiql scripts Jun 16, 2026
Under a CSP like "script-src 'self' 'strict-dynamic'" the bundled SwaggerUI
and GraphiQL scripts were blocked because they carried no nonce. Emit one,
reusing existing mechanisms with no new config key: a `_csp_nonce` request
attribute takes precedence, otherwise the nelmio `csp_nonce('script')` Twig
function is reused (so the emitted nonce matches the one nelmio adds to the
CSP response header); absent both, output is unchanged.

The nonce is resolved in PHP, so no `csp_nonce()` call appears in the
templates and plain installs without nelmio are unaffected.

Fixes api-platform#8142
@soyuka
soyuka force-pushed the fix/swagger-csp-nonce-8142-impl branch from 355ce8a to a9f111c Compare June 16, 2026 14:54
@micheh

micheh commented Jul 25, 2026

Copy link
Copy Markdown

The nonce is resolved in PHP (SwaggerUiProcessor / GraphiQlAction), not in Twig. This keeps any csp_nonce() call out of the templates: a literal csp_nonce() in a template would throw a Twig SyntaxError at parse time when the function is not registered, and a runtime {% if %} guard cannot prevent that. Resolving in PHP avoids the problem entirely — the template just emits the attribute when a nonce is present.

You could use the guard tag in Twig to only call the csp_nonce function if it is available: https://twig.symfony.com/doc/3.x/tags/guard.html

@soyuka
soyuka merged commit 24871a9 into api-platform:main Sep 11, 2026
81 of 96 checks passed
soyuka added a commit that referenced this pull request Sep 11, 2026
* fix(test): use the extracted ApiTestCase namespace

#7887 moved the test helpers to ApiPlatform\Test and deleted
src/Symfony/Bundle/Test. 11 test files still referenced the old FQCN,
either merged on main after #7887 or brought up from 4.3/4.4. The
resulting fatal took down the whole PHPUnit matrix and PHPStan.

Also stubs getFunction('csp_nonce') on the Twig double in
EntrypointActionTest: #8310 updated GraphiQlActionTest but missed this
one, which builds the same GraphiQlAction.

* feat(symfony): deprecate the moved test helper classes

#7887 moved eight public classes out of api-platform/symfony without a
deprecation path, so user test suites extending ApiTestCase break on a
plain "class not found". Restore them at the old FQCNs as deprecated
shims delegating to ApiPlatform\Test.

Client, Response, ArraySubset and MatchesJsonSchema are final and
cannot be subclassed, so they are class_alias'd; the traits are wrapped;
ApiTestCase subclasses. Adds api-platform/test and
symfony/deprecation-contracts to the component requirements.

* fix(symfony): satisfy cs-fixer and phpstan on the shims

CS Fixer's fully_qualified_strict_types applies to docblocks, so the
{@see} tags must use the imported alias. PHPStan reports trait.unused
for the three trait shims, which exist only for BC and are legitimately
used nowhere in the monorepo.

* fix: drop phpstan ignores left dead by #8517

#8517 removed the Request::getContentType() fallbacks and the
method_exists() guards around getContentTypeFormat(), but kept the
ignore patterns targeting them, so phpstan reports them as unmatched.
Neither has a call site left.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OpenAPI Symfony Symfony bridge: routing/DI/eventlistener

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Swagger UI scripts blocked by CSP when using 'strict-dynamic'

3 participants