openvibers/openvibe.media Commits
Object explorer (/me): never framed by another site (X-Frame-Options DENY, frame-ancestors 'none'); no x-powered-by on any answer (#142)
Changes
@@@ -70,6 +70,7 @@ const ready = (async () => {}const app = express();++ app.disable('x-powered-by'); // the framework is nobody's business (it was in every answer)// Only the local reverse proxy (nginx on this host) is trusted to say who the client is: req.ip is// its X-Forwarded-For for loopback peers and the socket address for anyone else, so a direct caller// cannot pick its own IP (views, rate limits). `true` trusted every peer's header.@@@ -47,6 +47,9 @@ function privateHeaders(_req, res, next) {res.set('Cache-Control', cache.htmlHeaders({ private: true }));res.set('X-Robots-Tag', 'noindex, nofollow');res.set('X-Content-Type-Options', 'nosniff');++ // A person's own pages and the staff form are never framed by another site (clickjacking).++ res.set('X-Frame-Options', 'DENY');++ res.set('Content-Security-Policy', "frame-ancestors 'none'");next();}@@@ -162,6 +162,8 @@ const http = require('http');assert.ok(r.text.includes('href="/auth/login?next=%2Fme"'), 'the sign-in prompt comes back to /me');assert.ok(r.text.includes('<meta name="robots" content="noindex, nofollow">'));assert.ok(r.headers['x-robots-tag'].includes('noindex') && r.headers['cache-control'] === 'private, no-store');++ assert.strictEqual(r.headers['x-frame-options'], 'DENY', 'never framed by another site');++ assert.strictEqual(r.headers['content-security-policy'], "frame-ancestors 'none'");assert.ok(!r.text.includes(anaReady[0]) && !r.text.includes('<table'), 'nothing of anyone is shown');assert.ok(r.text.includes('navbar.js') && r.text.includes('"sessionUrl":"/auth/me"'), 'in the OpenVibe Frame');console.log('✅ signed out: the API answers 401, /me is a sign-in prompt in the Frame');
@@ -70,6 +70,7 @@ const ready = (async () => {
@@ -70,6 +70,7 @@ const ready = (async () => {
}
}
const app = express();
const app = express();
+ app.disable('x-powered-by'); // the framework is nobody's business (it was in every answer)
// Only the local reverse proxy (nginx on this host) is trusted to say who the client is: req.ip is
// Only the local reverse proxy (nginx on this host) is trusted to say who the client is: req.ip is
// its X-Forwarded-For for loopback peers and the socket address for anyone else, so a direct caller
// its X-Forwarded-For for loopback peers and the socket address for anyone else, so a direct caller
// cannot pick its own IP (views, rate limits). `true` trusted every peer's header.
// cannot pick its own IP (views, rate limits). `true` trusted every peer's header.
@@ -47,6 +47,9 @@ function privateHeaders(_req, res, next) {
@@ -47,6 +47,9 @@ function privateHeaders(_req, res, next) {
res.set('Cache-Control', cache.htmlHeaders({ private: true }));
res.set('Cache-Control', cache.htmlHeaders({ private: true }));
res.set('X-Robots-Tag', 'noindex, nofollow');
res.set('X-Robots-Tag', 'noindex, nofollow');
res.set('X-Content-Type-Options', 'nosniff');
res.set('X-Content-Type-Options', 'nosniff');
+ // A person's own pages and the staff form are never framed by another site (clickjacking).
+ res.set('X-Frame-Options', 'DENY');
+ res.set('Content-Security-Policy', "frame-ancestors 'none'");
next();
next();
}
}
@@ -162,6 +162,8 @@ const http = require('http');
@@ -162,6 +162,8 @@ const http = require('http');
assert.ok(r.text.includes('href="/auth/login?next=%2Fme"'), 'the sign-in prompt comes back to /me');
assert.ok(r.text.includes('href="/auth/login?next=%2Fme"'), 'the sign-in prompt comes back to /me');
assert.ok(r.text.includes('<meta name="robots" content="noindex, nofollow">'));
assert.ok(r.text.includes('<meta name="robots" content="noindex, nofollow">'));
assert.ok(r.headers['x-robots-tag'].includes('noindex') && r.headers['cache-control'] === 'private, no-store');
assert.ok(r.headers['x-robots-tag'].includes('noindex') && r.headers['cache-control'] === 'private, no-store');
+ assert.strictEqual(r.headers['x-frame-options'], 'DENY', 'never framed by another site');
+ assert.strictEqual(r.headers['content-security-policy'], "frame-ancestors 'none'");
assert.ok(!r.text.includes(anaReady[0]) && !r.text.includes('<table'), 'nothing of anyone is shown');
assert.ok(!r.text.includes(anaReady[0]) && !r.text.includes('<table'), 'nothing of anyone is shown');
assert.ok(r.text.includes('navbar.js') && r.text.includes('"sessionUrl":"/auth/me"'), 'in the OpenVibe Frame');
assert.ok(r.text.includes('navbar.js') && r.text.includes('"sessionUrl":"/auth/me"'), 'in the OpenVibe Frame');
console.log('✅ signed out: the API answers 401, /me is a sign-in prompt in the Frame');
console.log('✅ signed out: the API answers 401, /me is a sign-in prompt in the Frame');