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

Send a configurable CSP in every HTML response #9665

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

pabzm
Copy link
Member

@pabzm pabzm commented Oct 7, 2024

The CSP gets adapted to remote objects being allowed or not. It can be configured or disabled via the config option content_security_policy (and
content_security_policy_add_allow_remote).

The default is pretty weak in order to not introduce a breaking change, but still not useless (e.g. it adds another layer of defence against loading remote objects unless allowed.)

Closes #9638

@pabzm
Copy link
Member Author

pabzm commented Oct 22, 2024

@alecpl Same question for this: would reviews by other developers help you?

@alecpl
Copy link
Member

alecpl commented Oct 22, 2024

I'm not sure it would help me, but it would likely help the pull request move forward.

@pabzm
Copy link
Member Author

pabzm commented Oct 23, 2024

Are there other things we can do to ease your load? E.g. get more ticket work off your back? Or release/version/patch-management? I notice you're backporting many changes that go into the master-branch to one or two other branches, I guess that's a lot to juggle, too.

@pabzm
Copy link
Member Author

pabzm commented Oct 23, 2024

@johndoh, @mvorisek Do you feel competent to review this PR? It would help us a lot, even if you're not perfectly sure about all parts, just tell us about your opinion.

$csp = $this->app->config->get('content_security_policy');
if (!in_array($csp, ['', false, 'false'])) {
$csp_header = "Content-Security-Policy: {$csp}";
if (isset($this->env['safemode']) && $this->env['safemode'] === true) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs E2E test as it is it can very easily break security if coded wrongly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing is a good point 👍

Do you have something specific in mind regarding breaking security?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean we need to be sure external resources are not fetched (otherwise spammers know what email addresses to spam even more) in every other case than explicitly configured so.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I think this change doesn't enlarge that danger. It only adds a Content-Security-Policy, which should reduce that danger. The safe-flag is not touched.

Do you see any way in which this change could be dangerous?

@alecpl
Copy link
Member

alecpl commented Oct 24, 2024

I'm not convinced that providing such configuration options is a good idea.

@johndoh
Copy link
Contributor

johndoh commented Oct 25, 2024

I know nothing about CSP headers. From a Roundcube code style point of view only I would say that

if (!in_array($csp, ['', false, 'false'])) {

is wrong and it should be

if ($csp = $this->app->config->get('content_security_policy', "default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';")) {

this means, i think, in common with other config values null reverts to a hard coded default and false disables (e.g imap_ns_*).

I would also say the comment in defaults.inc.php needs to mention that content_security_policy must end in a ; or the header will be invalid when concatenated with content_security_policy_add_allow_remote

@pabzm
Copy link
Member Author

pabzm commented Oct 28, 2024

I'm not convinced that providing such configuration options is a good idea.

@alecpl Why? I thought some people might need to change the CSP (e.g. for plugins using external resources), and this way they wouldn't need to patch the code.

@pabzm
Copy link
Member Author

pabzm commented Oct 28, 2024

I know nothing about CSP headers.

@johndoh But your comments are helpful, thank you very much!

this means, i think, in common with other config values null reverts to a hard coded default and false disables

For some values that's right, but defaults.inc.php has a lot of actual default values, too. E.g. most booleans, but also oauth_user_create_map, or blankpage_url.
I would like to avoid repeating the default value, because it's pretty complex in this case – but at the same time it needs to be visible outside of the actual code as a reference. Thus I find the other way more appropriate.
Is there a declared will or style guide towards one of the default value methods?

content_security_policy must end in a ;

Thank you for that catch! I'l solve that in code so admins can't break the CSP so easily by mistake.

The CSP gets adapted to remote objects being allowed or not.
It can be configured or disabled via the config option
`content_security_policy` (and
`content_security_policy_add_allow_remote`).
@pabzm pabzm force-pushed the always-send-csp branch 2 times, most recently from ba0ab41 to a9e0110 Compare October 29, 2024 20:12
@johndoh
Copy link
Contributor

johndoh commented Oct 29, 2024

I would like to avoid repeating the default value,

I agree with this.

I think in the past there has been issues with defaults.inc.php not being properly updated in 3rd party packaged versions of Roundcube (which I acknowledge are not our problem) and people affected by that create issues here. The comment could suggest a value while the code has a reliable default so if defaults.inc.php was not updated there is no unpredictable behavior.

Personally I just don't like the in_array check, I think its unnecessary because it handles the edge case where someone set 'false' vs false and I think in general Roundcube does not do that so why do it for this one var.

If people write a lax default CSP they might set the additional config
option to the blank string, or false. Then the CSP header should not
contain that value.
@pabzm
Copy link
Member Author

pabzm commented Oct 30, 2024

I think in the past there has been issues with defaults.inc.php not being properly updated in 3rd party packaged versions of Roundcube

Oh, I wasn't aware of that. Thank you for that explanation! Then we maybe should have a hardcoded value for all defaults.

I changed the approach to specifying the default value in defaults.inc.php, and having a hardcoded default. And to check that these are in sync, I wrote a test. What do you think about this way?

(I'd be happy to extend this approach to more config options, it would reduce some special case handling in rcube_config, too.)

Personally I just don't like the in_array check, I think its unnecessary because it handles the edge case

I also find it ugly, but I really like solid code, which isn't vulnerable to edge cases. But maybe checking against 'false' is too much. Regarding readability I can turn the check around by using is_string($csp) && strlen($csp) > 0.

Previously the code also treated `'false'` (the string) as invalid, but
that's a very specific check against a specific edge case, which
wouldn't even break the code (but will only make the browser complain),
so I'm dropping that check.
The config value might be something else than a string.
In the previous way useless semicolons could have happened.
We still support PHP v7, which doesn't support union types.
phpstan complains that `assertIsArray($config)` will always fail because
it doesn't know about the side effects of `require`ing the default
config.
@pabzm
Copy link
Member Author

pabzm commented Nov 6, 2024

I'm not convinced that providing such configuration options is a good idea.

@alecpl Why? Some people might need to change the CSP (e.g. for plugins using external resources).

@alecpl
Copy link
Member

alecpl commented Nov 6, 2024

Because it's easy to do wrong. Because CSP can be set on the http server and Roundcube cannot relax the rules, it can only make them more strict. Because the initial problem was lack of documentation, not lack of options. In other words this might be one of those power-user options that may bring more harm than value.

That being said, I don't really see a middle ground here. Either we set CSP (and need options and ways for plugins) or not.

@pabzm
Copy link
Member Author

pabzm commented Nov 6, 2024

From your tone and from the experience of other comment threads I assume that you're not interested in an open discussion of the points you listed. (Please prove me wrong.)

Which makes me wonder what you want from this PR. If you want to veto the change, please say so clearly. If not: please suggest a way forward.

@alecpl
Copy link
Member

alecpl commented Nov 6, 2024

I don't want it to sound like a veto, but I think we should not merge this. As other security-related headers that you can set, add the header sample to the .htaccess file or extend CONTENT-SECURITY-POLICY section in the INSTALL file (we'll get rid of the .htaccess file at some point).

It does not mean we shouldn't make Roundcube compatible with more strict rules in the future, but Roundcube should not set the header itself, imo. Of course, there's always exceptions, we use CSP header on file downloads.

@pabzm
Copy link
Member Author

pabzm commented Nov 7, 2024

This change increases security by introducing a CSP that is stricter if the safe-mode is off. How would you suggest to do that in a HTTP daemon config?

And it increases security of all installations by ensuring from code that the CSP is always sent, not only where an admin took the extra step to configure it.

And it doesn't break anything in a default installation.

Of course people can make themselves a problem with the config option, but that is true for a lot of the config options we already have. How is this one different?

@pabzm
Copy link
Member Author

pabzm commented Nov 8, 2024

@johndoh @mvorisek Do you have an opinion on this PRs approach vs. Alec's suggestion?

@pabzm pabzm self-assigned this Nov 11, 2024
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.

Document a working Content-Security-Policy
4 participants