diff --git a/api/src/host-check.js b/api/src/host-check.js
index 89d2f480..ec84a57f 100644
--- a/api/src/host-check.js
+++ b/api/src/host-check.js
@@ -1,39 +1,21 @@
-const { loadConfig, loadConfigForUpdate, saveConfig } = require('./config');
+const { loadConfig } = require('./config');
const { authActive } = require('./auth');
const { IS_DEMO } = require('./demo');
-const log = require('./log');
const { hostnameOf, isLocalAddress } = require('../../ui/js/host-names.js');
/* While no password is set, answer only addresses a page on another site
- cannot point at this server (DNS rebinding). The first page load sets the
- list when none is stored. An IP address sets an empty one. */
+ cannot point at this server (DNS rebinding). Never add a name to the list
+ from a request: a rebound page can send that request first. */
/** @returns {string | null} the refused host name, or null when allowed */
-function refusedHost(req, pathname) {
+function refusedHost(req) {
if (IS_DEMO) return null;
const cfg = loadConfig();
if (authActive(cfg)) return null;
const host = hostnameOf(req.headers.host);
if (!host) return String(req.headers.host || '');
const list = cfg.settings?.server?.allowedHosts;
- const local = isLocalAddress(host);
- if (!Array.isArray(list) && req.method === 'GET' && pathname === '/api/auth/check') {
- trustFirst(local ? [] : [host], host);
- return null;
- }
- return local || (Array.isArray(list) && list.includes(host)) ? null : host;
-}
-
-function trustFirst(list, host) {
- try {
- const cfg = loadConfigForUpdate();
- if (Array.isArray(cfg.settings?.server?.allowedHosts)) return;
- cfg.settings.server = { ...cfg.settings.server, allowedHosts: list };
- saveConfig(cfg);
- log.audit('allowed addresses set from the first visit', { host, allowed: list.join(',') || 'local only' });
- } catch (e) {
- log.warn('could not store the allowed addresses', { error: e.message });
- }
+ return isLocalAddress(host) || (Array.isArray(list) && list.includes(host)) ? null : host;
}
module.exports = { refusedHost };
diff --git a/api/src/router.js b/api/src/router.js
index c71f34ff..e0d15b6d 100644
--- a/api/src/router.js
+++ b/api/src/router.js
@@ -52,7 +52,7 @@ function route(req, res) {
}
if (u.pathname !== '/health') {
- const host = refusedHost(req, u.pathname);
+ const host = refusedHost(req);
if (host !== null) {
return json(res, 403, {
error: 'Forbidden: this address is not allowed while no password is set',
diff --git a/api/test/host-check.test.js b/api/test/host-check.test.js
index 52ae5e51..56bc397a 100644
--- a/api/test/host-check.test.js
+++ b/api/test/host-check.test.js
@@ -86,20 +86,28 @@ test('a rebound page cannot read or replace the config', async () => {
assert.equal(loadConfig().items.length, 1);
});
-test('the first page load by name trusts that name and no other', async () => {
- const first = await request('GET', '/api/auth/check', 'Dash.Example.com:8700');
- assert.equal(first.status, 200);
- assert.deepEqual(stored(), ['dash.example.com']);
+test('a rebound page that loads first is refused and stores nothing', async () => {
+ const first = await request('GET', '/api/auth/check', 'evil.example:8700');
+ assert.equal(first.status, 403);
+ assert.equal(first.body.code, 'blocked.host');
+ assert.equal(fs.existsSync(CFG), false);
expireCache();
- assert.equal((await request('GET', '/api/config', 'dash.example.com')).status, 200);
assert.equal((await request('GET', '/api/auth/check', 'evil.example')).status, 403);
});
-test('the first page load by IP address trusts no name', async () => {
+test('a page load by IP address trusts no name, and a name added there is answered', async () => {
assert.equal((await request('GET', '/api/auth/check', '192.168.1.10:8700')).status, 200);
- assert.deepEqual(stored(), []);
+ assert.equal(fs.existsSync(CFG), false);
expireCache();
assert.equal((await request('GET', '/api/auth/check', 'dash.example.com')).status, 403);
+ const add = await request('POST', '/api/config', '192.168.1.10', {
+ items: [],
+ settings: { server: { allowedHosts: ['dash.example.com'] } },
+ });
+ assert.equal(add.status, 200);
+ expireCache();
+ assert.equal((await request('GET', '/api/auth/check', 'dash.example.com')).status, 200);
+ assert.equal((await request('GET', '/api/auth/check', 'evil.example')).status, 403);
});
test('local addresses are always answered', async () => {
diff --git a/ui/index.html b/ui/index.html
index e756e45d..fdc7beea 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -56,5 +56,5 @@
-
+