···8484//// [read_multipart](#read_multipart "Read and parse a multipart form"),
8585//// [handle_head](#handle_head "Handle HEAD requests as GET requests"),
8686//// [csrf_known_header_protection](#csrf_known_header_protection "Protect against CSRF attacks"),
8787+//// [content_security_policy_protection](#content_security_policy_protection "Protect against XSS attacks"),
8788//// [require_method](#require_method "Middleware to ensure a specific method"),
8889//// [require_content_type](#require_content_type "Middleware to ensure a specific content-type header")
8990////
···123124import gleam/bit_array
124125import gleam/bool
125126import gleam/bytes_tree.{type BytesTree}
127127+import gleam/crypto
126128import gleam/dynamic.{type Dynamic}
127129import gleam/dynamic/decode.{type Decoder}
128130import gleam/http.{type Method, Get, Head}
···609611 }
610612 }
611613 }
614614+}
615615+616616+/// Protects against cross-site-scripting (XSS) attacks using a nonce-based
617617+/// content-security-policy (CSP).
618618+///
619619+/// This middleware will provide a unique single use random string (a nonce) to
620620+/// the handler, and set this CSP header on the response returned by the handler.
621621+///
622622+/// ```txt
623623+/// Content-Security-Policy:
624624+/// script-src 'nonce-{NONCE}' 'strict-dynamic';
625625+/// object-src 'none';
626626+/// base-uri 'none';
627627+/// ```
628628+///
629629+/// This header causes the browser to be restricted in these ways:
630630+///
631631+/// - Any `<script>` tag without a `nonce="..."` property set to the nonce for
632632+/// this request will not be executed. Any scripts created by scripts with
633633+/// the correct `nonce` property will be executed.
634634+///
635635+/// - Any inline JavaScript event handlers on elements will not be evaluated.
636636+/// e.g. `<span onclick="doSomething();">Click me</span>` will do nothing
637637+/// when clicked.
638638+///
639639+/// - Any `<object>` or `<embed>` elements will not be executed.
640640+///
641641+/// - Any use of `<base>` to change the base for relative URLs will be prevented.
642642+///
643643+/// When using this middleware be sure to add the `nonce="..."` property to all
644644+/// `<script>` elements.
645645+///
646646+/// ```gleam
647647+/// use csp_nonce <- smol.content_security_policy_protection()
648648+/// ```
649649+/// ```html
650650+/// <script type="module" nonce="RENDER_YOUR_CSP_NONCE_HERE">
651651+/// console.log("Hello, Joe!")
652652+/// </script>
653653+/// ```
654654+///
655655+/// It is recommended to add this middleware so that it applies to all routes
656656+/// in your application.
657657+///
658658+/// For more information about CSP see these articles:
659659+///
660660+/// - <https://web.dev/articles/strict-csp>
661661+/// - <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy>
662662+///
663663+pub fn content_security_policy_protection(
664664+ then handle_request: fn(String) -> Promise(Response),
665665+) -> Promise(Response) {
666666+ let nonce =
667667+ crypto.strong_random_bytes(18) |> bit_array.base64_url_encode(False)
668668+ let header =
669669+ "script-src 'nonce-"
670670+ <> nonce
671671+ <> "' 'strict-dynamic'; object-src 'none'; base-uri 'none'"
672672+ handle_request(nonce)
673673+ |> with_extra_headers([#("content-security-policy", header)])
612674}
613675614676/// A middleware function ensuring that the request has a specific HTTP method.