fix(security): one settings decoder, and the last creation path

Round-four review follow-ups.

Every reader of app_settings now shares decodeSettingValue. The previous
commit taught the GET handler to decode, which on a legacy SQLite install
made the tab show devtools protection as disabled while
readBooleanSetting — parsing once, getting the string 'false', rejecting
it — left new galleries with it enabled. A decoder used by only some
readers is worse than none, because the UI and the behaviour disagree.
readBooleanSetting, getImageSecurityDefaults, the v1 devtools fallback
and the settings GET all use it now.

Standalone contract conversion covered. contract/conversions.js takes
Path B and inserts its own event row when the contract has no source
quote, so signed standalone contracts were the last path still landing
on the migration-038 column defaults.

Refs #1296
This commit is contained in:
Paul Nothaft
2026-09-05 07:48:41 +02:00
parent 0e560ebb19
commit 0deef2584f
5 changed files with 71 additions and 26 deletions
+2 -8
View File
@@ -4,6 +4,7 @@ const { adminAuth } = require('../middleware/auth');
const { requirePermission } = require('../middleware/permissions');
const secureImageMiddleware = require('../middleware/secureImageMiddleware');
const logger = require('../utils/logger');
const { decodeSettingValue } = require('./adminEvents/helpers');
const router = express.Router();
@@ -40,14 +41,7 @@ router.get('/settings', adminAuth, requirePermission(['settings.view', 'image_se
// edited, until consumers could no longer read them (#1296). Decode
// here so a round trip is idempotent. This terminates: each parse of
// a string is strictly shorter than its input.
let value = setting.setting_value;
while (typeof value === 'string') {
let parsed;
try { parsed = JSON.parse(value); } catch { break; }
if (parsed === value) break;
value = parsed;
}
config[setting.setting_key] = value;
config[setting.setting_key] = decodeSettingValue(setting.setting_value);
});
res.json(config);