Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { SettingsStore } = require('../../src/persistence/settings-store');
|
||||
const { SecretsStore } = require('../../src/security/secrets-store');
|
||||
const { CommandRegistry } = require('../../src/core/command-registry');
|
||||
const { createAppServer } = require('../../src/server/app-server');
|
||||
|
||||
const repository = path.resolve(__dirname, '../..');
|
||||
|
||||
function fixture(t) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-public-security-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const settings = new SettingsStore(path.join(root, 'config', 'settings.json'));
|
||||
const secrets = new SecretsStore({ filePath: path.join(root, 'secrets', 'secrets.json'), keyFile: path.join(root, 'secrets', 'key') });
|
||||
const registry = new CommandRegistry({ platform: 'server' });
|
||||
registry.maintenance = { startOperations() {} };
|
||||
const logger = { info() {}, warn() {}, error() {}, audit() {} };
|
||||
return { root, settings, secrets, registry, logger };
|
||||
}
|
||||
|
||||
test('non-loopback HTTP binding fails closed without an HTTPS proxy boundary', async t => {
|
||||
const f = fixture(t);
|
||||
const previous = Object.fromEntries(['CHIMERA_PUBLIC_ORIGIN', 'CHIMERA_TRUST_PROXY', 'CHIMERA_COOKIE_SECURE'].map(key => [key, process.env[key]]));
|
||||
t.after(() => { for (const [key, value] of Object.entries(previous)) value === undefined ? delete process.env[key] : process.env[key] = value; });
|
||||
delete process.env.CHIMERA_PUBLIC_ORIGIN;
|
||||
process.env.CHIMERA_TRUST_PROXY = '0';
|
||||
process.env.CHIMERA_COOKIE_SECURE = '0';
|
||||
const app = createAppServer({ appRoot: repository, paths: { root: f.root, cache: path.join(f.root, 'cache') }, settings: f.settings, secrets: f.secrets, registry: f.registry, logger: f.logger, ftp: { disconnect: async () => {} }, capabilities: { features: {} }, version: 'test' });
|
||||
await assert.rejects(app.listen(0, '0.0.0.0'), /Niet-loopback serverbinding vereist/);
|
||||
});
|
||||
|
||||
test('proxied first-run setup requires the root-owned bootstrap token', { concurrency: false }, async t => {
|
||||
const f = fixture(t);
|
||||
const tokenFile = path.join(f.root, 'bootstrap.token');
|
||||
const token = 'bootstrap-' + 'a'.repeat(48);
|
||||
fs.writeFileSync(tokenFile, `${token}\n`, { mode: 0o600 });
|
||||
const keys = ['CHIMERA_ALLOWED_HOSTS', 'CHIMERA_PUBLIC_ORIGIN', 'CHIMERA_TRUST_PROXY', 'CHIMERA_COOKIE_SECURE', 'CHIMERA_BOOTSTRAP_TOKEN_FILE'];
|
||||
const previous = Object.fromEntries(keys.map(key => [key, process.env[key]]));
|
||||
t.after(() => { for (const [key, value] of Object.entries(previous)) value === undefined ? delete process.env[key] : process.env[key] = value; });
|
||||
process.env.CHIMERA_ALLOWED_HOSTS = 'chimera.test,127.0.0.1';
|
||||
process.env.CHIMERA_PUBLIC_ORIGIN = 'https://chimera.test';
|
||||
process.env.CHIMERA_TRUST_PROXY = '1';
|
||||
process.env.CHIMERA_COOKIE_SECURE = '1';
|
||||
process.env.CHIMERA_BOOTSTRAP_TOKEN_FILE = tokenFile;
|
||||
const app = createAppServer({ appRoot: repository, paths: { root: f.root, cache: path.join(f.root, 'cache') }, settings: f.settings, secrets: f.secrets, registry: f.registry, logger: f.logger, ftp: { disconnect: async () => {} }, capabilities: { features: {} }, version: 'test' });
|
||||
const address = await app.listen(0, '127.0.0.1');
|
||||
t.after(() => app.close());
|
||||
const base = `http://127.0.0.1:${address.port}`;
|
||||
const headers = { Host: `127.0.0.1:${address.port}`, Origin: base, 'X-Forwarded-For': '203.0.113.9', 'X-Forwarded-Proto': 'https', 'Content-Type': 'application/json' };
|
||||
const body = JSON.stringify({ username: 'beheerder', password: ['veilig', 'wachtwoord', '123'].join('-'), allowedRoots: [f.root], safetyConfirmed: true });
|
||||
const denied = await fetch(`${base}/api/setup`, { method: 'POST', headers, body });
|
||||
assert.equal(denied.status, 403);
|
||||
const allowed = await fetch(`${base}/api/setup`, { method: 'POST', headers: { ...headers, 'X-Chimera-Bootstrap-Token': token }, body });
|
||||
assert.equal(allowed.status, 201);
|
||||
});
|
||||
|
||||
test('shared Garlic credential is absent and both launch paths fail closed', () => {
|
||||
assert.equal(fs.existsSync(path.join(repository, 'build', 'garlic', 'config.ini')), false);
|
||||
for (const relative of ['src/ipc/garlic-saves.js', 'src/server/register-functional-modules.js']) {
|
||||
const source = fs.readFileSync(path.join(repository, relative), 'utf8');
|
||||
assert.match(source, /GARLIC_SECURE_ENROLLMENT_REQUIRED/);
|
||||
assert.doesNotMatch(source, /uploadFile\([^\n]*garlic\/config\.ini/);
|
||||
}
|
||||
});
|
||||
|
||||
test('desktop settings never return plaintext FTP or webhook secrets', () => {
|
||||
const source = fs.readFileSync(path.join(repository, 'main.js'), 'utf8');
|
||||
assert.match(source, /safeStorage\.encryptString/);
|
||||
assert.match(source, /getSelectedStorageBackend\(\) !== 'basic_text'/);
|
||||
assert.match(source, /ftpPass:\s*''/);
|
||||
assert.match(source, /discordWebhookUrl:\s*''/);
|
||||
assert.match(source, /ftpPassConfigured:hasDesktopSecret\('ftpPassword'\)/);
|
||||
assert.match(source, /discordWebhookConfigured:hasDesktopSecret\('discordWebhook'\)/);
|
||||
assert.doesNotMatch(source, /store\.get\('ftp\.pass'/);
|
||||
assert.doesNotMatch(source, /store\.get\('discord\.webhookUrl'/);
|
||||
});
|
||||
Reference in new issue
Block a user