Skip to content

Commit

Permalink
Add form-action, frame-ancestors CSP headers to response when FRAME_A…
Browse files Browse the repository at this point in the history
…NCESTORS_FORM_ACTIONS_CSP_HEADERS flag is true.
  • Loading branch information
LazarAlexandru-Constantin committed Nov 7, 2023
1 parent 03422e2 commit f98b18a
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 41 deletions.
2 changes: 2 additions & 0 deletions ci/terraform/sandpit.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ orch_to_auth_audience = "https://signin.sandpit.account.gov.uk/"
logging_endpoint_arns = [
"arn:aws:logs:eu-west-2:885513274347:destination:csls_cw_logs_destination_prodpython"
]

frame_ancestors_form_actions_csp_headers = "1"
6 changes: 6 additions & 0 deletions ci/terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,9 @@ variable "orch_to_auth_audience" {
type = string
default = ""
}

variable "frame_ancestors_form_actions_csp_headers" {
description = "When true, sets frame-ancestors and form-action CSP headers in reponses"
type = string
default = "0"
}
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ async function createApp(): Promise<express.Application> {
);

app.use(i18nextMiddleware.handle(i18next));
app.use(helmet(helmetConfiguration));
app.use(helmet(helmetConfiguration()));

const redisConfig = isProduction
? await getRedisConfig(getAppEnv())
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,7 @@ export function getPasswordResetCodeEnteredWrongBlockDurationInMinutes(): number
Number(process.env.PASSWORD_RESET_CODE_ENTERED_WRONG_BLOCKED_MINUTES) || 15
);
}

export function supportFrameAncestorsFormActionsCspHeaders(): boolean {
return process.env.FRAME_ANCESTORS_FORM_ACTIONS_CSP_HEADERS === "1";
}
114 changes: 74 additions & 40 deletions src/config/helmet.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,77 @@
import helmet from "helmet";
import { Request, Response } from "express";
import e, { Request, Response } from "express";
import { supportFrameAncestorsFormActionsCspHeaders } from "../config";
// Helmet does not export the config type - This is the way the recommend getting it on GitHub.
export const helmetConfiguration: Parameters<typeof helmet>[0] = {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'"],
scriptSrc: [
"'self'",
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
(req: Request, res: Response): string =>
`'nonce-${res.locals.scriptNonce}'`,
"'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='",
"https://www.googletagmanager.com",
"https://www.google-analytics.com",
"https://ssl.google-analytics.com",
],
imgSrc: [
"'self'",
"data:",
"https://www.googletagmanager.com",
"https://www.google-analytics.com",
],
objectSrc: ["'none'"],
connectSrc: ["'self'", "https://www.google-analytics.com"],
export function helmetConfiguration(): Parameters<typeof helmet>[0] {
let helmetConfig: {
permittedCrossDomainPolicies: boolean;
referrerPolicy: boolean;
expectCt: boolean;
frameguard: { action: string };
hsts: { maxAge: number; includeSubDomains: boolean; preload: boolean };
dnsPrefetchControl: { allow: boolean };
contentSecurityPolicy: {
directives: {
defaultSrc: string[];
objectSrc: string[];
styleSrc: string[];
scriptSrc: (string | ((req: e.Request, res: e.Response) => string))[];
imgSrc: string[];
connectSrc: string[];
"form-action"?: string[];
"frame-ancestors"?: string[];
};
};
};
helmetConfig = {

Check failure on line 26 in src/config/helmet.ts

View workflow job for this annotation

GitHub Actions / run-tests

'helmetConfig' is never reassigned. Use 'const' instead
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'"],
scriptSrc: [
"'self'",
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
(req: Request, res: Response): string =>
`'nonce-${res.locals.scriptNonce}'`,
"'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='",
"https://www.googletagmanager.com",
"https://www.google-analytics.com",
"https://ssl.google-analytics.com",
],
imgSrc: [
"'self'",
"data:",
"https://www.googletagmanager.com",
"https://www.google-analytics.com",
],
objectSrc: ["'none'"],
connectSrc: ["'self'", "https://www.google-analytics.com"],
},
},
},
dnsPrefetchControl: {
allow: false,
},
frameguard: {
action: "deny",
},
hsts: {
maxAge: 31536000, // 1 Year
preload: true,
includeSubDomains: true,
},
referrerPolicy: false,
permittedCrossDomainPolicies: false,
expectCt: false,
};
dnsPrefetchControl: {
allow: false,
},
frameguard: {
action: "deny",
},
hsts: {
maxAge: 31536000, // 1 Year
preload: true,
includeSubDomains: true,
},
referrerPolicy: false,
permittedCrossDomainPolicies: false,
expectCt: false,
};
if (supportFrameAncestorsFormActionsCspHeaders()) {
helmetConfig.contentSecurityPolicy.directives["frame-ancestors"] = [
"'self'",
"https://*.account.gov.uk",
];
helmetConfig.contentSecurityPolicy.directives["form-action"] = [
"'self'",
"https://*.account.gov.uk",
];
}
return helmetConfig;
}

0 comments on commit f98b18a

Please sign in to comment.