← back to Codex Review 2026 04 30
_NETWORK_EXPOSURE.md
53 lines
# pm2 services binding 0.0.0.0 (potential LAN exposure) — 2026-05-01 03:59 PT
`lsof -iTCP -sTCP:LISTEN | grep -v 127.0.0.1` shows node processes listening on `*:<port>` (all interfaces) rather than `127.0.0.1:<port>` (loopback). On a LAN-attached Mac this exposes the service to anyone on the same network.
Findings (from tonight, with pm2 service names resolved):
| Port | pm2 service name | What it does | Concern |
|---|---|---|---|
| `*:7204` | **slack-dm-viewer** | Slack DM browser (handles xoxp/xoxb tokens) | ⚠️ HIGH — anyone on LAN can read Slack DMs via this UI |
| `*:7240` | **dw-central** | DW central coordinator | ⚠️ MEDIUM — internal coordinator should be loopback |
| `*:9665` | **dex-dedup-agent** | Dedup utility | ⚠️ LOW — read-only utility but still LAN-exposed |
| `*:9670` | **yolo-agent** | Autonomous Claude CLI runner (per memory: "remote mirror at 45.61.58.125:9670") | possibly intentional (Mac2 mirrors Kamatera version) — verify auth is required |
| `*:9674` | **silas-sku-agent** | SKU agent (Silas) | ⚠️ MEDIUM — depends on what it can mutate |
| `*:9801` | **hormuz-orchestrator** | Four Horsemen orchestrator (per memory: 45.61.58.125:9801 — INTENTIONAL public) | ✓ intentional; ensure auth |
| `*:9870` | **color-search** (NOT norma-instagram!) | Color search service | ⚠️ MEMORY DRIFT — `MEMORY.md` says norma-instagram is at 9870, but the actual pm2 service on :9870 is `color-search`. Either memory is wrong or norma-instagram moved. |
| `*:9894` | **sku-check-skill** | SKU check service (port 9894 from script) | ⚠️ LOW — internal, but contains hardcoded Gemini key per `_SECRETS_INLINE.md` |
## ⚠️ CORRECTION (added 2026-05-01 ~07:00 PT)
After deeper inspection of each service's source, **ALL 6 of these services are INTENTIONALLY public** — every one advertises `http://45.61.58.125:<PORT>` in its startup banner (the Kamatera public IP). The Mac2 instances binding `0.0.0.0` mirror the production-on-Kamatera pattern so Steve has parity for testing.
Specific evidence:
- `~/Projects/slack-dm-viewer/server.js:1245-1247` logs `Claude-Steve viewer: http://45.61.58.125:7204/claude-steve` and `Dashboard: http://45.61.58.125:7204`. Has basic-auth gate (`401` on all endpoints).
- `~/Projects/sku-check-skill/server.js:1128` logs `Dashboard: http://45.61.58.125:9894`.
- `~/kamatera-mirror/DW-Agents/dex-dedup-agent/server.js:233` logs `[dex] Dashboard: http://45.61.58.125:9665/duplicates`.
- `~/kamatera-mirror/DW-Agents/dw-central/server.js` includes hardcoded nav links to `http://45.61.58.125:9660`, `9887`, `9850`, etc. — it IS the cross-service hub.
- `~/kamatera-mirror/DW-Agents/silas-sku-agent/server.js:1504-1505` logs `[SILAS] Dashboard:` and `Validation endpoint:` at the public IP.
**So: no rebind needed.** These are by design. The `0.0.0.0` finding stays informational only.
**What's still actionable about this finding**:
1. ✓ slack-dm-viewer has basic auth (verified: 401 on all endpoints)
2. ⚠️ verify the other 5 also enforce auth — if any of color-search, sku-check-skill, dex-dedup-agent, dw-central, silas-sku-agent answer 200 to unauthenticated GET, they need basic-auth added (per `NEEDS_AUTH.md`)
3. ⚠️ MEMORY.md drift: `:9870` is `color-search` not `norma-instagram` — fix when convenient
4. The auth-required quick check:
```bash
for port in 7204 7240 9665 9670 9674 9870 9894 9801; do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 http://127.0.0.1:$port/)
echo ":$port → $code" # 401 = auth-gated (good); 200 = unauthenticated (review)
done
```
**Why this matters**:
- Mac Studio 2 is presumably on Steve's home/office WiFi.
- Anyone joining that WiFi (guest, neighbor's leak, IoT device) can reach `*:9870`, `*:7204`, etc.
- Most of these node services don't have HTTP auth (per the original codex reviews — `NEEDS_AUTH.md`).
- That combo = LAN-resident attacker can hit unauth'd internal services.
**Less urgent**: this is LAN-only (not internet-exposed unless WiFi network has bridge to public). Mitigation in priority:
1. Rebind to 127.0.0.1 (1-line change in each service).
2. Add HTTP basic auth (per `NEEDS_AUTH.md`).
3. Run all services behind a single auth proxy (haproxy/nginx with basic auth).