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

Update/Add security headers #280

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f8068f2
Update helmet version for admin/server
max-debug022 Jun 26, 2024
dc8ba6c
Add Security-Header to ensure JEA for admin
max-debug022 Jun 26, 2024
6136af4
Update helmet for api
max-debug022 Jun 26, 2024
ec6d577
Strengthen security headers, remove deprecated and prohibited headers…
max-debug022 Jun 26, 2024
60506d2
Strengthen security headers, remove deprecated and prohibited headers…
max-debug022 Jun 26, 2024
3f55034
Correct CSP-option value
max-debug022 Jul 8, 2024
504196b
Remove explicit default value specification
max-debug022 Jul 8, 2024
3895329
Set frame-ancestors to self since it has no fallback directive and wo…
max-debug022 Jul 8, 2024
d8b0dcd
Add link to explaination
max-debug022 Jul 8, 2024
5d7c484
Add comments
max-debug022 Jul 8, 2024
994d4c2
Add comments for api
max-debug022 Jul 8, 2024
55b7a7c
Disable x-powered-by header
max-debug022 Jul 19, 2024
8e3b2a0
Add upgrade-insecure-requests to Site
max-debug022 Aug 6, 2024
677eed6
Add check for DEV_DOMAIN
max-debug022 Aug 6, 2024
23e6f72
Allow unsafe-inline for script-src-elem to prevent preview error
max-debug022 Aug 6, 2024
a2d158b
Remove unused import
max-debug022 Aug 6, 2024
20e8a1a
Only use upgrade-insecure-requests if not in local development
max-debug022 Aug 20, 2024
808d521
Update package-lock after rebase
max-debug022 Sep 10, 2024
057701e
Improve dokumentation and further restrict csp
max-debug022 Oct 8, 2024
0ca51f1
Merge branch 'main' into security-header-jea
johnnyomair Oct 15, 2024
d2578b0
Remove script-src unsafe-eval for production as it is only needed for…
max-debug022 Oct 16, 2024
134517b
Merge branch 'main' into security-header-jea
max-debug022 Oct 16, 2024
c78572c
Add unknown words to project-words.txt
max-debug022 Oct 16, 2024
ba88882
Move words to site
max-debug022 Oct 16, 2024
4040de6
Move words to api
max-debug022 Oct 22, 2024
e7050c5
Explicitly allow only the admin to embed site as iframe
max-debug022 Oct 28, 2024
42a5ebf
Add generate CSP function to improve readability
max-debug022 Oct 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion admin/server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion admin/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dependencies": {
"compression": "^1.7.4",
"express": "^4.17.1",
"helmet": "^7.0.0"
"helmet": "^7.1.0"
},
"engines": {
"node": "20"
Expand Down
34 changes: 26 additions & 8 deletions admin/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,42 @@ indexFile = indexFile.replace(/\$([A-Z_]+)/g, (match, p1) => {
});

app.use(compression());

app.disable("x-powered-by"); // Disable the X-Powered-By header as it is not needed and can be used to infer the server technology

app.use(
helmet({
contentSecurityPolicy: {
directives: {
"script-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "https:", "data:"],
"default-src": ["'self'", "https:"],
"media-src": ["'self'", "https:"],
"style-src": ["'self'", "https:", "'unsafe-inline'"],
"font-src": ["'self'", "https:", "data:"],
"default-src": ["'none'"],
"script-src-elem": ["'unsafe-inline'", "'self'"],
"style-src-elem": ["'unsafe-inline'", "'self'"],
"style-src-attr": ["'unsafe-inline'"],
"font-src": ["'self'", "data:"],
"connect-src": ["https:"],
"img-src": ["'self'", "data:", "https:"],
"frame-src": ["https:"],
"frame-ancestors": ["'self'"],
upgradeInsecureRequests: process.env.NODE_ENV === "development" ? undefined : [],
},
useDefaults: false, // Avoid default values for not explicitly set directives
},
xXssProtection: false,
xFrameOptions: false, // Disable deprecated header
crossOriginResourcePolicy: "same-origin", // Do not allow cross-origin requests to access the response
crossOriginEmbedderPolicy: false, // value=no-corp
crossOriginOpenerPolicy: true, // value=same-origin
strictTransportSecurity: {
maxAge: 63072000,
// Enable HSTS
maxAge: 63072000, // 2 years (recommended when subdomains are included)
includeSubDomains: true,
preload: true,
},
referrerPolicy: {
policy: "no-referrer", // No referrer information needs to be sent
},
xContentTypeOptions: true, // value=nosniff
xDnsPrefetchControl: false, // Disable non-standard header as recommended by MDN
xPermittedCrossDomainPolicies: true, // value=none (prevent MIME sniffing)
}),
);

Expand Down
11 changes: 5 additions & 6 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"cookie-parser": "^1.4.5",
"express": "^4.18.2",
"graphql": "^15.5.0",
"helmet": "^4.6.0",
"helmet": "^7.1.0",
"nestjs-console": "^8.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^6.0.0",
Expand Down
5 changes: 4 additions & 1 deletion api/project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ nowait
ormconfig
pkey
schemaname
tablename
tablename
HSTS
nosniff
noopen
2 changes: 1 addition & 1 deletion api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class AppModule {
origin: config.corsAllowedOrigin,
methods: ["GET", "POST"],
credentials: false,
exposedHeaders: [],
maxAge: 600,
thomasdax98 marked this conversation as resolved.
Show resolved Hide resolved
},
useGlobalPrefix: true,
// See https://docs.nestjs.com/graphql/other-features#execute-enhancers-at-the-field-resolver-level
Expand Down
30 changes: 28 additions & 2 deletions api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ async function bootstrap(): Promise<void> {
useContainer(app.select(appModule), { fallbackOnErrors: true });

app.setGlobalPrefix("api");

app.enableCors({
origin: config.corsAllowedOrigin,
methods: ["GET", "POST"],
credentials: false,
exposedHeaders: [],
maxAge: 600,
});

app.useGlobalInterceptors(new ExceptionInterceptor(config.debug));
Expand All @@ -45,9 +46,34 @@ async function bootstrap(): Promise<void> {
}),
);

app.disable("x-powered-by");

app.use(
helmet({
contentSecurityPolicy: false, // configure this when API returns HTML
contentSecurityPolicy: {
directives: {
"default-src": ["'none'"],
},
useDefaults: false, // Disable default directives
},
xFrameOptions: false, // Disable non-standard header
strictTransportSecurity: {
// Enable HSTS
maxAge: 63072000, // 2 years (recommended when subdomains are included)
includeSubDomains: true,
preload: true,
},
referrerPolicy: {
policy: "no-referrer", // No referrer information is sent along with requests
},
xContentTypeOptions: true, // value="nosniff" (prevent MIME sniffing)
xDnsPrefetchControl: false, // Disable non-standard header
xDownloadOptions: true, // value="noopen" (prevent IE from executing downloads in the context of the site)
xPermittedCrossDomainPolicies: true, // value="none" (prevent the browser from MIME sniffing)
originAgentCluster: true, // value=?1
crossOriginResourcePolicy: {
policy: "same-site", // This allows the resource to be shared with the same site (all subdomains/ports are included)
},
}),
);
app.use(json({ limit: "1mb" })); // increase default limit of 100kb for saving large pages
Expand Down
79 changes: 51 additions & 28 deletions site/next.config.js
johnnyomair marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});

function generateCSP() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

headers already is a function, so why not move the code there before returning the array?

const cspRules = {
"default-src": "'self'", // Needed for SVGs in Firefox (other browsers load SVGs with img-src)
"style-src-elem": "'self' 'unsafe-inline'",
"style-src-attr": "'unsafe-inline'",
"script-src-elem": "'self' 'unsafe-inline'",
"font-src": "data:",
"frame-src": "https://www.youtube-nocookie.com/",
"img-src": `data: 'self' ${process.env.API_URL}`,
"frame-ancestors": process.env.ADMIN_URL,
};

// Conditionally add environment-specific rules
if (process.env.NODE_ENV === "development") {
cspRules["script-src"] = "'unsafe-eval'"; // Needed in local development
cspRules["connect-src"] = "ws:"; // Used for hot reloading in local development
} else {
cspRules["upgrade-insecure-requests"] = ""; // Don't use upgrade-insecure-requests with Domain-Setup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This instructs user agents to treat a site's insecure URLs (HTTP) as though they have been replaced with secure URLs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but why do we need it? We don't serve any routes prefixed with http in our applications or do we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including upgrade-insecure-requests is optional but helps avoid potential misconfigurations and adds an extra layer of security, so I’d recommend keeping it.

}

return Object.entries(cspRules)
.map(([key, value]) => `${key} ${value}`.trim())
.join("; ");
}

const cometConfig = require("./src/comet-config.json");

/**
Expand All @@ -24,50 +49,48 @@ const nextConfig = {
experimental: {
optimizePackageImports: ["@comet/cms-site"],
},
poweredByHeader: false,
// https://nextjs.org/docs/advanced-features/security-headers
headers: async () => [
{
source: "/:path*",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
key: "Content-Security-Policy",
value: generateCSP(),
},
{
key: "Cross-Origin-Opener-Policy",
value: "same-origin",
key: "Strict-Transport-Security", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
value: "max-age=63072000; includeSubDomains; preload", // 2 years (recommended when subdomains are included)
},
{
key: "Permissions-Policy",
value: "",
key: "Cross-Origin-Opener-Policy", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy
value: "same-origin", // Only allow the same origin to open the page in a browsing context
},
{
key: "X-Content-Type-Options",
value: "nosniff",
key: "Cross-Origin-Embedder-Policy",
// This value should be set to 'require-corp' as soon as iframe credentialless is supported by all browsers
// https://developer.mozilla.org/en-US/docs/Web/Security/IFrame_credentialless
// https://caniuse.com/mdn-html_elements_iframe_credentialless
value: "unsafe-none",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
key: "Cross-Origin-Resource-Policy", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy
value: "same-site", // Do not allow cross-origin requests to access the response
},
{
key: "Content-Security-Policy",
value: `
default-src 'self';
form-action 'self';
object-src 'none';
img-src 'self' https: data:${process.env.NODE_ENV === "development" ? " http:" : ""};
media-src 'self' https: data:${process.env.NODE_ENV === "development" ? " http:" : ""};
style-src 'self' 'unsafe-inline';
font-src 'self' https: data:;
script-src 'self' 'unsafe-inline' https:${process.env.NODE_ENV === "development" ? " 'unsafe-eval'" : ""};
connect-src 'self' https:${process.env.NODE_ENV === "development" ? " http:" : ""};
frame-ancestors ${process.env.ADMIN_URL};
fraxachun marked this conversation as resolved.
Show resolved Hide resolved
upgrade-insecure-requests;
block-all-mixed-content;
frame-src 'self' https://*.youtube.com https://*.youtube-nocookie.com;
`
.replace(/\s{2,}/g, " ")
.trim(),
//
key: "Permissions-Policy", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy
value: "",
},
{
key: "X-Content-Type-Options", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
value: "nosniff", // Prevent MIME sniffing
},
{
// This should be changed when using web analytics tools. For example, use "strict-origin-when-cross-origin" for Google Analytics
key: "Referrer-Policy", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
value: "same-origin", // Only use referer on own domain.
},
...(process.env.ADMIN_URL
? [
Expand Down
Loading