Networking¶
Tailscale mesh, SSH keys, VNC, domains, DNS, and Cloudflare Workers.
Tailscale Mesh¶
All hosts connected via Tailscale (100.x.x.x). See hosts.md for full IP table.
ACL Tags:
- tag:sandbox — Applied to agent hosts. Can only receive inbound connections, cannot initiate to other Tailscale nodes (except allowed services). Note: On VPS, causes node key expiration — do NOT apply without updating ACL policy first.
Exit Node: Pi4-01 (100.73.159.126) configured as exit node.
SSH Keys¶
All keys stored in ~/.ssh/ on the MacBook Air M4:
| Key File | Target Host | User |
|---|---|---|
id_ed25519 |
Default key | — |
dell-stig-1 |
Dell k3s server | claude@100.95.212.93 |
mac-executor |
Mac Mini M4 (iBuild-E) | claude@100.92.170.124 |
rpi-claude |
Pi4-03 (Pi-E) | claude@100.107.48.17 |
rpi-pi4-01 |
Pi4-01 (exit node) | — |
rpi-pi4-02 |
Pi4-02 (spare; former Review-E host) | claude@100.77.12.75 |
vps-srv1099021 |
VPS (Volt-E) | root@100.111.142.118 |
VNC Endpoints¶
| Host | Purpose | Notes |
|---|---|---|
| Volt-E (VPS) | claude login OAuth refresh |
Required periodically |
| ~~Review-E (Pi4-02)~~ | Migrated to GCP k3s | No longer needs Pi VNC OAuth refresh |
Pi-E does NOT need VNC — OAuth tokens pushed from M4 via LaunchAgent.
Cloudflare Domains¶
| Domain | Purpose | Key Services |
|---|---|---|
| dashecorp.com | Shared domain for apps without own domain | Privacy policies, support pages, App Store links |
| invotek.no | Company website | Corporate site |
| stigjohnny.no | Consulting profile site (zone in a separate personal Cloudflare account) | Pages project stigjohnny-no on www, source dashecorp/stigjohnny-no; M365 mail (post@stigjohnny.no, its own tenant) |
| nutrie.app | Nutri-E app domain | App website, API workers |
| cutiefeedback.com | Cuti-E platform | SDK endpoints, dashboard |
Cloudflare Workers (32 total)¶
Production Workers¶
| Worker | Domain | Purpose |
|---|---|---|
cutie-worker |
cutiefeedback.com | Cuti-E platform API |
cutie-worker-sandbox |
— | Cuti-E sandbox environment |
deadline-tracker-worker |
— | Deadline tracking (cron: daily 07:00 UTC), D1 backend |
email-inbox |
— | Email handler (email + fetch triggers) |
infra-monitor |
— | Infrastructure monitoring (cron: every 15 min), KV backend |
invotek-account-deletion |
invotek.no | GDPR account deletion endpoint |
invotek-privacy-policy |
invotek.no | Privacy policy endpoint |
reward-e-website |
— | Reward-E production website |
reward-e-staging |
— | Reward-E staging website |
Nutri-E Workers¶
| Worker | Variant | Purpose |
|---|---|---|
nutrie-apple-webhook-worker-* |
v2, v3, sandbox-v2, sandbox-v3 | Apple Server Notifications |
nutrie-dsld-worker-* |
v2, v3, sandbox-v2, sandbox-v3 | DSLD supplement data API |
nutrie-openai-worker-* |
production, v2, v3, sandbox, sandbox-v2, sandbox-v3 | OpenAI proxy |
nutrie-stats-rss |
— | Stats RSS feed |
Other¶
| Worker | Notes |
|---|---|
astar-tracker |
Astar tracker (astar-db D1 backend) |
infra-registry-redirect |
Redirect to the infra-registry Pages site (infra-registry.pages.dev) |
rig-cockpit-worker |
Rig Cockpit API (rig-cockpit + rtd-social D1; fetch + scheduled + queue). Since 2026-10-03 also the rig's webhook router, see Rig webhook router |
rig-router |
Rig request router (OpenTofu-deployed) |
tripletex-proxy |
Tripletex (accounting) API proxy |
site-85a189d3 |
Created 2026-03-14 — verify purpose |
Count note (verified 2026-07-01): the 32 total includes 6 leftover Nutri-E
v2/sandbox-v2workers from the v2→v3 migration (retiring tracked indashecorp/infra#248). Analytics gotcha: the Workers GraphQL Analytics API counts cron + queue + fetch invocations together, so cron/queue workers (deadline-tracker-worker,infra-monitor,rig-cockpit-worker,cutie-worker) read as "anomalously busy" in raw request counts — not a real anomaly. Cost: ~$5/month (the Workers Paid floor, required bycutie-worker'sWebSocketHandlerDurable Object).
Cloudflare D1 Databases (8)¶
Sizes verified 2026-07-01 via mcp__cloudflare__d1_list_databases.
| Database | ID | Size | Used By |
|---|---|---|---|
cutie-sandbox |
c5ee2ef3-34e5-4e45-b21e-e5a203a7f177 |
3.8MB | cutie-worker-sandbox |
cutie-production |
3111e614-2200-4c51-b63f-d8d3e46f7503 |
1.9MB | cutie-worker |
rtd-social |
d298499d-abb8-4009-b184-9bd8145617c1 |
184KB | Rig Cockpit /videos + Run the Docs social pipeline |
rig-cockpit |
1d1ca944-8ba2-4ab0-8bb4-08582b81eb9d |
112KB | rig-cockpit-worker |
astar-db |
ea4d99db-e206-4d63-a2a4-405ec79ba372 |
60KB | astar-tracker worker |
email-inbox |
970229bf-a3e1-4a8f-9f9d-73fc14d959da |
52KB | email-inbox worker |
submission-checklist |
5019587a-5546-40f6-99c2-b235503a7284 |
48KB | submission-checklist MCP |
deadline-tracker |
f108263d-105e-44eb-94c9-a84e35a69568 |
36KB | deadline-tracker-worker |
Cloudflare Pages¶
~20+ Pages projects on the account (the Cloudflare MCP has no Pages list endpoint — enumerate live with wrangler pages project list or the dashboard; ~22 at the 2026-05-16 audit). Major projects: infra-registry (this registry), rig-research / rig-docs / dashecorp-docs / invotek-docs (docs sites), dashecorp (marketing), cutie-admin-dashboard, nutri-e, cuti-e, ai-accountant, scout. All on the free tier (generous headroom).
Cloudflare KV Namespaces (10)¶
| Namespace | ID | Used By |
|---|---|---|
SUBSCRIPTIONS |
17ffbd7939be4c59984fa8220a34fa2e |
Nutri-E Apple webhook (production) |
SUBSCRIPTIONS_SANDBOX |
c823bfef717348c982b00c8a310b6e00 |
Nutri-E Apple webhook (sandbox) |
SUBSCRIPTIONS_DELETED |
b2e12a4d1c1e494396e98c9ef8724b42 |
Nutri-E deleted subscriptions |
SUBSCRIPTIONS_DELETED_SANDBOX |
ebc6dd3e6fea43a090b24606d291fd93 |
Nutri-E deleted subs (sandbox) |
CACHE_KV |
a497291bf1ff41a888902c39281f338d |
Nutri-E DSLD cache (production) |
CACHE_SANDBOX |
3793746890ca4ba49641cacfddb768da |
Nutri-E DSLD cache (sandbox) |
RATE_LIMIT |
56f5968c8a86441aa98bbdee6edf66de |
Nutri-E rate limiting (production) |
RATE_LIMIT_SANDBOX |
f55e2bf148db4359970f63ac37c572f3 |
Nutri-E rate limiting (sandbox) |
production-DEVICE_ACTIVITY |
5a36bf6a3e894a08b15cbec1fa19dba8 |
Nutri-E device activity |
INFRA_MONITOR |
b67b5e912d914ce3b1ab856a3d8c9fc6 |
infra-monitor worker |
Cloudflare Queues (5)¶
| Queue | ID | Producers | Consumers | Used By |
|---|---|---|---|---|
cutie-notifications |
27bfce8f |
1 | 1 | Cuti-E push notifications |
cutie-notifications-sandbox |
bfe8812a |
1 | 1 | Cuti-E notifications (sandbox) |
cutie-notifications-dlq |
f0ff7f66 |
0 | 0 | Dead letter queue |
rig-webhooks |
— | — | — | Rig webhook buffer, 14-day retention (router) |
rig-webhooks-dlq |
— | — | — | Its dead letter queue, 14-day retention |
Cloudflare R2¶
| Bucket | Detail | Since |
|---|---|---|
rig-backups-dell |
The Dell's off-site backups (rig-gitops#897, see Kubernetes → Dell plan step 4). EU jurisdiction. Object lock and lifecycle are set per prefix, and a catch-all rule keeps everything else 34 days | infra#573 (P1a); lifecycle fix infra#577 |
No tokens yet. The R2 tokens come in P1b, after the operator grants infra CI's Cloudflare token "Account API Tokens: Edit" (O1). The writer token then goes into SOPS.
Email Routing¶
| Address | Forwards To | Domain |
|---|---|---|
| pi-e@dashecorp.com | pie.dashecorp@proton.me | dashecorp.com (Cloudflare Email Routing) |
Tailscale ACL Policy¶
Source of truth: dashecorp/infra → tailscale/main.tf (managed via OpenTofu, deployed via GitHub Actions terraform.yml)
Current policy uses role-based tags: tag:server, tag:agent, tag:terraform, tag:ci. tag:agent can reach tag:server; tag:server can reach tag:server (and nothing untagged: *:* never covers tagged devices, and an untagged device is not in tag:server); tag:ci, the ephemeral GitHub Actions node, may reach only SSH on the Dell (dell:22, infra#503); the Dell (hosts.dell) may reach the Synology NAS (hosts.nas, the untagged DS412plus) on tcp/2049 only, for its NFSv4.1 volumes (infra#525; before that rule the Dell's 11 NAS mounts hung from 2026-04-13 to 2026-10-01, rig-gitops#882); all members can use the exit node for internet access. Admin has full access to all devices. SSH access limited to admin in accept mode.
Cloudflare Tunnels¶
Source of truth: dashecorp/infra → cloudflare/dashecorp.com/tunnel.tf (Cloudflare tunnel resources, deployed via OpenTofu).
Every ingress rule is whole-host. There is no path key anywhere in
cloudflare_zero_trust_tunnel_cloudflared_config.gcp, so for each hostname the origin receives the
full URL space of that port. Path scoping exists only in the Access layer, and only once (the GitHub
webhook bypass). Audited 2026-09-11.
| Tunnel | Domain | Target | Cloudflare Access |
|---|---|---|---|
| dashecorp-gcp | rig-conductor.dashecorp.com | http://rig-conductor-api.rig-conductor.svc.cluster.local:8080 | Yes — email allow + non_identity service token (the cockpit worker's) |
| dashecorp-gcp | rig-conductor.dashecorp.com/api/webhook/github | (same origin) | Bypassed by design — HMAC is the only gate. Since 2026-10-03 a Workers route sends this path to rig-cockpit-worker first (router) |
| dashecorp-gcp | flux.dashecorp.com | http://weave-gitops.weave-gitops.svc.cluster.local:9001 (Flux GitOps UI) | Yes — email allow |
| dashecorp-gcp | rig-assistant.dashecorp.com | http://rig-assistant.rig-assistant.svc.cluster.local:8787 | No — see below |
| dashecorp-gcp | rig-e.dashecorp.com | http://rig-e-rig-agent-runtime.dashecorp.svc.cluster.local:3000 | No — see below |
| dashecorp-dell-v2 | rig-conductor-dell.dashecorp.com | http://rig-conductor-api.rig-conductor.svc.cluster.local:8080 on the Dell | Yes — the rig's service token only |
The Dell's connector is live since 2026-10-15 (rig-gitops#1015): one cloudflared pod on the Dell runs dashecorp-dell-v2 (f3b364b4…), a new tunnel made by OpenTofu (infra#655) and fed its token by the phone-approved C4 step (CI/CD). Without a token the hostname answers 403. It is unrelated to the hand-made Dell tunnels removed on 2026-10-02 (below).
GCP's tunnel secret leaked into old CI logs (infra#654): OpenTofu printed a random_id's id, which is its value. Rotating it is pending the operator's decision. Mask [id=...] in every CI-log grep.
cost.dashecorp.com CNAMEs to this tunnel with no matching ingress rule, so it lands on the
http_status:404 catch-all. It is Access-protected, so the 404 sits behind a login. Stale, not
exploitable; outputs.tf still advertises it.
Rig webhook router (multi-location rig)¶
Added 2026-10-03 for the multi-location rig (Kubernetes → Dell plan step 4, dashecorp/infra#565):
| Object | Detail | Since |
|---|---|---|
rig-conductor-gcp.dashecorp.com |
Behind Access, service token only: just the rig_cockpit_worker token is allowed. The router's forward target for gcp (rig-cockpit-worker#306, 2026-10-15) |
infra#567 |
Queues rig-webhooks, rig-webhooks-dlq |
14-day retention | infra#567 |
Workers route rig-conductor.dashecorp.com/api/webhook/github* → rig-cockpit-worker |
The router, live since 2026-10-15 14:04:33Z (ROUTING_MODE=live, rig-cockpit-worker#304): HMAC check, queued on rig-webhooks, 202, then forwarded to the tenant location's own hostname |
infra#569, rig-cockpit-worker#304 |
Worker secret GITHUB_WEBHOOK_SECRET |
The router's HMAC check: the shared webhook secret. OpenTofu writes it to rig-cockpit-worker as ROUTER_WEBHOOK_SECRET, and every deploy puts it (Credentials). Shadow logs show hmacValid: true (operator check, 2026-10-15) |
infra#664, rig-cockpit-worker#302 (2026-10-15) |
- Never set
global_fetch_strictly_publiconrig-cockpit-worker. Its pass-through fetchesrig-conductor.dashecorp.com; with the flag on, that fetch goes back through the route into the Worker itself and loops. - Forwards never target the stable host. The queue consumer is not on the route's zone, so a forward to
rig-conductor.dashecorp.comwould most likely re-enter the router (508). Every location has its own hostname, and a test in rig-cockpit-worker checks it (#305). - Route scope: only
/api/webhook/github*(and later/api/merge*), never/*. Check that GitHub hook deliveries return 2xx after any route change. - Rollback: a PR that sets
ROUTING_MODE = "shadow"(the merge deploys it); queued messages still drain; then redeliver failed deliveries from GitHub.
Rig Cockpit GitHub webhooks¶
Added 2026-10-03 for the Rig Cockpit approvals MVP (rig-cockpit-worker#268). Both paths are on rig-cockpit.dashecorp.com, and both hooks are signed with the same secret (Credentials):
| Path | Hook | Since |
|---|---|---|
/v1/github/waiting |
rig-gitops workflow_job/workflow_run hook 691622740: runs waiting for approval, shown as the banner and push. Ping returned 200 |
infra#582 |
/v1/github/inbox |
dashecorp/operator-inbox issues hook: the "Waiting for you" list (Operator inbox) |
infra#585 |
Neither operator-inbox nor rig-approve has a rig-conductor hook.
Manual / non-Terraform tunnels: the Dell's are gone (2026-10-02). The Dell's only tunnel now is the OpenTofu-managed dashecorp-dell-v2 above. Before that, the Dell ran three hand-made tunnels:
- invotek-dell: vcluster.invotek.no (vCluster Platform) and conductor-e.dashecorp.com;
- tablez-vcluster: grafana, api and app.invotek.no;
- a host tunnel in another account: code.stigjohnny.no (code-server) and argocd.stigjohnny.no.
Their workloads and connectors were removed in the Dell cleanup. The first two tunnels, their 4 invotek.no CNAMEs and their 2 Access apps were adopted into OpenTofu and deleted (dashecorp/infra#548, docs/2026-10-02-dell-cloudflare-cleanup.md there).
Left in place by operator decision ("Leave them for now. But document"): the stigjohnny.no objects, which sit in a Cloudflare account the CI token cannot see (Zero Trust team stigjohnnyno):
- the DNS records code.stigjohnny.no and argocd.stigjohnny.no;
- the former host tunnel;
- two Access apps.
They are inert: the tunnel has no connector, and both hostnames sit behind an Access login. Deleting them needs the operator in that account's dashboard, or a token for it.
Other down tunnels in the main account, not the Dell's and not yet decided: automate-e-dashboard, iclaw-e (iClaw-E retired 2026-09-25), and atl-dashboard below.
Offline / Retired Tunnels¶
| Tunnel | Domain | Notes |
|---|---|---|
| atl-dashboard (DOWN) | atl-e.dashecorp.com | Offline, removed from Terraform (legacy, namespace broken) |
| rig-agent-runtime-dashboard | book-e.dashecorp.com | Retired — no Terraform definition |
Cloudflare Email Routing¶
| Address | Forwards To | Domain |
|---|---|---|
| pi-e@dashecorp.com | pie.dashecorp@proton.me | dashecorp.com |
| (inbound to email-inbox worker) | D1 database email-inbox |
invotekas.workers.dev |
Unauthenticated surfaces (audited 2026-09-11)¶
Two of the tunnel's four hostnames have no Access application, and both justify it in-comment with "the origin's own token is the gate". That is a per-endpoint claim being used to license a whole-host mapping: it holds for the one route that checks a token and for nothing else on the port.
| Surface | State | Tracking |
|---|---|---|
rig-e.dashecorp.com — GET / and /api/state served the runtime dashboard: log ring, sessions, tool calls, token/cost accounting |
CLOSED 2026-09-11 13:05Z — /api/state now 404 |
dashecorp/infra#447 |
rig-e.dashecorp.com — wss://…/ws accepted an anonymous Upgrade and sent the same dashboard state, then tailed the log ring live |
CLOSED — the listener split (rig-agent-runtime#704); every Upgrade on the routed port is refused, /ws included |
rig-agent-runtime#700 |
SSH tcp:22 open to 0.0.0.0/0 on all three rig nodes, via GCP's auto-created default-allow-ssh |
PART-CLOSED — allow-ssh-iap added and applied, allow-ssh-admin un-staled and its dead tag fixed; the world-open rule is still there pending part 2 |
dashecorp/infra#449 |
valkey on NodePort 30079, auth off (upstream valkey/valkey, since 2026-09-23). It holds assignments:* and merge-gate:*, and its one live off-cluster client is iBuild-E on the Mac Mini |
OPEN — not internet-reachable; worse than the API half, since writing it means becoming the thing that tells the rig what to do. Auth is still off. RESTORE is now disabled beside the chart-default FLUSHDB/FLUSHALL (rig-gitops#828, the path for CVE-2026-63639 / CVE-2026-25243), which closes the one-shot wipe and the CVE path. Any client can still read and write every stream |
dashecorp/infra#448 |
rig-e.dashecorp.com — an Upgrade on the token-gated /api/chat bypassed the token check entirely |
CLOSED 2026-09-11 11:45Z — rig-agent-runtime#698, deployed via rig-gitops#753; re-probed 11:50Z, answers 400 where it answered 101 at 11:20Z |
rig-gitops#754 |
rig-assistant.dashecorp.com — FastAPI's default /docs, /redoc, /openapi.json; an interactive API console on the origin where the operator types the bearer, loading Swagger UI from a floating CDN tag |
OPEN — lower severity, the /v1/* routes are bearer-gated |
dashecorp/rig-assistant#18 |
rig-conductor on NodePort 30080 — the whole API, no authentication of any kind. Access guards the hostname, not this |
OPEN — verified NOT internet-reachable (no firewall rule, no load balancer, packets dropped). Lateral movement from any pod or tailnet device, not external attack | dashecorp/infra#448 |
The NodePort is the one that matters most once an attacker is already inside — and that
qualifier was missing when this was first written. The firewall check (2026-09-11) came back
negative: nothing permits 30080 or 30079 from the internet, there is no load balancer, and the
ports drop packets from outside. access.tf documents the NodePort as intentional ("internal
agents reach the API via Tailscale NodePort, not through this Cloudflare tunnel"), which means
Cloudflare Access is decorative against anything already inside. The write paths kept off the cockpit's proxy
allowlist were all reachable there unauthenticated. Since 2026-09-24:
- Merge needs Review-E's GitHub approval at the head, whoever calls (rig-conductor#2350).
- GPU start/stop, force-done, tenant create/delete, overrides and agent deletes carry an operator-token gate
(rig-conductor#2369, X-Rig-Operator-Token). It is report-only until OPERATOR_ROUTE_GATE=enforce.
- Still open: event injection (/api/events) and Valkey on 30079. Both need a per-agent identity (infra#448).
Two lessons worth carrying to any new surface:
- An auth check on an HTTP handler does not protect a WebSocket on the same server. Node
dispatches an Upgrade to the
'upgrade'event and never to'request', so method, path and bearer checks written as request handling are simply not on that code path. Scope the socket explicitly. - Scoping the leaking path is not the same as closing the leak. Pinning that socket to
/wsstopped the token bypass and closed nothing else:wss://…/wswas always a dashboard path, so it still serves the same state to anyone who can reach the port. The path was the symptom; the listener was the bug. Check the fix against the exposure, not against the symptom that revealed it. - Reachability is inherited, and that is the dangerous direction.
GET /api/stateis correct code for a cluster-internal listener. It became an internet-facing leak because a hostname was later pointed at the port it shared, and nothing in the endpoint changed to say so. The durable answer is default-deny on whichever listener is routed, so a new endpoint cannot become public by inheritance — it has to be added to an allowlist on purpose. - A case-sensitive pattern is safe for ALLOWING a route and unsafe for EXCLUDING one. An
over-strict allowance fails closed and someone notices the 404; an over-strict exclusion fails open
and silently, because the upstream is usually more permissive about case than the guard
(
rig-cockpit-worker#161).
Network Security Layers¶
- Tailscale ACL — tag:sandbox restricts outbound from agent hosts
- Docker iptables — DOCKER-USER chain blocks containers to private ranges (Pi-E, Volt-E)
- macOS pf — Firewall on Mac Mini M4 blocks outbound to Tailscale/LAN
- Host networking — Volt-E uses host networking (Docker bridge incompatible with Tailscale exit node)