Files
Redflag/docs/E1c_Fix_Implementation.md
jpetree331 5ae114df7a feat(config): E-1b/E-1c TypeScript strict compliance, configurable timeouts, path sanitization
E-1b: Fix 217 TypeScript strict errors to zero (tsc --noEmit clean).
  - Remove unused vars/imports, fix type mismatches, widen interfaces
  - TanStack Query v5 isLoading->isPending for mutations
  - No @ts-ignore or as any introduced

E-1ab verification fixes:
  - Fix audit table name mismatch (security_setting_audit -> security_settings_audit)
  - Fix DockerContainer TS type (image_name->image, image_tag->tag) to match server
  - Add 501 for empty binary_path in downloads
  - Fix ETHOS log format in downloads error path

E-1c: Configurable timeouts + path sanitization
  - Seed 6 operational timeout settings in DB (migration 030)
  - Wire server to read timeout values from DB at startup
  - Fallback to hardcoded defaults if DB settings missing
  - Fix binary_path traversal risk in downloads handler
  - Add BinaryStoragePath config (REDFLAG_BINARY_STORAGE_PATH)
  - Log resolved timeout values at startup

163 tests pass (103 server + 60 agent). No regressions.
Vite build passes. TypeScript: 0 errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:09:12 -04:00

4.7 KiB

E-1c Configurable Timeouts + Path Sanitization

Date: 2026-03-29 Branch: culurien


Summary

Two items completed:

  1. F-E1-3: 6 hardcoded operational timeout values made configurable via DB settings
  2. Security: Binary path traversal defense-in-depth for downloads handler

F-E1-3: Configurable Timeout Values

The 6 Hardcoded Values (Before)

Location Value Purpose
main.go:429 2 * time.Minute Offline agent check interval
main.go:436 10 * time.Minute Offline threshold (mark agent offline)
main.go:445 24 * time.Hour Token cleanup interval
timeout.go:28 2 * time.Hour Sent command timeout
timeout.go:29 30 * time.Minute Pending command timeout
timeout.go:40 5 * time.Minute Timeout check interval

Migration 030: Seed Operational Settings

Created 030_add_operational_settings.up.sql which inserts 6 rows into security_settings under category operational:

Key Default Range Description
offline_check_interval_seconds 120 30-3600 How often to check for offline agents
offline_threshold_minutes 10 2-60 Minutes before agent marked offline
token_cleanup_interval_hours 24 1-168 Hours between token cleanup runs
sent_command_timeout_hours 2 1-24 Hours before sent commands time out
pending_command_timeout_minutes 30 5-120 Minutes before pending commands time out
timeout_check_interval_minutes 5 1-30 How often timeout service checks

Migration uses ON CONFLICT (category, key) DO NOTHING for idempotency (ETHOS #4). Leverages existing UNIQUE(category, key) constraint from migration 020.

Fallback Strategy

A helper function getOperationalSetting() reads from the DB with fallback:

  1. If SecuritySettingsService is nil (DB not ready): return default
  2. If GetSetting() errors: return default
  3. If value is not a valid positive number: return default
  4. Otherwise: return the DB value

This means the server starts correctly even if migration 030 hasn't run yet.

TimeoutService Constructor Change (Option A)

Changed NewTimeoutService to accept 3 additional duration parameters:

func NewTimeoutService(cq, uq, sentTimeout, pendingTimeout, checkInterval time.Duration)

Zero values are replaced with defaults (2h, 30m, 5m). The check interval is now a configurable field instead of a hardcoded 5 * time.Minute in Start().

Startup Log

[INFO] [server] [config] operational_timeouts_loaded offline_check=2m0s offline_threshold=10m0s token_cleanup=24h0m0s sent_cmd_timeout=2h0m0s pending_cmd_timeout=30m0s timeout_check=5m0s

Security: Binary Path Sanitization

The Risk (Flagged in E-1ab Part 4b)

pkg.BinaryPath from the database was passed directly to c.File(). If the database were compromised, arbitrary file read was possible via path traversal.

The Fix

Before serving, the handler now:

  1. Resolves BinaryPath to an absolute path via filepath.Abs()
  2. Resolves the allowed directory from config.BinaryStoragePath (env: REDFLAG_BINARY_STORAGE_PATH, default: ./binaries)
  3. Checks that the resolved path has the allowed directory as a prefix
  4. If not: returns 403 and logs [ERROR] [server] [downloads] path_traversal_attempt
  5. If yes: serves the file from the resolved absolute path

Config Addition

Env Var Default Purpose
REDFLAG_BINARY_STORAGE_PATH ./binaries Allowed directory for binary downloads

Added to Config struct in config.go.

Files Changed

File Change
migrations/030_add_operational_settings.up.sql NEW: Seeds 6 operational settings
migrations/030_add_operational_settings.down.sql NEW: Removes operational settings
cmd/server/main.go Reads settings from DB, passes to TimeoutService, uses in goroutines, getOperationalSetting() helper
internal/services/timeout.go Constructor accepts durations + checkInterval field, Start() uses configurable interval
internal/config/config.go Added BinaryStoragePath field
internal/api/handlers/downloads.go Path sanitization before c.File()

Tests Added

File Tests
services/timeout_config_test.go TestTimeoutServiceUsesConfiguredValues, TestTimeoutServiceFallsBackToDefaults, TestGetOperationalSettingFallsBackToDefault
handlers/downloads_security_test.go TestDownloadsRejectsPathTraversal, TestDownloadsAcceptsSafePath, TestDownloadsRejectsSymlinkEscape

Test Results

Server: 103 passed, 0 failed (7 packages)
Agent:   60 passed, 0 failed (10 packages)
Total:  163 tests, 0 failures