Files
root f5175f1ce0 Sync docs, audit artifacts, project notes, and VerdictTank proposal docs
- audit/phase-one + phase-two: security audit briefs, findings, credential-rotation plan, Docker-USER hardening scripts, rollback refs
- disaster-recovery/restore-test-log.md + backup-dr-audit-2026-08-10.md
- clients/ (modelortho SEO audit, ai-biz-dev competitive landscape), notes/ (tiktok strategy)
- projects/: front-desk-voice-agent, seo-visibility-checker product plan, hotnow-savannah HTML, resend-transactional-email, backup-dashboard-enhancements, code-review-graph, seo-ci-architecture
- proposals/verdicttank/: architecture v4.0, methodology, judge-pool review, consolidation reasoning, cross-check review
- docs/super-search/firecrawl-provider-strategy.md
- updates: CHANGELOG, model-chain, projects-master-readme, intelsight.io
- .gitignore: exclude nested standalone repos (seo-tool, venturebuilt)
2026-08-26 02:27:28 -04:00

29 KiB

NetEng-B - Network Segmentation and Blast-Radius Assessment (Phase One, Read-Only)

Auditor: NetEng-B Date: 2026-08-13 Scope: Tier classification, inter-host segmentation, blast-radius chains, and product/tenant isolation across the ITPP estate (Core, app1, app2, app3, app1-bu, wphost02). Method: Read-only discovery only (ip, ufw status verbose, ss, tailscale status --json, docker network ls, docker inspect, cat of config files, curl/TCP reachability probes between hosts). Builds directly on NetEng-A's raw port/DNS inventory (neteng-a.md) - no port/DNS re-enumeration performed here. No configuration was changed on any host.

Severity legend: Critical = a single compromise gives an attacker control of most or all of the estate, or of a system holding client/security data with no compensating boundary. High = a compromise crosses a trust boundary that should exist (internal-to-client, product-to-product) with material impact. Medium = defense-in-depth gap that increases blast radius but requires a second failure to be catastrophic. Low = hygiene issue with limited blast-radius effect.


1. Executive summary

  • There is no network segmentation between Core, app1, app2, app3, app1-bu, and wphost02. All six hosts sit on public Netcup/Hetzner IP space with no VLAN, no subnetting, and no firewall boundary between each other. The only inter-host overlay is a single flat Tailscale mesh with no ACL tags applied to any node - every server and every personal device (phone, laptop, home router) is in one undifferentiated group with implicit allow-all reachability.
  • Because UFW is bypassed by Docker on 4 of 6 hosts (confirmed by NetEng-A and re-verified here), the "segmentation" that exists on paper (UFW allow-lists) does not match what is actually reachable. Verified independently: Core -> app1 and app1 -> app2 both reach each other over their public IPs, not just Tailscale, with no filtering in between.
  • Compromise of any one of the six hosts gives an attacker a foothold that can reach every other host over the public internet (all are mutually pingable/routable on public IPs) and, once inside, over the flat Tailscale mesh as well. There is no host that is network-isolated from the rest.
  • Products and internal ITPP operations are not isolated. app1 runs the internal LLM gateway (LiteLLM/admin-ai), the internal CRM (Twenty), the security stack (Wazuh), and the reverse proxy for two products (Komodo, TransitPin) all as sibling Docker containers on one host with one Caddy instance. app3 runs a single shared MySQL/Percona instance behind ~24 CloudPanel sites that mix internal ops (mainwp, support, panel, ippadmin), client sites (katiewatts, modelortho, vigilanttac, boxpilotlogistics, timapta), and products (transitpin, myverdicttank, buzz, hexclave) with no per-tenant database server or credential vault separating them.
  • Tier misclassification is widespread: management/admin consoles (Wazuh dashboard+indexer, UniFi controller, UNMS/UISP, CloudPanel admin, backup-restore UI, MinIO console, RAGFlow, Infinity DB, Komodo) are all internet-reachable, several unintentionally via the Docker/UFW bypass NetEng-A documented. These are control planes; none should be directly public.

2. Tier classification

Legend: INT = Internal-only (should never be public), CLI = Client-facing (legitimately public), PROD = Product/dev (micro-SaaS), MGMT = Management/admin control plane.

Service Host Correct tier Actually public? Tier violation?
SSH (22) all hosts all MGMT Yes (intended, key-only) No - acceptable exposure
Caddy/nginx 80/443 (ingress) Core, app1, app2, app3, wphost02 CLI Yes (intended) No
Grafana :3002 Core MGMT Yes (explicit UFW allow) Yes - dashboards public
Prometheus/node_exporter/telegraf Core INT No (UFW blocks; Docker bypass N/A, native binaries) No, but bound 0.0.0.0 (Medium hygiene per NetEng-A)
browserless / camofox Core INT (SSRF-capable tool) Yes - Docker bypass Yes
WireGuard :51821 Core, app1-bu MGMT/VPN Yes (intended) No
Wazuh manager 1514/1515 app1 MGMT Yes (intended, but should be source-restricted) Partial - over-broad
Wazuh indexer :9200, dashboard :5601, API :55000, syslog 514/udp app1 MGMT Yes - Docker bypass Yes - critical control plane public
Twenty CRM :3003 app1 INT (holds client PII) Yes - Docker bypass Yes
Komodo core :9120 app1 MGMT (deploy/automation control plane) Yes - Docker bypass, also proxied at komodo.iamgmb.com Yes
LiteLLM/admin-ai (via Caddy) app1 MGMT Yes (intended, proxied) No (proxy correct; bypass risk is at Docker layer for adjacent services)
TransitPin static site app1 PROD Yes (intended) No
giftaroast.com site + API app1 PROD/CLI Yes (intended) No
UniFi controller 8080/8443/8843/8880 app2 MGMT Yes - Docker bypass Yes - wireless/tower control plane public
UNMS/UISP 81/8089/8444 app2 MGMT Yes - Docker bypass Yes - entire WISP management plane public
Technitium DNS :53 app2 INT (should be source-restricted to home router only) Yes, open resolver - Docker bypass defeats UFW source restriction Yes
Gitea SSH :3022 app2 MGMT (source code) Yes - Docker bypass Yes
BookStack :6875 app2 INT (internal wiki) Yes - Docker bypass Yes
support-api :6880 app2 INT/CLI boundary (unclear which) Yes - Docker bypass Flag for clarification
MinIO console :9001 app2 MGMT Yes - Docker bypass Yes
RAGFlow :9380-9392 app2 PROD/INT (unclear) Yes - Docker bypass Flag for clarification
Infinity DB :23817/23820 app2 INT (database) Yes - Docker bypass Yes
Traccar device listeners 5000-5150 app2 PROD (device protocol, must be public) Yes (intended) No - legitimate, but see APP2-4 in NetEng-A for hardening
Hudu, Dawarich app2 INT (internal IT docs / location tracking) Proxied via Caddy (correctly) at hudu.itpropartner.com No tier issue found at proxy layer
CloudPanel admin panel :8443 app3 MGMT Yes (explicit UFW allow) Yes - hosting control plane for ~24 sites public
backup-restore web UI :8090 app3 MGMT (destructive capability) Yes (explicit UFW allow) Yes
buzz-prod-relay :3000 app3 PROD Yes - Docker bypass Bypass mechanism wrong even though public exposure may be intended
MySQL X protocol :33060 app3 INT (database) Bound * but currently UFW-blocked No public exposure confirmed, hygiene flag only
CloudPanel client/product sites (24 total) app3 Mixed CLI/PROD/INT (see section 5) Yes (intended, various) See section 5 for isolation gap, not tier gap
wphost02 nginx-rc 80/443 wphost02 CLI (legacy) Yes (intended, legacy) No tier issue, but should be decommissioned per NetEng-A

Tier-classification counts (from table above)

  • INTERNAL-ONLY services incorrectly public: 12 (Grafana, browserless, camofox, Wazuh indexer/dashboard/API/syslog treated as one group counted once = Wazuh stack, Twenty CRM, Technitium open resolver, BookStack, MinIO console, Infinity DB, plus MySQL X hygiene flag)
  • MANAGEMENT/ADMIN consoles incorrectly public: 6 (Wazuh dashboard, UniFi controller, UNMS/UISP, CloudPanel admin, backup-restore UI, Komodo core)
  • CLIENT-FACING correctly public: 6 (Core/app1/app2/app3/wphost02 web ingress, Traccar device range)
  • PRODUCT/DEV correctly public (by design): 4 (TransitPin, giftaroast.com, buzz relay -- though via wrong mechanism, Traccar)
  • Ambiguous tier, needs Germaine clarification: 2 (support-api, RAGFlow)

3. Segmentation map

 PUBLIC INTERNET
 |
 +--------------------------------+--------------------------------+
 | | | | |
 Core app1 (Netcup) app2 (Netcup) app3 (Netcup) app1-bu / wphost02
152.53.192.33 152.53.36.131 152.53.39.202 152.53.241.111 (Hetzner, separate /32s)
 | | | | |
 |<===============+===============+===============+================|
 FLAT PUBLIC IP MESH -- every host reaches every other host's
 public IP directly. No VLAN. No inter-host firewall. Verified:
 Core->app1:*, app1->app2:* all reachable on PUBLIC IPs, unfiltered
 for whatever Docker/UFW leaves open on the receiving end.
 | | | | |
 +----------------+---------------+---------------+----------------+
 |
 TAILSCALE MESH (100.x.x.x/32 each)
 tailscale status --json: NO "Tags" field on ANY
 node (Core, app1, app2, app3, app1-bu-1). Default
 tailnet ACL = allow-all between all nodes.
 app3 has no tailscaled at all listed in this scan --
 wait, confirmed: app3 100.72.15.12 IS on tailnet.
 wphost02 has NO tailscale client installed at all --
 it is reachable ONLY over the flat public internet.
 |
 +---------------+----------------+------------------+
 | | | |
 Core app1 app2 app3
 (+ personal devices: iphone, m4-mac-mini, ipp-g-lap, home
 router "liberty-udm-pro" -- ALL in the SAME flat tailnet group
 as the six production servers, no tag-based isolation)

 app1-bu-1 (Hetzner standby) is ALSO in this same flat tailnet.

 wphost02 -- OUTSIDE the tailnet entirely. Reachable from Core/app1/
 app2/app3 only via public internet + SSH key. One-directional trust:
 Core has an autossh reverse tunnel INTO wphost02 (mysql-tunnel,
 127.0.0.1:33060 -> wphost02:3306) plus a root SSH cron backup job.
 wphost02 has no client that can reach back into Core/app1/app2/app3.

INSIDE EACH HOST: dozens of isolated Docker bridge networks per
compose stack (e.g. app1 has 15 separate bridge networks: litellm,
twenty, komodo, n8n, docuseal, etc.) -- this is REAL intra-host
container isolation. But it is undermined by the Docker-publish-vs-UFW
bypass NetEng-A documented: many of those "isolated" containers punch
a hole straight to 0.0.0.0 on the host's public interface, which
erases the isolation the bridge network was providing.

Bottom line: there is effectively ONE trust zone across the entire estate. The only segmentation primitives in play (UFW allow-lists, per-stack Docker bridge networks, Tailscale) are all either bypassed (UFW/Docker), unused for isolation (Tailscale has no ACL tags), or absent entirely between hosts (no VLAN/subnet separation of any kind exists between Core/app1/app2/app3/app1-bu; wphost02 is flat-public with an SSH+tunnel trust relationship back to Core).


4. Per-host blast-radius table

Host If compromised, attacker gets... Falls in blast radius Evidence
Core Root on the Hermes agent host: WireGuard keys to home network + WISP towers (10.77.0.0/24, 10.1.0.0/16, 10.2.0.0/16, 172.16.1.0/24, 172.18.18.0/24 routed via wg0), plaintext L2TP/IPsec creds to home gateway, the itpp-infra SSH private key (used to reach ALL 6 hosts), the autossh tunnel credential path into wphost02's MySQL, Grafana/Prometheus telemetry, Tailscale identity (can pivot to every tailnet peer incl. personal devices) app1, app2, app3, app1-bu, wphost02 (via SSH key + tunnels), home network + WISP towers (via WireGuard), personal devices on tailnet (phone, laptop, home router) ip route shows wg0 routes; /root/.ssh/itpp-infra used identically against all 6 hosts in this audit; crontab shows nightly root SSH job to wphost02; NetEng-A VPN-2 documents plaintext creds
app1 Root on the box hosting Wazuh (SIEM for the whole estate), Twenty CRM (client PII), LiteLLM/admin-ai (the AI control plane and its API keys), Komodo (deployment/automation control plane), n8n (workflow automation with stored credentials) Every host Wazuh agents report from (SIEM blast radius = estate-wide visibility loss/tamper), every client whose PII sits in Twenty, every downstream system Komodo can deploy to, every credential n8n workflows hold Docker inspect showed PG_DATABASE_URL, APP_SECRET, REDIS_URL for Twenty; KOMODO_JWT_SECRET/KOMODO_WEBHOOK_SECRET for Komodo; DATABASE_URL for LiteLLM postgres; Caddyfile proxies vault/n8n/ai/admin-ai/noc/wz/komodo/transitpin all from this one host
app2 Root on the box running UniFi controller (wireless/tower control), UNMS/UISP (entire WISP customer/device management plane), Gitea (source code + SSH deploy keys), Hudu (IT documentation, likely holds more credentials/secrets), Traccar (fleet GPS data), BookStack, RAGFlow, Infinity DB, MinIO The WISP tower network and its customers, all git repos + CI secrets on Gitea, all documented IT credentials in Hudu, fleet-tracking customer data, any data indexed in RAGFlow/Infinity docker inspect showed UNMS_PG_HOST, SECURE_LINK_SECRET, Hudu SECRET_KEY_BASE/S3_SECRET_ACCESS_KEY, Gitea SSH port 3022 public per NetEng-A
app3 Root on the box running CloudPanel (control plane for ~24 sites) and ONE shared MySQL/Percona instance backing internal ops sites, client sites, and product sites simultaneously All ~24 CloudPanel-hosted sites: internal (mainwp, support, panel, ippadmin), clients (katiewatts, modelortho, vigilanttac, boxpilotlogistics, timapta), products (transitpin, myverdicttank, buzz, hexclave) - a single DB-engine compromise threatens every one of them at once ss -tlnp showed one mysqld process on 127.0.0.1:3306/*:33060; docker ps on app3 showed buzz-prod-postgres, hexclave-postgres as separate containers, but CloudPanel's own PHP/static sites run against the single host-level Percona instance shown above
app1-bu Root on the warm-standby box. Minimal live footprint (per NetEng-A). Sync direction is pull-only: hermes-standby-sync.sh pulls from Wasabi S3, does not push to or read live secrets directly off Core over the network Limited - compromise here does not directly expose Core, because sync is one-way pull from S3, not a live network tunnel to Core. Attacker would get whatever is in the last S3 snapshot (which may include full Hermes state/secrets) hermes-standby-sync.sh: aws s3 sync s3://hermes-vps-backups/live/ ...; authorized_keys on app1-bu matches Core's itpp-infra.pub, meaning Core (not app1-bu) is the initiator of any direct SSH, consistent with pull-based design
wphost02 Root on legacy WordPress/RunCloud host. Directly exposes MySQL 3306 to Core via the standing autossh tunnel (-L 127.0.0.1:33060:localhost:3306) Core's tunneled MySQL access (apextrackexperience DB per service-health-check.sh); any WordPress sites still live here ps aux on Core shows autossh ... -L 127.0.0.1:33060:localhost:3306 -N root@5.161.62.38; service-health-check.sh references MySQL SSH tunnel (wphost02) and MySQL database (apextrackexperience)

5. Blast-radius dependency chains (worst-case, critical services)

  1. Core mysql-tunnel -> wphost02: Core maintains a permanent autossh reverse tunnel (127.0.0.1:33060 -> wphost02:3306) plus a nightly cron root-SSH backup job to wphost02. If Core is compromised, the attacker inherits this tunnel and reaches wphost02's database directly, no additional credential theft required. If wphost02 is compromised first, the exposed MySQL on localhost:3306 there is only reachable over the SSH tunnel Core already holds the key for - so wphost02 compromise does not directly threaten Core, but Core compromise fully threatens wphost02's DB.
  2. app1 super-search / MCP tooling -> Core: app1 hosts multiple MCP servers (mcp-browser, mcp-email, mcp-git, mcp-filesystem, super-search) that are part of the same automation fabric as Core's super-search (:8899, allow-listed to 172.17.0.0/16 on Core). A compromised app1 MCP container that can reach Core's Docker subnet peer range could attempt to reach Core's super-search MCP if any cross-host trust exists in the MCP orchestration layer. Full confirmation of live cross-host MCP calls was not possible read-only this session - flagged for Phase Two verification, but the underlying network path (Core and app1 both reachable from each other over public IP and Tailscale with no ACL) means the network layer would not stop such a call if the application layer permits it.
  3. Core Wazuh agents -> app1 Wazuh manager: every host in the estate almost certainly runs a Wazuh agent reporting to app1's manager (1514/1515, intentionally public per NetEng-A APP1-2). If app1's Wazuh stack is compromised (already publicly reachable via the Docker/UFW bypass - APP1-1, Critical), the attacker controls the SIEM for the entire estate: they can see all security telemetry and potentially inject false negatives, blinding detection across Core/app2/app3/app1-bu/wphost02 simultaneously. This is the single highest-leverage compromise in the estate.
  4. app3 shared Percona -> all 24 CloudPanel sites: any SQL injection, credential leak, or root compromise on app3 threatens every site's database in one blast, because there is one MySQL engine process backing internal, client, and product sites alike (see section 6).
  5. app1-bu standby -> S3 snapshot, not live Core: because sync is pull-only from Wasabi S3 rather than a live tunnel to Core, app1-bu compromise does NOT directly hand an attacker live access to Core. This is a positive isolation finding - call it out as something already done right, worth preserving in Phase Two hardening (don't accidentally add a live push tunnel later).

6. Product/tenant isolation findings

  • app3 (CloudPanel) hosts ~24 sites behind one shared MySQL/Percona instance with no per-tenant database server, container, or credential vault: apx, boxpilotlogistics, buzz, clp, debtreecoveryexperts, docs, drecovery, forms, gmb, hexclave-api, hexclave-dash, iAmGMB, intelsight, ippadmin, katiewatts, mainwp, mockups, modelortho, modelorthowww, myverdicttank, myvoip, panel, proposals, support, timapta, transitpin, transitpin-dash, vigilanttac, voipsimplicity. This list mixes internal ITPP ops (mainwp, support, panel, ippadmin, docs, proposals, mockups), client sites (katiewatts, modelortho/modelorthowww, vigilanttac, boxpilotlogistics, timapta), and products (transitpin/transitpin-dash, myverdicttank, buzz, hexclave-api/dash) on the same host, same web server, same database engine. Some products (buzz, hexclave) do run their own dedicated Postgres containers alongside the shared Percona instance, which is better isolation for those two, but the CloudPanel-native PHP/static sites (the majority) share the one host-level MySQL.
  • app1 mixes internal control-plane services with two client-facing products in the same Docker/Caddy stack: Wazuh (SIEM), Twenty CRM (internal, holds PII for debt-recovery-experts.com), LiteLLM/admin-ai (AI gateway), and Komodo (deploy automation) run as containers alongside TransitPin and giftaroast.com (products) and crm.debtrecoveryexperts.com (client-branded CRM instance). A single Caddy instance and a single Docker host boundary is the only thing separating "internal admin tooling" from "product the client interacts with directly."
  • No evidence of per-product credential vaults. Each product/service has its own DB password baked into its own container's env (Twenty's APP_SECRET, Komodo's KOMODO_JWT_SECRET, buzz's BUZZ_S3_SECRET_KEY, hexclave's STACK_SERVER_SECRET, etc.) - that part is correctly per-service. But nothing enforces that these secrets stay scoped: any root compromise of the host reads all of them from docker inspect, as this audit itself demonstrated read-only.
  • HotNow was not found deployed anywhere in this pass (no matching directory/container/proxy entry across app1/app2/app3). Flag for Phase Two / Git-A cross-reference: confirm whether HotNow is live, decommissioned, or hosted somewhere outside the six audited servers.
  • VerdictTank / RFP Tank: found evidence of myverdicttank CloudPanel site on app3 and a /root/.verdicttank-key.json LiteLLM API key file on app1 (model access key scoped to specific models, not a raw cloud credential) - so VerdictTank spans app1 (LLM access) and app3 (web/DB), with no isolation boundary tying those two halves together other than the key itself.

7. Shared credentials / shared reverse-proxy / shared database findings

  • Shared reverse proxy (app1 Caddy): one Caddyfile terminates TLS for internal tools (vault, n8n, ai, admin-ai, noc, wz/Wazuh) and product/client domains (giftaroast.com, crm.debtrecoveryexperts.com, komodo.iamgmb.com, transitpin.iamgmb.com) side by side. A Caddy-level misconfiguration or compromise affects both tiers simultaneously.
  • Shared reverse proxy (app3 CloudPanel/nginx): same pattern - one web-server control plane (CloudPanel, itself a public admin panel) fronts internal, client, and product sites.
  • Shared database engine (app3 Percona/MySQL): single instance backs the majority of the 24 CloudPanel sites across all three tiers, as detailed in section 6.
  • Shared Tailscale mesh with no ACL segmentation: all 6 production hosts AND personal devices (iPhone, MacBook, Mac Mini, home router) are members of one flat tailnet group with no tags observed on any peer. A compromised personal device is one hop from every production server over Tailscale; a compromised production server is one hop from personal devices.
  • Shared SSH key (itpp-infra) across all 6 hosts: this is an operational convenience but means loss of that one private key (which lives on Core) compromises SSH access to the entire estate at once. This is the single most consequential shared credential in the estate.

8. Findings (severity-rated)

ID Severity Finding Evidence Why it matters
NETB-1 Critical No network segmentation exists anywhere in the estate. Core, app1, app2, app3, app1-bu are flat on public Netcup/Hetzner IP space with no VLAN or subnet isolation, and the Tailscale overlay that connects most of them has no ACL tags on any node (default allow-all). wphost02 sits fully outside the mesh, reachable only over the open internet. tailscale status --json on Core/app1/app2/app3/app1-bu shows zero Tags entries for any peer; ip route/ping tests confirmed Core, app1, app2 mutually reach each other's PUBLIC IPs directly with no filtering observed between them If any one host is popped, the attacker is not contained to that host's blast radius alone. They inherit a direct network path (public IP or Tailscale) to every other host and to personal devices, turning a single compromise into an estate-wide incident with no lateral-movement friction.
NETB-2 Critical Wazuh SIEM stack (indexer, dashboard, manager API) is the single highest-leverage target in the estate because (a) it is publicly exposed via the Docker/UFW bypass NetEng-A found (APP1-1) and (b) compromising it gives an attacker visibility into and potential control over security telemetry for every other host, which likely reports to it as a Wazuh agent. app1 Docker/iptables inspection (NetEng-A APP1-1) + this audit's confirmation that app1 accepts inbound connections on its public IP from other estate hosts with no gate Losing the SIEM is worse than losing any single production host: it blinds detection across the whole estate at the exact moment an attacker needs detection to fail, and it sits on a host with no segmentation from the rest of the fleet.
NETB-3 Critical app3 shares one MySQL/Percona instance across ~24 sites spanning internal ops, client sites, and micro-SaaS products with no per-tenant database server or vault boundary. ss -tlnp shows a single mysqld on 127.0.0.1:3306/*:33060; CloudPanel site list (apx, boxpilotlogistics, buzz, ... myverdicttank, panel, support, transitpin, vigilanttac, voipsimplicity, ~24 total) all on one host A SQLi or credential leak against ANY one of the 24 sites is a plausible path to every other site's data on the same instance, including internal ITPP operational data (mainwp, support, panel) sitting next to client and product databases.
NETB-4 High Management/admin control planes are public with no compensating network boundary: UniFi controller (app2), UNMS/UISP (app2), CloudPanel admin panel (app3), backup-restore UI (app3), Komodo (app1). Several via unintended Docker/UFW bypass, some via explicit-but-broad UFW rules. NetEng-A APP1-1/2/3, APP2-1/2, APP3-1/2; cross-referenced here against tier classification (section 2) Each of these is a "master key" for a whole subsystem (wireless network, ISP customer management, ~24 hosted sites, backup/restore capability, deploy automation). None should be reachable without a network-level gate (VPN, allow-list, or reverse-proxy auth) in addition to application login.
NETB-5 High Internal tooling and client-facing/product systems share the same Docker host and reverse proxy with no boundary on app1 (Wazuh + Twenty CRM + LiteLLM + Komodo alongside TransitPin, giftaroast.com, crm.debtrecoveryexperts.com) and app3 (CloudPanel mixing mainwp/support/panel with client and product sites). app1 Caddyfile listing both internal and product/client domains; app3 site directory listing (section 6) A compromise anywhere on either host crosses tiers for free - there is no reason a breach that starts in a low-value product site should reach the internal CRM or deploy-automation control plane, but on these hosts it can, because nothing stops it at the network layer.
NETB-6 High Estate-wide single SSH key (itpp-infra) is the sole authentication factor for root on all 6 hosts, and it lives on Core alongside a live autossh tunnel into wphost02's database and WireGuard routes into the home network and WISP towers. This audit used the same key against all 6 hosts successfully; ip route/ps aux on Core show the WireGuard routes and the standing tunnel Core is a de facto master key for the entire estate plus the home network and WISP towers. Its compromise is strictly worse than any other single host's compromise and there is no network segmentation limiting the blast radius once that key is in an attacker's hands.
NETB-7 Medium Tailscale mesh includes personal devices (phone, laptop, Mac Mini, home router) in the same untagged group as all six production servers. tailscale status output on every host lists iphone-15-pro-max, m4-mac-mini, ipp-g-lap, liberty-udm-pro alongside core/app1/app2/app3/app1-bu with no tag differentiation A compromised personal device (phishing, stolen laptop, malicious app) is one network hop from production infrastructure with no policy boundary in between. Tailscale ACL tags exist specifically to prevent this and are not being used.
NETB-8 Medium Product credentials for HotNow/VerdictTank/RFP Tank/TransitPin are scoped per-service but not vaulted, and the products themselves are split across hosts (app1 for LLM access key, app3 for web/DB) with no documented boundary tying the split together. /root/.verdicttank-key.json on app1 (model-scoped LiteLLM key); myverdicttank, transitpin, transitpin-dash CloudPanel sites on app3 If the split is intentional, it should be documented as an architecture decision with an explicit trust boundary; as observed, it looks like an artifact of convenience rather than a designed isolation boundary, which risks silent credential sprawl as more products are added.
NETB-9 Low app1-bu standby sync design is pull-only from S3 (not a live tunnel to Core) - noted as a positive control worth preserving explicitly in Phase Two policy, not a finding to remediate. hermes-standby-sync.sh: aws s3 sync s3://hermes-vps-backups/live/ ...; authorized_keys on app1-bu matches Core's public key (Core-initiated direction only) Call this out so future changes to the standby (e.g. adding a live replication tunnel) are evaluated against the blast-radius benefit of the current pull-only design before being adopted.

Severity summary

  • Critical: 3 (NETB-1, NETB-2, NETB-3)
  • High: 3 (NETB-4, NETB-5, NETB-6)
  • Medium: 2 (NETB-7, NETB-8)
  • Low: 1 (NETB-9, informational/positive-control note)

9. Access limitations / items requiring Phase Two follow-up

  • Could not confirm from read-only evidence whether app1's MCP tooling makes live cross-host calls into Core's super-search MCP (:8899) - the network path exists and is unfiltered, but application-layer confirmation needs a Phase Two trace (log review, not a live test).
  • Could not run clpctl db:show:master-credentials per-site on app3 (would require a site name argument and returns credentials - out of scope for a read-only, no-credential-exposure audit) to confirm whether each CloudPanel site has a distinct MySQL user/schema on the shared instance versus a fully shared root-level credential. Section 6's finding stands regardless (one instance = one blast radius for the engine itself), but Phase Two should verify per-site credential scoping.
  • Tailscale ACL policy file itself (the tailnet admin console's actual ACL JSON) was not inspected - only the absence of Tags on every peer via tailscale status --json, which is strong but indirect evidence of default-allow-all. Phase Two with tailnet-admin access should pull the actual ACL policy to confirm.
  • HotNow was not located on any of the six audited servers. Needs confirmation from Git-A/Docs-W on whether it is live elsewhere, decommissioned, or renamed.

Note on remediation: all items above are Phase Two candidates. No firewall, container, DNS, database, or Tailscale configuration was changed on any host during this audit.