Fix 7 critical security bugs and 1 high-severity data loss bug
- CSRF: HMAC-signed double-submit cookie (server-bound, not raw compare)
- Keychain: execFileSync with arg arrays to prevent command injection
- Caddy config: always use structured generation, never accept raw config
- Templates: replace {{GENERATED_SECRET}} with crypto.randomBytes
- Caddyfile removal: move regex inside ctx.caddy.modify() to fix TOCTOU race
- Credentials: proper-lockfile for all file operations, fix key rotation
to decrypt with old key before generating new key
- Service removal: filter by ID only, not AND with appTemplate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const fsp = require('fs').promises;
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { REGEX, DOCKER } = require('../../constants');
|
||||
const { exists } = require('../../fs-helpers');
|
||||
const platformPaths = require('../../platform-paths');
|
||||
@@ -70,7 +71,8 @@ module.exports = function(ctx) {
|
||||
'{{SUBDOMAIN}}': config.subdomain,
|
||||
'{{PORT}}': config.port || template.defaultPort,
|
||||
'{{MEDIA_PATH}}': mediaPaths[0] || '/media',
|
||||
'{{TIMEZONE}}': ctx.siteConfig.timezone || 'UTC'
|
||||
'{{TIMEZONE}}': ctx.siteConfig.timezone || 'UTC',
|
||||
'{{GENERATED_SECRET}}': crypto.randomBytes(32).toString('hex')
|
||||
};
|
||||
|
||||
function replaceInObject(obj) {
|
||||
|
||||
@@ -68,18 +68,14 @@ module.exports = function(ctx, helpers) {
|
||||
} else {
|
||||
// Subdomain mode: remove standalone domain block
|
||||
const domain = ctx.buildDomain(subdomain);
|
||||
let content = await ctx.caddy.read();
|
||||
const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const siteBlockRegex = new RegExp(`\\n?${escapedDomain}\\s*\\{[^{}]*(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}[^{}]*)*\\}\\s*`, 'g');
|
||||
const originalLength = content.length;
|
||||
content = content.replace(siteBlockRegex, '\n');
|
||||
if (content.length !== originalLength) {
|
||||
content = content.replace(/\n{3,}/g, '\n\n');
|
||||
const caddyResult = await ctx.caddy.modify(() => content);
|
||||
results.caddy = caddyResult.success ? 'removed' : 'removed (reload failed)';
|
||||
} else {
|
||||
results.caddy = 'not found';
|
||||
}
|
||||
const caddyResult = await ctx.caddy.modify(currentContent => {
|
||||
const replaced = currentContent.replace(siteBlockRegex, '\n');
|
||||
if (replaced.length === currentContent.length) return null;
|
||||
return replaced.replace(/\n{3,}/g, '\n\n');
|
||||
});
|
||||
results.caddy = caddyResult.success ? 'removed' : (caddyResult.rolledBack ? 'removed (reload failed)' : 'not found');
|
||||
}
|
||||
ctx.log.info('caddy', 'Caddy config removal', { result: results.caddy });
|
||||
} catch (error) {
|
||||
@@ -94,7 +90,7 @@ module.exports = function(ctx, helpers) {
|
||||
let removed = false;
|
||||
await ctx.servicesStateManager.update(services => {
|
||||
const initialLength = services.length;
|
||||
const filtered = services.filter(s => s.id !== subdomain && s.appTemplate !== appId);
|
||||
const filtered = services.filter(s => s.id !== subdomain);
|
||||
removed = filtered.length !== initialLength;
|
||||
return filtered;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user