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>
4.7 KiB
E-1c Configurable Timeouts + Path Sanitization
Date: 2026-03-29 Branch: culurien
Summary
Two items completed:
- F-E1-3: 6 hardcoded operational timeout values made configurable via DB settings
- 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:
- If
SecuritySettingsServiceis nil (DB not ready): return default - If
GetSetting()errors: return default - If value is not a valid positive number: return default
- 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:
- Resolves
BinaryPathto an absolute path viafilepath.Abs() - Resolves the allowed directory from
config.BinaryStoragePath(env:REDFLAG_BINARY_STORAGE_PATH, default:./binaries) - Checks that the resolved path has the allowed directory as a prefix
- If not: returns 403 and logs
[ERROR] [server] [downloads] path_traversal_attempt - 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