fix(stacks): harden stack management with security, validation, and logging (#520)

* fix(stacks): harden stack management with security fixes, validation alignment, and logging

Validate WebSocket stack names with isValidStackName() to close a
path-traversal gap on the /api/stacks/:stackName/logs WS endpoint.
Align POST /api/stacks to use the canonical validator (allows underscores).
Replace error: any catch blocks with error: unknown + type narrowing.
Add cache invalidation to PUT /api/stacks/:stackName/env.
Rename DELETE param from :name to :stackName for consistency.

Add standard [Stacks] lifecycle logs and diagnostic [Stacks:debug] logs
gated behind the Developer Mode toggle (with 5s TTL cache).
Extract shared isDebugEnabled() and getErrorMessage() utilities.

Frontend: roll back optimistic status on API failure, guard unsaved
changes when switching stacks, pre-check duplicate names in App Store.

* docs(settings): update Developer Mode description to mention debug diagnostics
This commit is contained in:
Anso
2026-04-12 05:43:15 -04:00
committed by GitHub
parent 3ad1ab5c84
commit 2465f7607e
11 changed files with 350 additions and 30 deletions
+30
View File
@@ -0,0 +1,30 @@
/**
* Shared diagnostic logging gate.
*
* Reads `developer_mode` from the global settings, cached for a short
* window so hot paths (per-request, per-log-line) do not hit SQLite
* on every call.
*/
let cachedValue = false;
let cacheExpiry = 0;
const CACHE_TTL_MS = 5_000;
export function isDebugEnabled(): boolean {
const now = Date.now();
if (now < cacheExpiry) return cachedValue;
try {
// Dynamic require avoids circular-dependency issues when this
// utility is imported from services that DatabaseService itself
// depends on, and prevents SQLite side effects during tests.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { DatabaseService } = require('../services/DatabaseService');
cachedValue = DatabaseService.getInstance().getGlobalSettings().developer_mode === '1';
} catch {
cachedValue = false;
}
cacheExpiry = now + CACHE_TTL_MS;
return cachedValue;
}