timmy-talking-turd/tests/staging-config.test.js
Timmy f8f01c4b2e
All checks were successful
Quality gates / quality (pull_request) Successful in 1m28s
feat: add hardened atomic staging deployment
2026-08-21 14:49:28 +00:00

106 lines
6.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { spawnSync } from 'node:child_process';
const servicePath = new URL('../deploy/timmy-staging.service', import.meta.url);
const envPath = new URL('../deploy/timmy-staging.env.example', import.meta.url);
const caddyPath = new URL('../deploy/Caddyfile.staging.example', import.meta.url);
const runbookPath = new URL('../docs/STAGING-RUNBOOK.md', import.meta.url);
const literalSecret = /(password|token|secret|api[_-]?key)\s*[=:]\s*(?!\$\{|<|CHANGE_ME|false|$)["']?[A-Za-z0-9/+_.-]{8,}/i;
test('systemd template runs a dedicated loopback-only agent-disabled service', async () => {
const service = await readFile(servicePath, 'utf8');
for (const directive of [
'User=timmy-staging', 'Group=timmy-staging',
'WorkingDirectory=/opt/timmy-staging/current',
'EnvironmentFile=/etc/timmy-staging.env',
'Environment=HOST=127.0.0.1', 'Environment=PORT=4174',
'Environment=TIMMY_BASE_PATH=/timmy-staging',
'Environment=TIMMY_AGENT_ENABLED=false',
]) assert.match(service, new RegExp(`^${directive.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'), directive);
assert.match(service, /^ExecStart=\/usr\/bin\/env TIMMY_AGENT_ENABLED=false TIMMY_VISION_ENABLED=false \/usr\/local\/lib\/timmy-staging\/node server\.mjs$/m);
assert.doesNotMatch(service, /ExecStart=\/usr\/(?:local\/)?bin\/node|0\.0\.0\.0|TIMMY_AGENT_ENABLED=true/);
assert.doesNotMatch(service, literalSecret);
});
test('systemd command-level environment keeps agent and vision disabled after EnvironmentFile overrides', async () => {
const service = await readFile(servicePath, 'utf8');
const environmentFileIndex = service.indexOf('EnvironmentFile=');
const execLine = service.match(/^ExecStart=(.+)$/m)?.[1];
assert.ok(execLine, 'ExecStart must exist');
assert.ok(environmentFileIndex < service.indexOf(`ExecStart=${execLine}`), 'EnvironmentFile must be loaded before command overrides');
const argv = execLine.trim().split(/\s+/);
assert.equal(argv.shift(), '/usr/bin/env');
const assignments = argv.filter(value => /^TIMMY_(?:AGENT|VISION)_ENABLED=/.test(value));
assert.deepEqual(assignments, ['TIMMY_AGENT_ENABLED=false', 'TIMMY_VISION_ENABLED=false']);
const probe = spawnSync('/usr/bin/env', [
...assignments, process.execPath, '-e',
'process.stdout.write(`${process.env.TIMMY_AGENT_ENABLED},${process.env.TIMMY_VISION_ENABLED}`)',
], { env: { ...process.env, TIMMY_AGENT_ENABLED: 'true', TIMMY_VISION_ENABLED: 'true' }, encoding: 'utf8' });
assert.equal(probe.status, 0, probe.stderr);
assert.equal(probe.stdout, 'false,false');
});
test('systemd template applies filesystem privilege process and resource confinement', async () => {
const service = await readFile(servicePath, 'utf8');
for (const directive of [
'UMask=0077', 'NoNewPrivileges=true', 'PrivateTmp=true', 'PrivateDevices=true',
'ProtectSystem=strict', 'ProtectHome=true', 'ReadWritePaths=/var/lib/timmy-staging',
'RestrictSUIDSGID=true', 'LockPersonality=true', 'RestrictNamespaces=true',
'ProtectKernelTunables=true', 'ProtectKernelModules=true', 'ProtectKernelLogs=true',
'ProtectControlGroups=true', 'ProtectClock=true', 'ProtectHostname=true',
'CapabilityBoundingSet=', 'AmbientCapabilities=', 'RestrictRealtime=true',
'TasksMax=64', 'MemoryMax=512M', 'LimitNOFILE=1024',
]) assert.match(service, new RegExp(`^${directive.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'), directive);
assert.equal((service.match(/^ReadWritePaths=/gm) || []).length, 1);
});
test('environment example is least privilege base-path staging configuration without credentials', async () => {
const env = await readFile(envPath, 'utf8');
for (const setting of [
'HOST=127.0.0.1', 'PORT=4174', 'TIMMY_BASE_PATH=/timmy-staging',
'TIMMY_STAGING_LABEL=true', 'TIMMY_AGENT_ENABLED=false', 'TIMMY_VISION_ENABLED=false',
]) assert.match(env, new RegExp(`^${setting.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'));
assert.match(env, /install -m 600/);
assert.doesNotMatch(env, /TIMMY_AGENT_ACCESS_TOKEN|GITEA_TOKEN|OPENAI_API_KEY|COOKIE/);
assert.doesNotMatch(env, literalSecret);
});
test('Caddy snippet isolates git and authenticates staging before a catchall', async () => {
const caddy = await readFile(caddyPath, 'utf8');
const git = caddy.indexOf('handle @git');
const staging = caddy.indexOf('handle_path /timmy-staging/*');
const catchall = caddy.indexOf('handle {');
assert.ok(git >= 0 && staging > git && catchall > staging, 'route order must be git, staging, catchall');
assert.match(caddy, /basic_auth/);
assert.match(caddy, /\{\$TIMMY_STAGING_PASSWORD_HASH\}/);
assert.match(caddy, /reverse_proxy 127\.0\.0\.1:4174/);
assert.match(caddy, /max_size 8MB/);
assert.match(caddy, /X-Content-Type-Options "nosniff"/);
assert.match(caddy, /X-Frame-Options "DENY"/);
assert.match(caddy, /Referrer-Policy "no-referrer"/);
assert.match(caddy, /Content-Security-Policy/);
assert.match(caddy, /rewrite \* \/timmy-staging\{uri\}/, 'handle_path strips once, then upstream base path is explicitly reconstructed');
assert.doesNotMatch(caddy, /\$2[aby]\$[A-Za-z0-9./]{20,}|password\s+[^<{\s]/i);
});
test('runbook covers safe installation operation verification rollback backup and removal', async () => {
const runbook = await readFile(runbookPath, 'utf8');
for (const heading of [
'Prerequisites', 'DNS and URL', 'Install', 'Promote', 'Smoke test', 'Status and logs',
'Rollback', 'Backup', 'Remove staging', 'Template validation',
]) assert.match(runbook, new RegExp(`^## .*${heading}`, 'mi'), heading);
assert.match(runbook, /sha256/i);
assert.match(runbook, /install -D -o root -g root -m 755[^\n]+\/usr\/local\/lib\/timmy-staging\/node/);
assert.match(runbook, /\/usr\/local\/lib\/timmy-staging\/node --version/);
assert.match(runbook, /chmod 600|install -m 600/);
assert.match(runbook, /systemctl restart timmy-staging\.service/);
assert.match(runbook, /no prior release[^.]*stops[^.]*service/i);
assert.match(runbook, /failed release[^.]*inert[^.]*evidence/i);
assert.match(runbook, /journalctl -u timmy-staging\.service/);
assert.match(runbook, /do not.*live|approval/i);
assert.doesNotMatch(runbook, literalSecret);
});