Files
archy/loop/pentest/recon/attack-surface-analysis.md
Dorian 870ff095d8 feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
  RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
  RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)

Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)

Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready

UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
  after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect

Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00

28 KiB

Archipelago Attack Surface Analysis

Target: 192.168.1.228 Date: 2026-03-18 Scope: Authorized security assessment — full infrastructure Assessor: Automated recon + source code review


1. Target Overview

Technologies Detected

Layer Technology Version
OS Debian 12 (Bookworm) x86_64, kernel unknown
Web Server nginx 1.22.1
Reverse Proxy (alt) OpenResty (port 81, Nginx Proxy Manager)
Backend Rust (custom binary) 0.1.0 (archipelago)
Frontend Vue 3 + TypeScript + Vite 7 SPA at /
Container Runtime Podman (rootless)
Lightning LND auto-generated TLS cert
Bitcoin Bitcoin Core/Knots mainnet, block 941146
Monitoring Grafana 10.2.0
Uptime Uptime Kuma (port 3001)
Proxy Manager Nginx Proxy Manager 2.14.0
SSH OpenSSH 9.2p1 Debian 2+deb12u7
TLS Self-signed cert CN=archipelago.local, expires 2027-02-17

Open Ports and Services

Port Service Protocol Direct Access
22 SSH (OpenSSH 9.2p1) TCP Yes
80 Nginx (main reverse proxy) HTTP Yes
81 Nginx Proxy Manager (OpenResty) HTTP Yes
443 Nginx (HTTPS, self-signed) HTTPS Yes
3000 Grafana HTTP Yes
3001 Uptime Kuma HTTP Yes
5678 Archipelago Rust backend HTTP Yes (behind nginx)
7777 IndeedHub (nginx 1.29.6) HTTP Yes
8080 LND REST API HTTPS (TLS) Yes
8334 Bitcoin UI (custom nginx) HTTP Inferred from config
8083 FileBrowser HTTP Inferred from config
8888 SearXNG HTTP Inferred from config
9000 Portainer HTTP Yes
11434 Ollama (local LLM) HTTP Inferred from config
3141/3142 Claude OAuth Proxy HTTP Internal

Subdomains Discovered

  • archipelago.local (self-signed cert SAN)
  • No external subdomains (internal LAN deployment)

2. Complete Endpoint Map

2.1 Nginx HTTP Routes (Port 80/443)

Unauthenticated Endpoints

Method Path Backend Source Auth Enforced
GET /health 127.0.0.1:5678 nginx config line ~45 None
GET /electrs-status 127.0.0.1:5678 nginx config, CORS * None
GET /lnd-connect-info 127.0.0.1:5678 nginx config, CORS * None
GET /content 127.0.0.1:5678 nginx config None (P2P)
GET /content/* 127.0.0.1:5678 nginx config None (P2P)
POST /dwn 127.0.0.1:5678 nginx config None (P2P)
GET /dwn/health 127.0.0.1:5678 nginx config None
POST /archipelago/node-message 127.0.0.1:5678 nginx config None (P2P)
GET / Static SPA nginx config None
GET /assets/* Static files nginx config None
GET /nostr-provider.js Static file nginx config None
Method Path Backend Source Notes
POST /rpc/v1 127.0.0.1:5678 nginx config Rate limited: 20r/s, burst 40. 1MB body. 600s timeout
WS /ws/db 127.0.0.1:5678 nginx config WebSocket upgrade. 86400s timeout
GET /api/container/logs* 127.0.0.1:5678 handler.rs Query: ?app_id=&lines=
GET /proxy/lnd/* 127.0.0.1:8080 handler.rs Proxies to LND REST API
GET /aiui/api/claude/* 127.0.0.1:3141 nginx config Streaming. 300s timeout
GET /aiui/api/ollama/* 127.0.0.1:11434 nginx config Streaming. 300s timeout
GET /aiui/api/openrouter/* openrouter.ai nginx config External API proxy
GET /aiui/api/web-search 127.0.0.1:8888 nginx config SearXNG. 30s timeout

App Proxy Routes (/app/*)

All inject nostr-provider.js, strip X-Frame-Options, re-apply SAMEORIGIN.

Path Backend Port Timeout Special
/app/bitcoin-ui/ 8334 5s
/app/electrumx/ 50002 5s
/app/grafana/ 3000 5s
/app/uptime-kuma/ 3001 5s
/app/searxng/ 8888 5s
/app/portainer/ 9000 5s
/app/filebrowser/ 8083 5s 10GB upload limit; path traversal check
/app/jellyfin/ 8096 5s
/app/photoprism/ 2342 5s
/app/onlyoffice/ 9980 5s
/app/tailscale/ 8240 5s
/app/ollama/ 11434 5s
/app/nginx-proxy-manager/ 81 5s
/app/lnd/ 8081 300s Long timeout
/app/mempool/ 4080 300s Long timeout
/app/fedimint/ 8175 300s Long timeout
/app/fedimint-gateway/ 8176 300s Long timeout
/app/nextcloud/ 8085 300s
/app/vaultwarden/ 8082 300s Password manager
/app/immich/ 2283 300s
/app/penpot/ 9001 300s
/app/indeedhub/ 7777 5s Complex URL rewriting, WebSocket

External Site Proxies (Separate Ports)

Port Upstream Purpose
8901 botfights.net Nostr proxy
8902 484.kitchen Nostr proxy
8903 present.l484.com Nostr proxy

2.2 Rust Backend RPC Methods (POST /rpc/v1)

Protocol: JSON-RPC 2.0 Content-Type: application/json Auth: Session cookie (except where noted)

Unauthenticated RPC Methods (No Session Required)

Method Parameters Returns Source
auth.login password Sets session cookie api/rpc/auth.rs
auth.login.totp token, code Session api/rpc/auth.rs
auth.login.backup token, backup_code Session api/rpc/auth.rs
auth.isOnboardingComplete boolean api/rpc/auth.rs
auth.isSetup boolean api/rpc/auth.rs
backup.restore-identity backup_file, password {did} api/rpc/mod.rs
federation.get-state {state} P2P inter-node
federation.peer-joined peer_did, address P2P inter-node
federation.peer-address-changed peer_did, new_address P2P inter-node

Authenticated RPC Methods (150+ total, grouped by domain)

Authentication & Session (12 methods)
Method Parameters Purpose
auth.logout Invalidate session
auth.changePassword currentPassword, newPassword Change password
auth.onboardingComplete Mark onboarding done
auth.resetOnboarding Reset onboarding
auth.totp.setup.begin Get TOTP secret + QR
auth.totp.setup.confirm code Confirm TOTP setup
auth.totp.disable password Disable 2FA
auth.totp.status Check 2FA enabled
Container Management (10 methods)
Method Parameters Purpose
container-install image, name Install container
container-start app_id Start container
container-stop app_id Stop container
container-remove app_id Remove container
container-list List all containers
container-status app_id Container status
container-logs app_id, lines Container logs
container-health app_id Container health
bundled-app-start app_id Start bundled app
bundled-app-stop app_id Stop bundled app
Package Management (5 methods)
Method Parameters Purpose
package.install package_id, version Install from marketplace
package.start package_id Start package
package.stop package_id Stop package
package.restart package_id Restart package
package.uninstall package_id Uninstall
Bitcoin & Lightning (15 methods)
Method Parameters Purpose
bitcoin.getinfo Bitcoin Core info
lnd.getinfo LND node info
lnd.listchannels List channels
lnd.openchannel peer_pubkey, local_funding_amount Open channel
lnd.closechannel channel_point Close channel
lnd.newaddress Generate address
lnd.sendcoins address, amount_sats Send BTC
lnd.createinvoice amount_sats, memo Create invoice
lnd.payinvoice payment_request Pay invoice
lnd.create-psbt inputs, outputs Create PSBT
lnd.finalize-psbt psbt Broadcast PSBT
lnd.create-raw-tx inputs, outputs Raw transaction
lnd.gettransactions Wallet history
lnd.connect-info LND connection string
Identity & Crypto (30+ methods)

Covers: identity CRUD, DID resolution, Nostr key operations, NIP-04/NIP-44 encryption/decryption, verifiable credentials (issue/verify/revoke), presentations, DHT DID, NIP-05 names, key export.

Node & P2P (15+ methods)

Covers: node DID, challenge signing, backup creation, Tor address, Nostr publishing, peer management, message sending, peer discovery.

Federation (10 methods)

Covers: invite generation, joining, node listing, node removal, trust scoring, state sync, app deployment.

Mesh Networking (20+ methods)

Covers: status, peers, messaging, broadcast, LoRa configuration, invoice relay, GPS coordinates, emergency alerts, deadman switch, Bitcoin tx relay, Lightning relay, block headers, X3DH prekey rotation.

Ecash Wallet (6 methods)

Covers: balance, mint, melt, send, receive, transaction history.

Content Sharing (7 methods)

Covers: list own content, add/remove files, pricing, availability, browse/download from peers.

DWN (7 methods)

Covers: status, sync, protocol management, message query/write.

Network & Infrastructure (20+ methods)

Covers: network interfaces, WiFi scan/config, Ethernet config, DNS config, UPnP router discovery/forwarding, Tor service management (list/create/delete/rotate), Nostr relay management, VPN config.

System Management (15+ methods)

Covers: system stats, processes, temperature, USB detection, disk status/cleanup, factory reset, monitoring (current/history/alerts), updates (check/download/apply/rollback), backup (create/list/verify/restore/USB/S3).

Other (10+ methods)

Covers: server naming, analytics opt-in/out, webhook config, security secret rotation, marketplace discovery/publishing.

2.3 Direct HTTP Endpoints (Backend)

Method Path Auth Source
GET /health None handler.rs:~120
GET /electrs-status None handler.rs
GET /lnd-connect-info None handler.rs
GET /content None handler.rs
GET /content/* None handler.rs (Range header support)
POST /archipelago/node-message P2P validation handler.rs
GET /dwn/health None handler.rs
POST /dwn None (P2P) handler.rs
WS /ws/db Session cookie handler.rs:514-625
GET /api/container/logs* Session handler.rs
GET /proxy/lnd/* Session handler.rs

2.4 Direct Port Services

Port Service Own Auth Notes
3000 Grafana Session/Basic Login page directly accessible
3001 Uptime Kuma Session Redirects to /dashboard
81 Nginx Proxy Manager Session Login page directly accessible
7777 IndeedHub Nostr NIP-07 Full app accessible
8080 LND REST TLS + Macaroon Requires valid macaroon header
8334 Bitcoin UI None/Basic Auth on /bitcoin-rpc/ Hardcoded creds in nginx config
9000 Portainer Session Redirects to timeout (possibly unconfigured)

2.5 WebSocket Endpoints

Path Auth Protocol Features
/ws/db Session cookie JSON Patch 30s ping, 5min inactivity timeout, state streaming
/app/indeedhub/ws/ Nostr WebSocket 86400s timeout

3. Attack Surface Map

3.1 Input Vectors

Vector Endpoint(s) Input Type Validation
Password login auth.login JSON body (password) Bcrypt comparison, rate limited (5/min)
TOTP code auth.login.totp JSON body (code) Constant-time comparison, 5 attempts
RPC method dispatch /rpc/v1 JSON body (method, params) Switch on method name, typed params
Container image install container-install JSON body (image) Image name passed to Podman
File upload /app/filebrowser/ Multipart/file body 10GB limit, path traversal check
P2P messages /archipelago/node-message JSON body Source validation (Tor onion)
DWN writes /dwn JSON body Protocol validation
Content download /content/* URL path + Range header Path-based content ID lookup
Bitcoin transactions lnd.sendcoins, lnd.payinvoice JSON body (address, amount) Address validation
DNS configuration network.configure-dns JSON body (servers) Server address validation
WiFi config network.configure-wifi JSON body (ssid, password)
Package install package.install JSON body (id, version, url) marketplace URL fetched
Federation join federation.join JSON body (invite code) Code validation
Webhook config webhook.configure JSON body (url, events) URL stored, callbacks sent
Bitcoin RPC proxy 8334:/bitcoin-rpc/ JSON body (method, params) Basic Auth (hardcoded)
Factory reset system.factory-reset JSON body (confirm: true) Auth + confirm flag

3.2 Authentication Mechanisms

Mechanism Used By Strength
Password + bcrypt (cost 12) Main login Strong (rate limited)
TOTP (RFC 6238) 2FA Strong (constant-time, replay-protected)
Session cookie (256-bit random) All authenticated endpoints Strong (HttpOnly, SameSite=Strict)
Remember-me (HMAC-SHA256) Session persistence Medium (derived from machine-id)
CSRF token State-changing operations Present but enforcement unclear
Macaroon (LND) LND REST API Strong (but exposed via endpoint)
Basic Auth (hardcoded) Bitcoin UI RPC proxy Weak (hardcoded in config)
Default creds (Grafana) Grafana admin Weak (admin:admin works)
No auth 8 HTTP endpoints, 6 RPC methods N/A

3.3 Data Flow

User Browser
  │
  ├─[Session Cookie]──→ Nginx (80/443)
  │                       ├──→ /rpc/v1 ──→ Rust Backend (5678) ──→ Podman containers
  │                       ├──→ /ws/db ──→ WebSocket state stream
  │                       ├──→ /app/* ──→ Container UIs (iframes)
  │                       └──→ /aiui/* ──→ Claude Proxy (3141) ──→ Anthropic API
  │
  ├─[No Auth]──→ /health, /electrs-status, /lnd-connect-info, /content, /dwn
  │
  ├─[Direct Port]──→ Grafana:3000 (admin:admin)
  ├─[Direct Port]──→ NPM:81 (session)
  ├─[Direct Port]──→ LND:8080 (TLS + macaroon)
  └─[Direct Port]──→ Bitcoin UI:8334 (Basic Auth hardcoded)

4. Interesting Findings

CRITICAL

4.1 Unauthenticated LND Admin Macaroon Exposure

  • Endpoint: GET /lnd-connect-info (no auth required)
  • Confirmed: Returns full admin macaroon (base64url), TLS certificate, gRPC port (10009), REST port (8080)
  • Macaroon permissions: address:rw, info:rw, invoices:rw, macaroon:generate/rw, message:rw, offchain:rw, onchain:rw, peers:rw, signer:generate/read
  • Impact: Any host on the LAN can retrieve the admin macaroon and gain full control of the Lightning node — send all funds, open/close channels, create invoices, sign messages. This is the equivalent of exposing a root password to the Bitcoin wallet.
  • CORS: Access-Control-Allow-Origin: * (any origin)

Proof:

curl -sk http://192.168.1.228/lnd-connect-info
# Returns: {"cert_base64url":"MIIC...","grpc_port":10009,"macaroon_base64url":"AgED...","rest_port":8080}

4.2 Grafana Default Credentials (admin:admin)

  • Endpoint: http://192.168.1.228:3000
  • Confirmed: admin:admin returns full organization data
  • Version: Grafana 10.2.0 (commit 895fbafb7a)
  • Impact: Full access to monitoring dashboards, data sources, alert rules. Can potentially access connected databases, execute queries, and pivot to other services via configured data sources.

Proof:

curl -sk http://192.168.1.228:3000/api/org -u admin:admin
# Returns: {"id":1,"name":"Main Org.","address":{...}}

4.3 Bitcoin RPC Full Access via Hardcoded Credentials

  • Endpoint: POST http://192.168.1.228:8334/bitcoin-rpc/
  • Credentials: archipelago:archipelago123 (hardcoded in docker/bitcoin-ui/nginx.conf)
  • Confirmed: Returns full getblockchaininfo — mainnet, block 941146, 828GB on disk
  • Impact: Full Bitcoin Core RPC access. Depending on wallet configuration, could call sendtoaddress, dumpprivkey, listunspent, or any other RPC method. Mainnet node with real funds.

Proof:

curl -sk -X POST http://192.168.1.228:8334/bitcoin-rpc/ \
  -u archipelago:archipelago123 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"1.0","id":"test","method":"getblockchaininfo","params":[]}'
# Returns: {"result":{"chain":"main","blocks":941146,...},"error":null}

HIGH

4.4 Unauthenticated Content Catalog Exposure

  • Endpoint: GET /content
  • Confirmed: Returns complete file catalog — filenames, sizes, MIME types, UUIDs
  • Data leaked: Personal music files with full paths (/Music/1 - Govcucks.wav, etc.)
  • Impact: Information disclosure of personal files shared via P2P. File UUIDs could be used to download content via /content/{id}.

4.5 Nginx Proxy Manager Accessible on LAN

  • Endpoint: http://192.168.1.228:81
  • API Status: {"status":"OK","setup":false,"version":{"major":2,"minor":14,"revision":0}}
  • setup: false — Unclear if this means initial setup hasn't completed (would allow admin takeover) or refers to some other state
  • Impact: NPM controls reverse proxy routing for all services. Compromise = ability to redirect traffic, intercept credentials, or add malicious proxy rules.

MEDIUM

4.6 Version and Service Information Disclosure

Source Information Exposed
HTTP Server header nginx/1.22.1
Port 81 Server header openresty
Port 3000 /api/health Grafana 10.2.0, commit hash, database status
Port 81 /api/ NPM version 2.14.0
Port 8080 TLS cert lnd autogenerated cert, internal IPs, Tailscale IPs
Port 443 TLS cert SANs include: 192.168.1.228, 192.168.1.198, 10.0.0.1, archipelago.local
SSH banner OpenSSH 9.2p1 Debian 2+deb12u7, ECDSA + ED25519 host keys
/electrs-status Blockchain sync: 99%, index size 124.8GB, network height
/dwn/health 1027 messages, 10 protocols, 551KB storage
auth.isOnboardingComplete Node setup state (returns true)
Error responses "Password Incorrect" (confirms account exists)

4.7 LND TLS Certificate Leaks Internal Network Topology

The LND auto-generated TLS cert (port 8080) exposes SANs including:

  • Internal IPs: 192.168.1.228, 10.88.0.1 (Podman bridge)
  • Tailscale IPs: 2A00:23C5:E31:A001:572F:29BF:5A00:2D46 (IPv6)
  • Link-local IPs: 5 different FE80:: addresses (reveals all network interfaces)

4.8 CSP Allows unsafe-inline

script-src 'self' 'unsafe-inline'
style-src 'self' 'unsafe-inline'

While necessary for the Vue SPA, unsafe-inline for scripts significantly weakens XSS protection. If any injection point exists, inline script execution is possible.

4.9 connect-src Allows Broad Connections

connect-src 'self' ws: wss: http://192.168.1.228:* https:

Allows JavaScript to connect to ANY port on the host and ANY HTTPS endpoint. An XSS payload could exfiltrate data to external servers or interact with any local service port.

4.10 DWN Endpoint Accepts Unauthenticated Queries

  • Endpoint: POST /dwn
  • Confirmed: Accepts JSON queries and returns results
  • Impact: Remote parties can query DWN records. While designed for P2P, the lack of access control means any network-adjacent attacker can enumerate stored data.

LOW / INFORMATIONAL

4.11 Login Rate Limiting Works

Rate limiting triggers after 4 failed attempts (returns HTTP 429). Effective against brute force. However, the limit is per-IP, not per-account — an attacker with multiple IPs could parallelize attempts.

4.12 CORS Properly Restricts Origins

CORS preflight for Origin: http://evil.com returns no Access-Control-Allow-Origin header. Only configured origins (http://192.168.1.228, http://localhost:8100) are allowed. WebSocket also returns 401 without valid session.

4.13 Path Traversal Mitigated

/content/../../../etc/passwd returns the SPA index.html (nginx catches it). URL-encoded traversal (%2f..%2f) returns 400 Bad Request. FileBrowser has explicit .. regex checks in nginx config.

4.14 Git/Env Files Not Exposed

/.git/HEAD and /.env both return the SPA index.html (Vue Router catch-all). No source code or credential leakage.

4.15 Security Headers Present

All security headers are properly set: HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, X-DNS-Prefetch-Control. This is above average for self-hosted applications.


5. Priority Targets

Rank 1: LND Admin Macaroon via /lnd-connect-info (CRITICAL)

  • What: Unauthenticated HTTP endpoint returns full admin macaroon for LND Lightning node
  • Why it's critical: Grants complete control over Lightning funds — send payments, drain channels, create invoices. No authentication required. Accessible to any device on the LAN.
  • Category: Broken Access Control (OWASP A01:2021)
  • Remediation: Require session authentication on /lnd-connect-info. Use read-only macaroon for status checks; only expose admin macaroon via authenticated RPC.

Rank 2: Bitcoin RPC via Hardcoded Credentials (CRITICAL)

  • What: Port 8334 proxies Bitcoin Core RPC with hardcoded Basic Auth archipelago:archipelago123
  • Why it's critical: Mainnet Bitcoin node. If wallet is loaded, attacker can send transactions, export private keys, or manipulate the mempool. Credentials are in version-controlled nginx config.
  • Category: Security Misconfiguration (OWASP A05:2021), Hardcoded Credentials
  • Remediation: Remove hardcoded credentials from nginx config. Proxy Bitcoin RPC through the authenticated Rust backend only. Restrict port 8334 to localhost.

Rank 3: Grafana Default Credentials (HIGH)

  • What: Grafana on port 3000 accepts admin:admin
  • Why it's critical: Full admin access to monitoring infrastructure. Grafana can query connected data sources (Prometheus, InfluxDB), potentially exposing system metrics, logs, and providing a pivot point. Version 10.2.0 may have known CVEs.
  • Category: Identification and Authentication Failures (OWASP A07:2021)
  • Remediation: Change default password. Restrict Grafana to localhost access only (proxy through authenticated nginx). Consider enabling Grafana's built-in auth proxy mode.

Rank 4: Unauthenticated Content Catalog (HIGH)

  • What: GET /content exposes personal files (names, sizes, UUIDs) without authentication
  • Why it's concerning: Reveals personal data. UUIDs may allow direct file download via /content/{id}. Designed for P2P but accessible from any LAN host.
  • Category: Broken Access Control (OWASP A01:2021)
  • Remediation: Require peer authentication (DID signature verification) for content catalog access, not just content downloads.

Rank 5: Nginx Proxy Manager Direct Access (HIGH)

  • What: Port 81 serves NPM admin interface directly on LAN with setup: false status
  • Why it's concerning: NPM controls all reverse proxy rules. If the "setup" state allows initial admin creation by anyone, an attacker could take over routing. Even with auth, direct port access bypasses the main nginx security headers.
  • Category: Security Misconfiguration (OWASP A05:2021)
  • Remediation: Restrict port 81 to localhost. Only expose NPM through the authenticated /app/nginx-proxy-manager/ proxy path.

Rank 6: Service Ports Directly Accessible on LAN (MEDIUM)

  • What: Ports 3000, 3001, 7777, 8080, 8334, 9000 are directly accessible, bypassing the main nginx proxy and its security headers/CSP/CORS
  • Why it's concerning: Each service has its own (potentially weaker) authentication. Direct access bypasses rate limiting, security headers, and session validation at the nginx layer.
  • Category: Security Misconfiguration (OWASP A05:2021)
  • Remediation: Bind container ports to 127.0.0.1 or Podman bridge network only. All external access should flow through the nginx proxy on port 80/443.

Rank 7: RPC Input Injection Surface (MEDIUM)

  • What: 150+ RPC methods accept JSON parameters that control container operations, system commands, network config, file operations, and Bitcoin transactions
  • Why it's concerning: Methods like container-install (image name → Podman), network.configure-dns (DNS servers), webhook.configure (arbitrary URL callbacks), package.install (marketplace URL fetch) all accept user-controlled strings that interact with system commands or external services.
  • Category: Injection (OWASP A03:2021), SSRF (OWASP A10:2021)
  • Remediation: Audit each method for proper input sanitization. Especially: container image names (prevent registry confusion), webhook URLs (prevent SSRF), DNS servers (prevent DNS rebinding), marketplace URLs (prevent SSRF).

Rank 8: CSP unsafe-inline + Broad connect-src (MEDIUM)

  • What: CSP allows inline scripts and connections to any port on the host or any HTTPS endpoint
  • Why it's concerning: If any XSS vector exists (e.g., in app iframe content, reflected parameters, or injected HTML), the attacker can execute arbitrary JavaScript and exfiltrate data to external servers or interact with all local services.
  • Category: XSS / Security Misconfiguration (OWASP A03/A05:2021)
  • Remediation: Migrate to nonce-based CSP. Restrict connect-src to specific required ports/domains.

Appendix: Security Headers (Full)

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-DNS-Prefetch-Control: off
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';
  style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:;
  font-src 'self' data:; connect-src 'self' ws: wss: http://192.168.1.228:* https:;
  frame-src 'self' http://192.168.1.228:* https:; frame-ancestors 'self';
  base-uri 'self'; form-action 'self';
Server: nginx/1.22.1

Appendix: Rate Limiting Configuration

Layer Target Rate Burst
Nginx /rpc/ 20 req/s 40
Backend auth.login 5 per 60s per IP
Backend Financial ops (send, pay) 5-10 per 300s
Backend Auth changes (password, TOTP) 3 per 300s
Backend Container ops 5 per 300s
Backend Federation join 5 per 60s

Appendix: Authentication Summary

What's Good What Needs Work
Bcrypt cost 12 for passwords /lnd-connect-info unauthenticated
Argon2id for TOTP key derivation Bitcoin RPC hardcoded creds
ChaCha20-Poly1305 for TOTP secret encryption Grafana default admin:admin
256-bit random session tokens Service ports directly accessible
HttpOnly + SameSite=Strict cookies CSP unsafe-inline
Rate limiting on login (5/min) NPM port 81 open on LAN
CORS origin validation connect-src too permissive
Session rotation on password change Initial password only 8 chars
TOTP replay protection Error messages confirm account existence
AES-256-GCM secrets at rest Rate limiter enforcement unclear for some methods