diff --git a/files/nginx/odk.conf.template b/files/nginx/odk.conf.template
index 06193f499..b8b1e8728 100644
--- a/files/nginx/odk.conf.template
+++ b/files/nginx/odk.conf.template
@@ -130,8 +130,7 @@ server {
}
# To read single submission cookies
location = /-/single/check-submitted {
- alias /usr/share/nginx/html/blank.html;
- default_type text/html;
+ try_files $uri @blank.html;
}
# For that iframe to work, we'll need another path prefix (enketo-passthrough) under which we can
@@ -166,6 +165,17 @@ server {
proxy_read_timeout 2m;
}
+ location @blank.html {
+ root /usr/share/nginx/html;
+ try_files /blank.html =404;
+
+ add_header Content-Security-Policy-Report-Only "default-src 'none'";
+ include /usr/share/odk/nginx/common-headers.conf;
+ }
+ location = /blank.html {
+ try_files $uri @blank.html;
+ }
+
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
diff --git a/test/nginx/test-nginx.js b/test/nginx/test-nginx.js
index 0346491d0..8c336d1df 100644
--- a/test/nginx/test-nginx.js
+++ b/test/nginx/test-nginx.js
@@ -6,7 +6,7 @@ const none = `'none'`;
const self = `'self'`;
const unsafeInline = `'unsafe-inline'`;
const contentSecurityPolicies = {
- 'restrictive': {
+ 'backend-default': {
'default-src': none,
'connect-src': [
'https://translate.google.com',
@@ -37,6 +37,9 @@ const contentSecurityPolicies = {
'worker-src': 'data:',
'report-uri': '/csp-report',
},
+ 'disallow-all': {
+ 'default-src': none,
+ },
enketo: {
'default-src': none,
'connect-src': [
@@ -183,7 +186,6 @@ describe('nginx config', () => {
[
[ '/index.html', /
<\/div>/ ],
[ '/version.txt', /^versions:/ ],
- [ '/blank.html', /^\n$/ ],
[ '/favicon.ico', /^\n$/ ],
].forEach(([ path, expectedContent ]) => {
it(`${path} file should serve expected content`, async () => {
@@ -317,15 +319,23 @@ describe('nginx config', () => {
});
});
- it('should serve blank page on /-/single/check-submitted', async () => {
- // when
- const res = await fetchHttps('/-/single/check-submitted');
+ describe('blank.html', () => {
+ [
+ '/blank.html',
+ '/-/single/check-submitted',
+ ].forEach(path => {
+ it(`should serve blank page on ${path}`, async () => {
+ // when
+ const res = await fetchHttps(path);
- // then
- assert.equal(res.status, 200);
- assert.isEmpty((await res.text()).trim());
- assertSecurityHeaders(res, { csp:'restrictive' });
- await assertEnketoReceivedNoRequests();
+ // then
+ assert.equal(res.status, 200);
+ assert.isEmpty((await res.text()).trim());
+ assert.equal(res.headers.get('Content-Type'), 'text/html');
+ assertSecurityHeaders(res, { csp:'disallow-all' });
+ await assertEnketoReceivedNoRequests();
+ });
+ });
});
it('/v1/... should forward to backend', async () => {
@@ -335,7 +345,7 @@ describe('nginx config', () => {
// then
assert.equal(res.status, 200);
assert.equal(await res.text(), 'OK');
- assertSecurityHeaders(res, { csp:'restrictive' });
+ assertSecurityHeaders(res, { csp:'backend-default' });
// and
await assertBackendReceived(
{ method:'GET', path:'/v1/some/central-backend/path' },
@@ -347,7 +357,7 @@ describe('nginx config', () => {
const res = await fetchHttps('/v1/reflect-headers');
// then
assert.equal(res.status, 200);
- assertSecurityHeaders(res, { csp:'restrictive' });
+ assertSecurityHeaders(res, { csp:'backend-default' });
// when
const body = await res.json();
@@ -365,7 +375,7 @@ describe('nginx config', () => {
// then
assert.equal(res.status, 200);
// and
- assertSecurityHeaders(res, { csp:'restrictive' });
+ assertSecurityHeaders(res, { csp:'backend-default' });
// when
const body = await res.json();