Skip to content

Commit

Permalink
docs: Add more context to CSP ADR (#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
timmc-edx authored Oct 2, 2024
1 parent 000db25 commit 43db7f8
Showing 1 changed file with 6 additions and 2 deletions.
8 changes: 6 additions & 2 deletions docs/decisions/0006-content-security-policy-middleware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ Context
- Gather reports of violations, allowing deployers to discover vulnerabilities even while CSP prevents them from being exploited
- Enforce the use of a business process for adding new external scripts to the site

At the most basic level, CSP allows the server to tell the browser to refuse to load any Javascript that isn't on an allowlist. More advanced deployments can restrict other kinds of resources (including service workers, child frames, images, and XHR connections). A static set of headers can only support an allowlist that is based on domain names and paths, but features such as ``strict-dynamic`` and ``nonce`` allow the server to vouch for scripts individually. This is more secure but requires more integration with application code.
The most basic use of CSP is to allow the server to tell the browser to refuse to load any Javascript that isn't on an allowlist. More complete deployments can restrict other kinds of resources (including service workers, child frames, images, and XHR connections). A static set of headers can only support an allowlist that is based on domain names and paths, but features such as ``strict-dynamic`` and ``nonce`` allow the server to vouch for ``<script>`` elements individually. This is more secure but requires more integration with application code.

If fully deployed, there is a reduction of risk to learners, partners, and the site's reputation. However, for full effect, these security headers must be returned on all responses, including non-HTML resources and error pages. CSP headers must also be tailored not only to each IDA and MFE, but to each *deployment*, as different deployers will load images and scripts from different domains.

CSP policies are often very long (1 kB in one example) and are built from a number of directives that each contain an allowlist or configuration. They cannot be loosened by the addition of later headers, but only tightened. This means that the server must return the entire policy in one shot, or alternatively emit some directives as separate headers. The use of ``strict-dynamic`` can shrink the header and make the policy more flexible, but requires integration between body and headers: The headers must either contain hashes of the scripts, or nonces that are also referenced in the HTML.

Microfrontends are generally served from static file hosting such as Amazon S3 and cannot take advantage of ``strict-dynamic``; they are limited to source allowlists set by the fileserver or a CDN. In contrast, Django-based IDAs could use ``strict-dynamic`` (via nonces and hashes) because of the ability to customize header and body contents for each response.

Different views also have different needs. For example, some of our views need to allow framing by arbitrary other websites, but by default we want all other pages to have a restrictive ``frame-ancestors`` directive. Because CSP is purely additive, this means we can't take the approach of returning one default header with a restrictive ``frame-ancestors`` and then a second header to override it for this handful of views; unlike ``X-Frame-Options``, CSP does not have a concept of overrides. Instead, these views need to return a different version of the default header. String manipulation in a post-response middleware is unpalatable, so an ideally flexible solution might involve views registering their individual needs with a framework that then compiles the results into a final header (with all overrides applied at the individual directive level). Mozilla's `django-csp package <https://github.com/mozilla/django-csp>`__ takes this approach.

Because it's easy to accidentally break the entire website by use of an overly restrictive CSP, there is a way to try out a policy without enforcing it, via the ``Content-Security-Policy-Report-Only`` header. This runs the same policy but only reports violations to a specified server rather than enforcing the policy. For example, if you have a policy that restricts to loading scripts from domains A, B, C, and D and you want to see if you can remove D from that list, you would deploy the pair ``Content-Security-Policy: script-src A B C D`` and ``Content-Security-Policy-Report-Only: script-src A B C`` and check for reports about domain D. This reporting mechanism is critical for low-impact rollouts, so any CSP solution needs to accomodate it.

As of April 2023, most MFEs and other IDAs include inline Javascript, which severely limits the protection that CSP can provide. However, an initial deployment of CSP that locks down script source domains to an allowlist and reports on the locations of remaining inline scripts would be a good start towards cleaning up these inline scripts. In other words, the goal here is to get *started* on CSP.

Decision
Expand All @@ -37,7 +41,7 @@ We will enable this middleware for all Django-based IDAs.
Consequences
************

Django-based IDAs will for now be restricted to using static headers, and will not support ``strict-dynamic``. This is equivalent in configurability to having a CDN attach headers, and is good enough for a first pass, but will almost certainly need to be replaced with a more integrated system later as IDAs are adjusted to become amenable to ``strict-dynamic``.
Django-based IDAs will for now be restricted to using static headers, and will not support ``strict-dynamic`` or view-specific variation. This is equivalent in configurability to having a CDN attach headers, and is good enough for a first pass, but will almost certainly need to be replaced with a more integrated system later as IDAs are adjusted to become amenable to ``strict-dynamic`` and we've already collected all the gains we can achieve with service-wide policies.

Rejected Alternatives
*********************
Expand Down

0 comments on commit 43db7f8

Please sign in to comment.