Skip to content

Commit 8420626

Browse files
antoinerousseauGatsbyJS Bot
authored and
GatsbyJS Bot
committed
fix(gatsby-plugin-netlify): security headers merging (#17538)
1 parent 459f197 commit 8420626

File tree

3 files changed

+127
-2
lines changed

3 files changed

+127
-2
lines changed

packages/gatsby-plugin-netlify/src/__tests__/__snapshots__/build-headers-program.js.snap

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,83 @@ exports[`with caching headers 1`] = `
7676
"
7777
`;
7878

79+
exports[`with security headers 1`] = `
80+
"## Created with gatsby-plugin-netlify
81+
82+
/*
83+
X-Frame-Options: ALLOW-FROM https://app.storyblok.com/
84+
X-XSS-Protection: 1; mode=block
85+
X-Content-Type-Options: nosniff
86+
Referrer-Policy: same-origin
87+
Content-Security-Policy: frame-ancestors 'self' https://*.storyblok.com/
88+
/component---node-modules-gatsby-plugin-offline-app-shell-js-78f9e4dea04737fa062d.js
89+
Cache-Control: public, max-age=31536000, immutable
90+
/0-0180cd94ef2497ac7db8.js
91+
Cache-Control: public, max-age=31536000, immutable
92+
/component---src-templates-blog-post-js-517987eae96e75cddbe7.js
93+
Cache-Control: public, max-age=31536000, immutable
94+
/component---src-pages-404-js-53e6c51a5a7e73090f50.js
95+
Cache-Control: public, max-age=31536000, immutable
96+
/component---src-pages-index-js-0bdd01c77ee09ef0224c.js
97+
Cache-Control: public, max-age=31536000, immutable
98+
/pages-manifest-ab11f09e0ca7ecd3b43e.js
99+
Cache-Control: public, max-age=31536000, immutable
100+
/webpack-runtime-acaa8994f1f704475e21.js
101+
Cache-Control: public, max-age=31536000, immutable
102+
/styles.1025963f4f2ec7abbad4.css
103+
Cache-Control: public, max-age=31536000, immutable
104+
/styles-565f081c8374bbda155f.js
105+
Cache-Control: public, max-age=31536000, immutable
106+
/app-f33c13590352da20930f.js
107+
Cache-Control: public, max-age=31536000, immutable
108+
/static/*
109+
Cache-Control: public, max-age=31536000, immutable
110+
/sw.js
111+
Cache-Control: no-cache
112+
/offline-plugin-app-shell-fallback/
113+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
114+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
115+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
116+
Link: </component---node-modules-gatsby-plugin-offline-app-shell-js-78f9e4dea04737fa062d.js>; rel=preload; as=script
117+
/hi-folks/
118+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
119+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
120+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
121+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
122+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
123+
/my-second-post/
124+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
125+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
126+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
127+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
128+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
129+
/hello-world/
130+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
131+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
132+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
133+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
134+
Link: </component---src-templates-blog-post-js-517987eae96e75cddbe7.js>; rel=preload; as=script
135+
/404/
136+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
137+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
138+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
139+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
140+
Link: </component---src-pages-404-js-53e6c51a5a7e73090f50.js>; rel=preload; as=script
141+
/
142+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
143+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
144+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
145+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
146+
Link: </component---src-pages-index-js-0bdd01c77ee09ef0224c.js>; rel=preload; as=script
147+
/404.html
148+
Link: </webpack-runtime-acaa8994f1f704475e21.js>; rel=preload; as=script
149+
Link: </styles-565f081c8374bbda155f.js>; rel=preload; as=script
150+
Link: </app-f33c13590352da20930f.js>; rel=preload; as=script
151+
Link: </0-0180cd94ef2497ac7db8.js>; rel=preload; as=script
152+
Link: </component---src-pages-404-js-53e6c51a5a7e73090f50.js>; rel=preload; as=script
153+
"
154+
`;
155+
79156
exports[`without caching headers 1`] = `
80157
"## Created with gatsby-plugin-netlify
81158

packages/gatsby-plugin-netlify/src/__tests__/build-headers-program.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,24 @@ test(`without caching headers`, async () => {
177177
await fs.readFile(pluginData.publicFolder(`_headers`), `utf8`)
178178
).toMatchSnapshot()
179179
})
180+
181+
test(`with security headers`, async () => {
182+
const pluginData = await createPluginData()
183+
184+
const pluginOptions = {
185+
...DEFAULT_OPTIONS,
186+
mergeSecurityHeaders: true,
187+
headers: {
188+
"/*": [
189+
`Content-Security-Policy: frame-ancestors 'self' https://*.storyblok.com/`,
190+
`X-Frame-Options: ALLOW-FROM https://app.storyblok.com/`,
191+
],
192+
},
193+
}
194+
195+
await buildHeadersProgram(pluginData, pluginOptions)
196+
197+
expect(
198+
await fs.readFile(pluginData.publicFolder(`_headers`), `utf8`)
199+
).toMatchSnapshot()
200+
})

packages/gatsby-plugin-netlify/src/build-headers-program.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import {
1212
NETLIFY_HEADERS_FILENAME,
1313
} from "./constants"
1414

15+
function getHeaderName(header) {
16+
const matches = header.match(/^([^:]+):/)
17+
return matches && matches[1]
18+
}
19+
1520
function validHeaders(headers) {
1621
if (!headers || !_.isObject(headers)) {
1722
return false
@@ -21,7 +26,10 @@ function validHeaders(headers) {
2126
headers,
2227
(headersList, path) =>
2328
_.isArray(headersList) &&
24-
_.every(headersList, header => _.isString(header))
29+
_.every(
30+
headersList,
31+
header => _.isString(header) && getHeaderName(header)
32+
)
2533
)
2634
}
2735

@@ -97,6 +105,25 @@ function defaultMerge(...headers) {
97105
return _.mergeWith({}, ...headers, unionMerge)
98106
}
99107

108+
function headersMerge(userHeaders, defaultHeaders) {
109+
const merged = {}
110+
Object.keys(defaultHeaders).forEach(path => {
111+
if (!userHeaders[path]) {
112+
merged[path] = defaultHeaders[path]
113+
return
114+
}
115+
const headersMap = {}
116+
defaultHeaders[path].forEach(header => {
117+
headersMap[getHeaderName(header)] = header
118+
})
119+
userHeaders[path].forEach(header => {
120+
headersMap[getHeaderName(header)] = header // override if exists
121+
})
122+
merged[path] = Object.values(headersMap)
123+
})
124+
return merged
125+
}
126+
100127
function transformLink(manifest, publicFolder, pathPrefix) {
101128
return header =>
102129
header.replace(LINK_REGEX, (__, prefix, file, suffix) => {
@@ -216,7 +243,7 @@ const applySecurityHeaders = ({ mergeSecurityHeaders }) => headers => {
216243
return headers
217244
}
218245

219-
return defaultMerge(headers, SECURITY_HEADERS)
246+
return headersMerge(headers, SECURITY_HEADERS)
220247
}
221248

222249
const applyCachingHeaders = (

0 commit comments

Comments
 (0)