Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce DirectiveSetBuilderInterface to allow runtime modification #348

Merged
merged 2 commits into from
Mar 11, 2025

Conversation

martijnc
Copy link
Contributor

@martijnc martijnc commented Jun 1, 2024

This PR introduces the DirectiveSetBuilderInterface and the default implementation ConfigurationDirectiveSetBuilder proposed in #347, to allow for runtime modification of the CSP directive sets. The major changes are:

  • Introduction of DirectiveSetBuilderInterface and ConfigurationDirectiveSetBuilder;
  • The ContentSecurityPolicyListener constructor now takes DirectiveSetBuilderInterface instead of the directive sets directly;
  • Changes to NelmioSecurityExtension to provide the configuration to the ConfigurationDirectiveSetBuilders, which in turn are injected into ContentSecurityPolicyListener (instead of the directive sets).

This adds a layer between the configuration (and the directive sets built from it) and ContentSecurityPolicyListener. This layer provides an integration point for application code to modify the directive sets based on the request (e.g., in a controller or a kernel event listener).

@martijnc martijnc force-pushed the feature/directive-set-builder branch from 8ea7fa0 to 7126387 Compare June 1, 2024 18:47
@martijnc martijnc force-pushed the feature/directive-set-builder branch from 7126387 to 65a8e55 Compare June 14, 2024 18:25
@martijnc martijnc force-pushed the feature/directive-set-builder branch from 65a8e55 to e86b401 Compare July 3, 2024 18:07
@Seldaek
Copy link
Member

Seldaek commented Jul 5, 2024

Sorry but I won't manage to review this one right now, seems solid at first sight but I'd rather give it some more thought.

@acrobat
Copy link

acrobat commented Feb 20, 2025

@Seldaek Any chance this can be looked at? Thanks in advance!

@Seldaek Seldaek merged commit 140a3dc into nelmio:master Mar 11, 2025
14 checks passed
@Seldaek
Copy link
Member

Seldaek commented Mar 11, 2025

Thanks - what would be great is if someone can provide some code sample for the docs on how this can be used to configure extra values

@karstennilsen
Copy link

karstennilsen commented Mar 11, 2025

I now implemented my CSP exceptions like this:

public function page(ContentSecurityPolicyListener $cspListener) {
   // Allow *.doubleclick.net as extra script-src
    $cspListener->getEnforcement()->setDirective('script-src', $cspListener->getEnforcement()->getDirective('script-src') . ' *.doubleclick.net');

    return $this->render('exampe.html.twig');
}

ContentSecurityPolicyListener::getEnforcement() is now deprecated.

Can an example be provided how to achieve the same behaviour in the new way?

@ro0NL
Copy link

ro0NL commented Mar 12, 2025

ref #369 cc @martijnc

@martijnc
Copy link
Contributor Author

The idea is that your app or bundle can now provide its own DirectiveSetBuilderInterface which builds or modifies the directive set. Your implementation can replace or decorate nelmio_security.directive_set_builder.enforce. @.inner would be the default implementation (ConfigurationDirectiveSetBuilder), which holds the directive set that is build from the config. For your case, you could do something like this:

<?php

declare(strict_types=1);

namespace App;

use Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSet;
use Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSetBuilderInterface;

class EnforcedDirectiveSetBuilder implements DirectiveSetBuilderInterface
{
    private DirectiveSet $enforcedSet;

    public function __construct(
        DirectiveSetBuilderInterface $decorated,
    ) {
        // Get the directive set build from the config.
        $this->enforcedSet = $decorated->buildDirectiveSet();
    }

    public function buildDirectiveSet(): DirectiveSet
    {
        return $this->enforcedSet;
    }

    // or `addScriptSrc`, ....
    public function getDirectiveSet(): DirectiveSet
    {
        return $this->enforcedSet;
    }
}

And register it in the container

    App\EnforcedDirectiveSetBuilder:
        decorates: 'nelmio_security.directive_set_builder.enforce'
        arguments:
            - '@.inner'

In your controller you can then inject EnforcedDirectiveSetBuilder instead and do

$cspEnforcement = $this->enforcedDirectiveSetBuilder->getDirectiveSet();
$cspEnforcement->setDirective('script-src', $cspEnforcement->getDirective('script-src') . ' https://*.some');

The EnforcedDirectiveSetBuilder can do a lot more; you can add methods like addScriptSrc or inject the RequestStack and modify based on the request URI, the matched route, ...

I will look at creating a PR to add some examples to the documentation.

@ro0NL
Copy link

ro0NL commented Mar 12, 2025

is this really a gain compared to the previous way of doing it 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants