Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express CSP Nonce with TypeScript

I have an express project in TypeScript and I tried to add a CSP Nonce with Helmet.

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

But when I start the program, it throws the following error:

./index.ts:820
return new TSError(diagnosticText, diagnosticCodes);
       ^
TSError: ⨯ Unable to compile TypeScript:
src/app.ts:17:59 - error TS2339: Property 'locals' does not exist on type 'ServerResponse'.

17         scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
                                                         ~~~~~~

at createTSError (/home/frdiolin/WebstormProjects/Calender2.0/node_modules/ts-node/src/index.ts:820:12)
at reportTSError (/home/frdiolin/WebstormProjects/Calender2.0/node_modules/ts-node/src/index.ts:824:19)
at getOutput (/home/frdiolin/WebstormProjects/Calender2.0/node_modules/ts-node/src/index.ts:1014:36)
at Object.compile (/home/frdiolin/WebstormProjects/Calender2.0/node_modules/ts-node/src/index.ts:1322:43)
at Module.m._compile (/home/frdiolin/WebstormProjects/Calender2.0/node_modules/ts-node/src/index.ts:1454:30)
at Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Object.require.extensions.<computed> [as .ts] (/home/frdiolin/WebstormProjects/Calender2.0/node_modules/ts-node/src/index.ts:1458:12)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) {
diagnosticCodes: [ 2339 ]
}

When I tried to start the same code in JS, it works fine. What's going wrong?

like image 780
Fridolin1 Avatar asked Jan 20 '26 17:01

Fridolin1


1 Answers

In short: cast res to an Express Response. See the code snippet below.

This is a TypeScript-only error. res.locals is an Express feature, but Helmet is designed to work without Express, so res.locals are not part of Helmet's types. In other words, res doesn't know about a .locals property because Helmet doesn't assume it's there.

You can fix this by telling TypeScript that this is an Express response object. Use res as Response.

Here's an example:

import express, { Response } from "express";

// ...

app.use(
  helmet.contentSecurityPolicy({
    useDefaults: true,
    directives: {
      scriptSrc: [
        "'self'",
        // The `res as Response` is what you need.
        (_req, res) => `'nonce-${(res as Response).locals.cspNonce}'`,
      ],
    },
  })
);
like image 152
Evan Hahn Avatar answered Jan 22 '26 10:01

Evan Hahn