archy/loop/plan.md
Dorian 6656d2f1d9 fix: implement 22 security pentest remediation fixes
Server-side session management with SHA-256 hashed tokens and HttpOnly
cookies. Auth middleware gating all RPC/WS/proxy routes with method
allowlist. Login rate limiting (5/60s per IP). CORS restricted to
config origin. Docker registry allowlist. App ID and path validation.
P2P message sanitization (HTML + log injection). Onion address and
known-peer validation. Nginx security headers (CSP, X-Frame-Options,
etc.) and AIUI proxy auth. Systemd hardening (non-root, NoNewPrivileges,
ProtectSystem).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 03:26:56 +00:00

7.7 KiB

I now have complete visibility into all affected code. Here is the remediation plan:

Security Fixes — http://192.168.1.228

  • FIX-001 — fix(AUTH-001): add server-side session management to core/archipelago/src/api/rpc/auth.rs handle_auth_login — on successful password verification, generate a cryptographic random token, hash with SHA-256, store in a server-side session map (Arc<RwLock<HashMap<HashedToken, Session>>>), and return it via Set-Cookie: session=<token>; HttpOnly; SameSite=Strict; Path=/
  • FIX-002 — fix(AUTH-002): add authentication middleware in core/archipelago/src/api/handler.rs handle_request and core/archipelago/src/api/rpc/mod.rs handle — extract and validate session cookie before dispatching to any handler; allowlist only auth.login, auth.isOnboardingComplete, health, and echo as unauthenticated; reject all other requests with 401
  • FIX-003 — fix(AUTH-005): update frontend auth in neode-ui/src/api/rpc-client.ts to send credentials with requests (credentials: 'same-origin') and store auth state based on server session cookie presence, not just localStorage
  • FIX-004 — fix(AUTH-007): add session cookie validation to WebSocket upgrade in core/archipelago/src/api/handler.rs handle_websocket (line 42-43) — parse Cookie header from the upgrade request, validate the session token against the session store, reject with 401 if invalid
  • FIX-005 — fix(SSRF-004): restrict dockerImage in core/archipelago/src/api/rpc/package.rs handle_package_install (line 28) — replace is_valid_docker_image blocklist with an allowlist of trusted registries (docker.io/, ghcr.io/, localhost/) and reject all other image sources
  • FIX-006 — fix(INJ-002): validate package_id in core/archipelago/src/api/rpc/package.rs handle_package_uninstall (line 564-567) and handle_package_install (line 15-18) — add validate_app_id() helper that enforces ^[a-z0-9][a-z0-9-]{0,63}$ regex; call it before any filesystem or command usage; also apply in get_data_dirs_for_app (line 763) and get_containers_for_app
  • FIX-007 — fix(AUTH-003): add rate limiting to core/archipelago/src/api/rpc/auth.rs handle_auth_login — track failed attempts per IP with a sliding window (max 5 failures per 60 seconds); return 429 with Retry-After header when exceeded; use Arc<Mutex<HashMap<IpAddr, Vec<Instant>>>> in RpcHandler
  • FIX-008 — fix(AUTH-008): validate incoming P2P messages in core/archipelago/src/api/handler.rs handle_node_message (line 125-145) — verify from_pubkey is a valid ed25519 public key format (^[0-9a-f]{64}$), add an optional HMAC or ed25519 signature field for message authenticity, and rate-limit by source IP
  • FIX-009 — fix(AUTH-009): replace CORS_ANY wildcard in core/archipelago/src/api/handler.rs (lines 15, 108, 118, 142, 154, 173) — remove the const CORS_ANY: &str = "*" constant; set Access-Control-Allow-Origin to the node's own origin (from Config.host_ip or request Origin header validated against an allowlist); add Vary: Origin header
  • FIX-010 — fix(AUTH-011): ensure /proxy/lnd/* route in core/archipelago/src/api/handler.rs handle_lnd_proxy (line 67-68) is gated by the session validation middleware added in FIX-002; additionally forward the LND macaroon only from server-side config, never from client headers
  • FIX-011 — fix(XSS-004): add security headers to image-recipe/configs/nginx-archipelago.conf in both server blocks — add X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy: camera=(), microphone=(), geolocation=(), and a baseline Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' (with frame-src exceptions for app iframes)
  • FIX-012 — fix(XSS-007): remove the blanket Access-Control-Allow-Origin $http_origin echo pattern from nginx config if present; ensure nginx does not add its own CORS headers that override the backend's restricted CORS (from FIX-009); confirm only same-origin requests are allowed for /rpc/ and /ws locations
  • FIX-013 — fix(SSRF-001): add validate_onion() call at the top of core/archipelago/src/node_message.rs check_peer_reachable (line 115) — currently missing, unlike send_to_peer which already validates; this prevents arbitrary host/port injection via the onion parameter
  • FIX-014 — fix(SSRF-002): ensure node-send-message RPC is behind auth middleware (FIX-002); additionally, in core/archipelago/src/api/rpc/peers.rs handle_node_send_message (line 50-67), validate that the onion address exists in the node's known peer list (peers::load_peers) before sending — prevent SSRF to arbitrary Tor destinations
  • FIX-015 — fix(AUTH-006): implement functional logout in core/archipelago/src/api/rpc/auth.rs handle_auth_logout (line 34-36) — extract session token from request cookie, remove it from the server-side session store, and return a Set-Cookie header that expires the cookie
  • FIX-016 — fix(AUTH-012): ensure /api/container/logs route in core/archipelago/src/api/handler.rs handle_container_logs_http (line 64-66) is gated by the session validation middleware added in FIX-002; also validate app_id query parameter with validate_app_id() from FIX-006
  • FIX-017 — fix(XSS-001): sanitize P2P message content in core/archipelago/src/node_message.rs store_received (line 40-42) — strip or escape HTML entities (<, >, &, ", ') from message and from_pubkey before storing; also ensure the Vue frontend component rendering messages uses {{ }} text interpolation (not v-html)
  • FIX-018 — fix(INJ-001): validate manifest_path in core/archipelago/src/api/rpc/container.rs handle_container_install (line 17-18) — canonicalize the path and verify it starts with the apps/ directory under config.data_dir; reject paths containing .. segments; reject absolute paths outside the allowed base
  • FIX-019 — fix(INJ-006): add authentication to /aiui/api/claude/ in image-recipe/configs/nginx-archipelago.conf (lines 17-28 and 371-382) — add auth_request directive pointing to an internal auth-check endpoint on the backend (e.g., /internal/auth-check that validates the session cookie), or restrict access to authenticated sessions via cookie check in the location block
  • FIX-020 — fix(XSS-005): gate echo/server.echo behind authentication in core/archipelago/src/api/rpc/mod.rs (lines 87-88) — remove echo from the unauthenticated allowlist so it requires a valid session; alternatively, strip or limit message param to alphanumeric + basic punctuation
  • FIX-021 — fix(INJ-007): sanitize log output in core/archipelago/src/api/handler.rs handle_node_message (line 136) — replace newlines (\n, \r) and ANSI escape sequences in from and msg with safe representations before passing to tracing::info!; use .replace('\n', "\\n").replace('\r', "\\r")
  • FIX-022 — fix: harden image-recipe/configs/archipelago.service — change User=root to User=archipelago (dedicated non-root service account); set Environment="ARCHIPELAGO_DEV_MODE=false"; add NoNewPrivileges=true, ProtectSystem=strict, ReadWritePaths=/var/lib/archipelago; this reduces blast radius for all findings
  • VERIFY — test: re-run pentest curl probes from the exploitation report against all 21 finding endpoints to confirm: unauthenticated requests return 401, path traversal payloads are rejected, CORS headers are restrictive, security headers are present, WebSocket requires auth, and the service runs as non-root with dev mode disabled