Skip to content

Type-safe CSP (content security policy) #1797

@kodeFant

Description

@kodeFant

Although IHP has strong XSS protection, adding a builtin built-in feature for managing CSP would make it even more secure.

For Shipnix, I have added some security headers with a Content Security Policy that works well, but as you can see the code quality could be better and more maintainable.

module Application.Helper.SecurityHeaders where

import Application.Helper.Common (CSPNonce (CSPNonce))
import Web.Controller.Prelude

setSecurityHeaders :: _ => IO ()
setSecurityHeaders = do
    nonce <- generateAuthenticationToken
    putContext (CSPNonce nonce)
    -- https://securityheaders.com
    setHeader ("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
    setHeader ("X-Frame-Options", "SAMEORIGIN")
    setHeader ("X-Content-Type-Options", "nosniff")
    setHeader ("Content-Security-Policy", contentSecurityPolicy nonce)
    setHeader ("Referrer-Policy", "strict-origin-when-cross-origin")


contentSecurityPolicy :: _ => Text -> ByteString
contentSecurityPolicy nonce =
    ( if isDevelopment
        then
            [plain|default-src 'self';
img-src 'self' https://tailwindui.com https://avatars.githubusercontent.com data:;
connect-src ws://localhost:* 'self';
style-src  'nonce-#{nonce}' 'self';
script-src 'strict-dynamic' 'nonce-#{nonce}';
object-src 'none';
frame-ancestors http://localhost:8000;
base-uri 'none';
    |]
        else [plain|default-src 'self';img-src 'self' https://avatars.githubusercontent.com  data:;style-src 'nonce-#{nonce}' 'self';script-src 'strict-dynamic' 'nonce-#{nonce}';object-src 'none';frame-ancestors https://shipnix.io;base-uri 'none';connect-src 'self' https://shipnix.io https://plausible.io wss://shipnix.io;|]
    )
        |> cs

I for example generate nonces for each IHP pageload that are put into the context, restricting any unauthorized script and CSS files, preventing for example data mining attacks.

scripts :: Html
scripts =
            [hsx|
            <script nonce={nonce} src={assetPath "/prod.js"}></script>
            <script nonce={nonce} defer data-domain="shipnix.io" src="https://plausible.io/js/script.js"></script>
            |]
  where
    CSPNonce nonce = fromFrozenContext

CSP's are a pain to write, and IHP has the opportunity to add a type-safe API for it with good defaults.

Yesod could be used as an inspiration, but I also think we could make it even nicer for IHP
https://hackage.haskell.org/package/yesod-csp-0.2.5.0/docs/Yesod-Csp.html

Instead of having a list with directives, I think it would be nice to have it in a set structure that ensures that every rule has some mandatory directives to ensure some minimum default with the possibility to customize it according to specific needs.

data CSP = CSP
                       { defaultSrc = CSPSourceList
                       , scriptScr = CSPSourceList
                       , frameAncestors = CSPSourceList
                       , reportURI = CSPEscapedURI
                       ..... etc
                       }

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions