fix(security): push-endpoint SSRF guard + tmux name validation; document tail-file roots

- M7 (SSRF): add isSafePushEndpoint (https-only; reject internal/loopback/link-local/metadata IPs incl. IPv4-mapped); enforce in PushSubscribeSchema and re-check before webpush.sendNotification. + unit test.
- M1 (command injection): validate tmux session names with isValidMuxName in sessionExists, killSession, and reconcileSessions before they reach a shell call site.
- M5: keep the intentional /var/log + ~/logs log-tail roots (a tested feature) and document the wider read scope in docs/security-architecture.md section 5 instead of dropping it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-06-09 20:02:17 +02:00
co-authored by Claude Opus 4.8
parent d5f91e4cd7
commit b84438a0aa
8 changed files with 174 additions and 12 deletions
+18 -7
View File
@@ -920,6 +920,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
private sessionExists(muxName: string): boolean {
if (IS_TEST_MODE) return false;
if (!isValidMuxName(muxName)) return false;
try {
execSync(`${this.tmux()} has-session -t "${muxName}" 2>/dev/null`, {
@@ -1060,13 +1061,15 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
}
}
// Strategy 3: Kill tmux session by name
try {
execSync(`${this.tmux()} kill-session -t "${session.muxName}" 2>/dev/null`, {
timeout: EXEC_TIMEOUT_MS,
});
} catch {
// Session may already be dead
// Strategy 3: Kill tmux session by name (guard the name before it reaches the shell)
if (isValidMuxName(session.muxName)) {
try {
execSync(`${this.tmux()} kill-session -t "${session.muxName}" 2>/dev/null`, {
timeout: EXEC_TIMEOUT_MS,
});
} catch {
// Session may already be dead
}
}
// Strategy 4: Direct kill by PID as final fallback
@@ -1166,6 +1169,14 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
for (const [sessionName, pid] of active) {
if (!sessionName.startsWith('codeman-') && !sessionName.startsWith('claudeman-')) continue;
// Only admit names that pass the safe-name pattern. A foreign process on the
// shared `tmux -L codeman` socket could create a `codeman-…` session whose name
// contains shell metacharacters; rejecting it here keeps it out of this.sessions
// and away from the name-interpolating tmux call sites (M1).
if (!isValidMuxName(sessionName)) {
console.warn(`[TmuxManager] Skipping discovered tmux session with unsafe name: ${sessionName}`);
continue;
}
if (knownMuxNames.has(sessionName)) continue;
const fragment = sessionName.replace(/^(?:codeman|claudeman)-/, '');