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

CSP nonce support #1554

Open
JonWatkins opened this issue Mar 15, 2024 · 6 comments · May be fixed by #1645
Open

CSP nonce support #1554

JonWatkins opened this issue Mar 15, 2024 · 6 comments · May be fixed by #1645

Comments

@JonWatkins
Copy link

Description

It would be good if the renderPage was able to apply CSP nonce to the relevent tags. We are using helmet with express in our application, and i have managed to get it to work by doing something linke this

const pageContext = await renderPage(pageContextInit);
const { httpResponse } = pageContext;

if (!httpResponse) {
    return next();
  } else {
    const { body, statusCode, headers, earlyHints } = httpResponse;

    if (res.writeEarlyHints) {
      const links: string[] = earlyHints.map((e) => e.earlyHintLink);
      res.writeEarlyHints({ link: links });
    }

    headers.forEach(([name, value]) => res.setHeader(name, value));

    res.status(statusCode);
    res.send(applyCSPToHTML(body, res.locals.cspNonce));
  }
import { parse, serialize } from "parse5";

export const applyCSPToHTML = (html: string, nonce: string): string => {
  const document = parse(html);

  // Function to apply nonce attribute to script tags
  const applyNonceToScripts = (node: any) => {
    if (node.tagName === "script") {
      const nonceAttribute = node.attrs.find(
        (attr: any) => attr.name === "nonce",
      );
      if (!nonceAttribute) {
        node.attrs.push({ name: "nonce", value: nonce });
      }
    }
    if (node.childNodes) {
      node.childNodes.forEach(applyNonceToScripts);
    }
  };

  // Apply nonce attribute to script tags
  applyNonceToScripts(document);

  // Serialize the modified document back to HTML
  const modifiedHTML = serialize(document);
  return modifiedHTML;
};

Im aware, that I would be able to include a meta tag in the HTML easy enough, but applying a nonce to tags generated by Vike is a little more awkward currently.

@brillout
Copy link
Member

I agree that'd be nice.

I think the following would be a simple solution.

  • Setting pageContext.nonce to tell Vike to add the nonce tag attribute.
    renderPage({ nonce: 'random-string' })
  • Mentioning it in a new short documentation page vike.dev/CSP.

Contribution welcome.

@brillout
Copy link
Member

Idea: also alow the user to set pageContext.nonce to true and, in that case, Vike will generate a nonce.

@JonWatkins What do you use to generate the nonce? Is Math.random() secure enough?

@JonWatkins
Copy link
Author

The way that I was generating the nonce was with the crypto module, not sure that Math.random would be enough. But this also ensures that the value changes for every request.

app.use((req: Request, res: Response, next: NextFunction) => {
    res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
    next();
  });

@brillout
Copy link
Member

brillout commented Mar 18, 2024

Indeed.

👍 And I guess crypto.randomBytes(16).toString("hex") isn't too slow?

If it's relatively slow, I'd be inclined for going for something faster albeit less secure.

@JonWatkins
Copy link
Author

JonWatkins commented Mar 21, 2024

I don't have any issues with it in our app. it's such a negligable amount of time, even when the server is under heavy load.
The parsing and serialization of the rendered page is more work than generating the nonce.

I think the implementation of the generated nonce might be best left to who ever is implementing it, as other people may have different requirements for it, but having support and docs would be super helpful.

We are using helmet for the handling the CSP headers like this so that it is able to use the res.locals.cspNonce

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      objectSrc: ["'none'"],
      scriptSrc: [
        "'self'",
        (req, res) => `'nonce-${res.locals.cspNonce}'`,
      ],
    },
  },
});

@branberry
Copy link

branberry commented May 15, 2024

Hey all! I am looking to help contribute to Vike, and I started working on this issue. I created a draft PR with some small changes. I wanted to post and see if I am on the right path before I continue forward. @brillout, I see that you proposed to add the nonce property as a boolean to the pageContext for renderPage. Hopefully I had the right idea with this one 😅

Thanks in advance!

@branberry branberry linked a pull request May 16, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants