Phase 1: Add ESLint/Prettier config + baseline auto-fixes

This commit is contained in:
Krystie
2026-03-22 11:00:25 +01:00
parent 41a0cdee7e
commit e2c67a8fe8
90 changed files with 4008 additions and 3066 deletions

View File

@@ -150,7 +150,7 @@ describe('Sites Route Security', () => {
.post('/api/site/external')
.send({
subdomain: 'test',
externalUrl: 'https://evil.com/path{inject}'
externalUrl: 'https://evil.com/path{inject}',
});
// Should be rejected — either 400 (our validation) or 500 (URL constructor throws on {})
@@ -164,7 +164,7 @@ describe('Sites Route Security', () => {
.post('/api/site/external')
.send({
subdomain: 'test',
externalUrl: 'https://evil.com/path\nreverse_proxy malicious:1234'
externalUrl: 'https://evil.com/path\nreverse_proxy malicious:1234',
});
expect(res.statusCode).toBe(400);
@@ -183,7 +183,7 @@ describe('Sites Route Security', () => {
.post('/api/site/external')
.send({
subdomain: '../etc/passwd',
externalUrl: 'https://example.com'
externalUrl: 'https://example.com',
});
expect(res.statusCode).toBe(400);
@@ -205,7 +205,7 @@ describe('Error Logs — No Stack Trace Leak', () => {
'[2026-03-07 12:01:00] dns: DNS timeout',
'Error: connect ECONNREFUSED 192.168.1.1:5380',
' at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1234:16)',
'================================================================================'
'================================================================================',
].join('\n');
// Write to the server's error log file location
// The server uses ctx.ERROR_LOG_FILE — we need to check what that resolves to
@@ -334,10 +334,10 @@ describe('Backup Security', () => {
files: {
encryptionKey: {
type: 'text',
content: 'malicious-key-data'
}
}
}
content: 'malicious-key-data',
},
},
},
});
// The encryptionKey should be skipped (not in fileMapping)
@@ -392,8 +392,8 @@ describe('Custom Volume Path Validation', () => {
port: '32400',
customVolumes: [{
containerPath: '/config',
hostPath: '/etc/shadow'
}]
hostPath: '/etc/shadow',
}],
});
// The deploy will likely fail for other reasons (no Docker, etc.)
@@ -414,7 +414,7 @@ describe('Logo Delete Path Traversal', () => {
// Write config with a malicious logo path
const configWithMaliciousLogo = {
customLogo: '/assets/../../etc/passwd',
customLogoDark: '/assets/../../../root/.ssh/id_rsa'
customLogoDark: '/assets/../../../root/.ssh/id_rsa',
};
await fsp.writeFile(testConfigFile, JSON.stringify(configWithMaliciousLogo), 'utf8');
@@ -439,7 +439,7 @@ describe('DNS Server SSRF Prevention', () => {
.query({
domain: 'test.sami',
type: 'A',
server: '169.254.169.254' // AWS metadata endpoint
server: '169.254.169.254', // AWS metadata endpoint
});
// Must never succeed — 400 (server rejected), 401 (no token), or 500 (dns not configured in test)
@@ -452,7 +452,7 @@ describe('DNS Server SSRF Prevention', () => {
.send({
domain: 'test.sami',
ipAddress: '192.168.1.1',
server: '10.0.0.1' // Not a configured DNS server
server: '10.0.0.1', // Not a configured DNS server
});
expect(res.statusCode).not.toBe(200);
@@ -463,7 +463,7 @@ describe('DNS Server SSRF Prevention', () => {
.get('/api/dns/resolve')
.query({
domain: 'test.sami',
server: '127.0.0.1'
server: '127.0.0.1',
});
expect(res.statusCode).not.toBe(200);
@@ -503,7 +503,7 @@ describe('HTTP Fetch Response Size Limit', () => {
test('server should define MAX_RESPONSE_SIZE constant', () => {
// Read server.js and verify the limit is defined
const serverSource = fs.readFileSync(
path.join(__dirname, '..', 'server.js'), 'utf8'
path.join(__dirname, '..', 'server.js'), 'utf8',
);
expect(serverSource).toContain('MAX_RESPONSE_SIZE');
expect(serverSource).toContain('10 * 1024 * 1024');
@@ -516,7 +516,7 @@ describe('HTTP Fetch Response Size Limit', () => {
describe('Middleware Security', () => {
test('middleware should set Secure flag on cookies', () => {
const middlewareSource = fs.readFileSync(
path.join(__dirname, '..', 'middleware.js'), 'utf8'
path.join(__dirname, '..', 'middleware.js'), 'utf8',
);
// Verify the Set-Cookie string includes Secure
expect(middlewareSource).toContain('; Secure;');
@@ -529,7 +529,7 @@ describe('Middleware Security', () => {
describe('Config Save Atomicity', () => {
test('saveConfig should use state manager for locking', () => {
const serverSource = fs.readFileSync(
path.join(__dirname, '..', 'server.js'), 'utf8'
path.join(__dirname, '..', 'server.js'), 'utf8',
);
// Verify saveConfig uses configStateManager.update (not raw fs.writeFile)
expect(serverSource).toContain('configStateManager.update');
@@ -542,7 +542,7 @@ describe('Config Save Atomicity', () => {
describe('External URL Security', () => {
test('sites.js should validate URL components for unsafe chars', () => {
const sitesSource = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'sites.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'sites.js'), 'utf8',
);
// Verify the unsafe character regex exists
expect(sitesSource).toContain('unsafeCaddyChars');
@@ -556,7 +556,7 @@ describe('External URL Security', () => {
describe('Credential Manager File Locking', () => {
test('credential-manager should use proper-lockfile', () => {
const cmSource = fs.readFileSync(
path.join(__dirname, '..', 'credential-manager.js'), 'utf8'
path.join(__dirname, '..', 'credential-manager.js'), 'utf8',
);
expect(cmSource).toContain('proper-lockfile');
expect(cmSource).toContain('_lockedUpdate');
@@ -569,7 +569,7 @@ describe('Credential Manager File Locking', () => {
describe('TOTP Config File Security', () => {
test('loadTotpConfig should delete secret from file data', () => {
const serverSource = fs.readFileSync(
path.join(__dirname, '..', 'server.js'), 'utf8'
path.join(__dirname, '..', 'server.js'), 'utf8',
);
// Verify the secret deletion exists in loadTotpConfig
expect(serverSource).toContain('delete loaded.secret');
@@ -577,7 +577,7 @@ describe('TOTP Config File Security', () => {
test('totp verify-setup should not write secret to config file', () => {
const totpSource = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'auth', 'totp.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'auth', 'totp.js'), 'utf8',
);
// Verify totpConfig.secret assignment is NOT present
expect(totpSource).not.toContain('totpConfig.secret = pendingSecret');
@@ -591,7 +591,7 @@ describe('TOTP Config File Security', () => {
describe('Helpers — Volume Security', () => {
test('helpers.js should validate hostPath against allowed roots', () => {
const helpersSource = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'apps', 'helpers.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'apps', 'helpers.js'), 'utf8',
);
expect(helpersSource).toContain('allowedRoots');
expect(helpersSource).toContain('platformPaths.dockerData');
@@ -605,7 +605,7 @@ describe('Helpers — Volume Security', () => {
describe('Error Logs — Response Format', () => {
test('errorlogs.js should not include details field', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'errorlogs.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'errorlogs.js'), 'utf8',
);
// The parsed log object should only have timestamp, context, error
// NOT details (which contains stack traces)
@@ -622,7 +622,7 @@ describe('Error Logs — Response Format', () => {
describe('Assets — Logo Path Safety', () => {
test('assets.js should use path.basename for logo filename extraction', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'config', 'assets.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'config', 'assets.js'), 'utf8',
);
expect(source).toContain('path.basename(logoPath)');
// Should NOT use string replace for path extraction
@@ -636,7 +636,7 @@ describe('Assets — Logo Path Safety', () => {
describe('Backup — Encryption Key Exclusion', () => {
test('backup.js should not include encryptionKey in filesToBackup', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'config', 'backup.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'config', 'backup.js'), 'utf8',
);
// Should have a comment about deliberate exclusion
expect(source).toContain('encryptionKey deliberately excluded');
@@ -646,7 +646,7 @@ describe('Backup — Encryption Key Exclusion', () => {
test('backup.js restore fileMapping should not include encryptionKey', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'config', 'backup.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'config', 'backup.js'), 'utf8',
);
// The RESTORE route's fileMapping (after "encryptionKey excluded" comment) must not have it
// The preview route's fileMapping is allowed to have it (informational only)
@@ -659,7 +659,7 @@ describe('Backup — Encryption Key Exclusion', () => {
test('backup.js should require TOTP for sensitive restores', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'config', 'backup.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'config', 'backup.js'), 'utf8',
);
expect(source).toContain('sensitiveKeys');
expect(source).toContain('totpCode');
@@ -673,7 +673,7 @@ describe('Backup — Encryption Key Exclusion', () => {
describe('DNS — Server Validation Function', () => {
test('dns.js should define validateDnsServer', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'dns.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'dns.js'), 'utf8',
);
expect(source).toContain('function validateDnsServer');
expect(source).toContain('configuredIps');
@@ -687,7 +687,7 @@ describe('DNS — Server Validation Function', () => {
describe('Containers — Verified Container Access', () => {
test('containers.js update route should use getVerifiedContainer', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'containers.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'containers.js'), 'utf8',
);
// update and check-update should both use getVerifiedContainer
const updateSection = source.substring(source.indexOf("'/:id/update'"));
@@ -704,7 +704,7 @@ describe('Containers — Verified Container Access', () => {
describe('Logs — Symlink Resolution', () => {
test('logs.js should use realpath for symlink resolution', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'logs.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'logs.js'), 'utf8',
);
expect(source).toContain('fsp.realpath');
expect(source).toContain('path.sep');
@@ -712,7 +712,7 @@ describe('Logs — Symlink Resolution', () => {
test('logs.js container routes should verify container exists', () => {
const source = fs.readFileSync(
path.join(__dirname, '..', 'routes', 'logs.js'), 'utf8'
path.join(__dirname, '..', 'routes', 'logs.js'), 'utf8',
);
// Both container/:id and stream/:id should have inspect + NotFoundError
expect(source).toContain('container.inspect()');