mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
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:
+6
-2
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { SAFE_PATH_PATTERN } from '../utils/index.js';
|
||||
import { SAFE_PATH_PATTERN, isSafePushEndpoint } from '../utils/index.js';
|
||||
|
||||
// ========== Path Validation ==========
|
||||
|
||||
@@ -531,7 +531,11 @@ export const RespawnEnableSchema = z.object({
|
||||
|
||||
/** POST /api/push/subscribe */
|
||||
export const PushSubscribeSchema = z.object({
|
||||
endpoint: z.string().url().max(2000),
|
||||
endpoint: z
|
||||
.string()
|
||||
.url()
|
||||
.max(2000)
|
||||
.refine(isSafePushEndpoint, { message: 'endpoint must be an https URL to a public (non-internal) host' }),
|
||||
keys: z.object({
|
||||
p256dh: z.string().min(1).max(500),
|
||||
auth: z.string().min(1).max(500),
|
||||
|
||||
+15
-1
@@ -97,7 +97,13 @@ import {
|
||||
type ImageDetectedEvent,
|
||||
DEFAULT_NICE_CONFIG,
|
||||
} from '../types.js';
|
||||
import { CleanupManager, KeyedDebouncer, StaleExpirationMap, startEventLoopMonitor } from '../utils/index.js';
|
||||
import {
|
||||
CleanupManager,
|
||||
KeyedDebouncer,
|
||||
StaleExpirationMap,
|
||||
startEventLoopMonitor,
|
||||
isSafePushEndpoint,
|
||||
} from '../utils/index.js';
|
||||
import type { EventLoopMonitorHandle } from '../utils/index.js';
|
||||
import { MAX_CONCURRENT_SESSIONS, MAX_SSE_CLIENTS } from '../config/map-limits.js';
|
||||
import { SseEvent } from './sse-events.js';
|
||||
@@ -1639,6 +1645,14 @@ export class WebServer extends EventEmitter {
|
||||
// Check per-subscription preferences
|
||||
if (sub.pushPreferences[event] === false) continue;
|
||||
|
||||
// Re-validate the stored endpoint before fetching it server-side (SSRF, M7).
|
||||
// Defense-in-depth: subscribe-time validation already rejects unsafe URLs.
|
||||
if (!isSafePushEndpoint(sub.endpoint)) {
|
||||
console.warn('[push] skipping notification to unsafe endpoint:', sub.endpoint);
|
||||
this.pushStore.removeByEndpoint(sub.endpoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pushSub = {
|
||||
endpoint: sub.endpoint,
|
||||
keys: sub.keys,
|
||||
|
||||
Reference in New Issue
Block a user