mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
Phase 3 — Code Quality: - Deduplicate nowUnix() in lock-ops.mjs (import from utils.mjs) - Promote ESLint no-unused-vars to error, fix all 46 violations - Remove dead code: unused imports, variables, and catch bindings - Add /* global marked */ for CDN-loaded library in dashboard Phase 4 — CI/CD: - Add Node.js version matrix (22, 23) to CI workflow - Add npm audit security scan step to CI - Add innerHTML usage check to CI pipeline - Enable Dependabot for npm and GitHub Actions dependencies - Create PR template Phase 5 — Developer Experience: - Create .env.example documenting all environment variables - Expand CONTRIBUTING.md with commit conventions, agent/prompt creation guide, architecture overview, and test integrity - Add system architecture diagram with Mermaid (docs/architecture/) https://claude.ai/code/session_01GfwUNbgeMgXreFDbsNQMsS
115 lines
4.3 KiB
JavaScript
115 lines
4.3 KiB
JavaScript
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import http from 'node:http';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { cmdDashboard } from '../src/dashboard.mjs';
|
|
|
|
const tempConfigPath = path.resolve('torch-config-exposure-test.json');
|
|
|
|
const SENSITIVE_CONFIG = {
|
|
nostrLock: {
|
|
namespace: 'test-namespace',
|
|
relays: ['wss://relay.example.com'],
|
|
dailyRoster: ['user1', 'user2'], // Sensitive roster
|
|
weeklyRoster: ['user3'],
|
|
ttlSeconds: 3600,
|
|
queryTimeoutMs: 5000,
|
|
publishTimeoutMs: 5000,
|
|
minSuccessfulRelayPublishes: 2,
|
|
minActiveRelayPool: 1,
|
|
},
|
|
dashboard: {
|
|
auth: 'secret-auth-string', // stripped by auth check
|
|
relays: ['wss://dashboard.relay.example.com'],
|
|
},
|
|
scheduler: {
|
|
paused: {
|
|
daily: ['user1'],
|
|
},
|
|
firstPromptByCadence: {
|
|
daily: 'some prompt',
|
|
},
|
|
},
|
|
};
|
|
|
|
test('Dashboard Configuration Exposure Security', async (t) => {
|
|
// Setup temp config
|
|
fs.writeFileSync(tempConfigPath, JSON.stringify(SENSITIVE_CONFIG, null, 2));
|
|
|
|
// Override env to use this config
|
|
const originalEnvPath = process.env.TORCH_CONFIG_PATH;
|
|
process.env.TORCH_CONFIG_PATH = tempConfigPath;
|
|
// Disable auth via env so we can access the endpoint without credentials
|
|
// The vulnerability is about exposure of *other* fields.
|
|
// We need to bypass the basic auth middleware to test the JSON response.
|
|
// However, cmdDashboard checks config.dashboard.auth too.
|
|
// If we want to test that auth is stripped from JSON, we should enable auth but provide credentials.
|
|
// OR we can just test the sanitization logic which runs regardless of how we got there?
|
|
// No, the endpoint logic runs after auth check.
|
|
|
|
// To keep it simple: we want to verify that IF we can access the endpoint, the sensitive data is gone.
|
|
// We can simulate an authorized request or disable auth.
|
|
// If we disable auth in config, we can access it.
|
|
|
|
// Let's modify the config for the test to NOT have auth, so we can access it freely.
|
|
// We want to verify that things like 'dailyRoster' are stripped.
|
|
const configForTest = { ...SENSITIVE_CONFIG };
|
|
delete configForTest.dashboard.auth;
|
|
fs.writeFileSync(tempConfigPath, JSON.stringify(configForTest, null, 2));
|
|
|
|
let server;
|
|
let serverPort;
|
|
|
|
// Cleanup function
|
|
t.after(() => {
|
|
if (server) server.close();
|
|
if (fs.existsSync(tempConfigPath)) fs.unlinkSync(tempConfigPath);
|
|
if (originalEnvPath) process.env.TORCH_CONFIG_PATH = originalEnvPath;
|
|
else delete process.env.TORCH_CONFIG_PATH;
|
|
});
|
|
|
|
// Start server
|
|
await new Promise((resolve) => {
|
|
cmdDashboard(0).then((srv) => {
|
|
server = srv;
|
|
serverPort = server.address().port;
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
// Fetch config
|
|
const responseBody = await new Promise((resolve, reject) => {
|
|
http.get(`http://localhost:${serverPort}/torch-config.json`, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => resolve(data));
|
|
}).on('error', reject);
|
|
});
|
|
|
|
let exposedConfig;
|
|
try {
|
|
exposedConfig = JSON.parse(responseBody);
|
|
} catch (_e) {
|
|
assert.fail(`Failed to parse JSON response: ${responseBody}`);
|
|
}
|
|
|
|
// 1. Assert sensitive fields are NOT present
|
|
assert.equal(exposedConfig.nostrLock?.dailyRoster, undefined, 'dailyRoster should NOT be exposed');
|
|
assert.equal(exposedConfig.nostrLock?.weeklyRoster, undefined, 'weeklyRoster should NOT be exposed');
|
|
assert.equal(exposedConfig.nostrLock?.relayFallbacks, undefined, 'relayFallbacks should NOT be exposed');
|
|
assert.equal(exposedConfig.scheduler, undefined, 'scheduler config should NOT be exposed');
|
|
assert.equal(exposedConfig.raw, undefined, 'raw config should NOT be exposed');
|
|
|
|
// 2. Assert required fields ARE present
|
|
assert.ok(exposedConfig.dashboard, 'dashboard config should be present');
|
|
// Note: cmdDashboard loads from file, parseTorchConfig normalizes keys.
|
|
// check dashboard.relays
|
|
assert.deepEqual(exposedConfig.dashboard.relays, ['wss://dashboard.relay.example.com'], 'dashboard relays should be present');
|
|
|
|
assert.ok(exposedConfig.nostrLock, 'nostrLock config should be present');
|
|
assert.equal(exposedConfig.nostrLock.namespace, 'test-namespace', 'nostrLock namespace should be present');
|
|
assert.deepEqual(exposedConfig.nostrLock.relays, ['wss://relay.example.com'], 'nostrLock relays should be present');
|
|
});
|