Abi Abiassi
WRITING /
Command Center was public. That stopped making sense.
Moving an internal control plane behind Tailscale, with a small public mailbox at the edge.
Command Center started as a public application on Vercel, protected by login and API keys. That was convenient. It was also the same default shape as almost every internal tool.
The problem was what sat behind the login.
Command Center is our internal registry and access-control layer. It holds project context, permissions, resource mappings, and references to where secrets live. It also brokers secret reads. Every Darkmatter project and agent uses that context to find the right repository, Linear team, database, deployment project, analytics property, or skill.
That made it the highest-value target in the organisation. Compromise it and you have a route toward everything else.
Shai-Hulud 2.0 spread through npm in November 2025, stealing credentials as it went. In April 2026, Anthropic published Claude Mythos Preview, describing a model that could find and exploit zero-day vulnerabilities across major operating systems and browsers. One was an attack. The other was a preview. The direction was hard to ignore.
Command Center had not been breached. Attempts against our own public surface were increasing anyway. Waiting for a breach was not much of a security strategy. We started cutting exposure around applications that held secrets or billing data. Command Center was the obvious first move. A public application boundary, with authentication carrying most of the burden, no longer matched the value of the asset behind it.
This was a product decision before it was an infrastructure decision. The convenience was real. So was the attack surface.
Around 12 May, we moved Command Center to a small Hetzner VPS behind Tailscale. The application listens locally. Caddy binds only to the Tailscale address. The public host exposes no web ports. TLS uses a DNS challenge, so port 80 never needs to open. The old Vercel application was decommissioned.
The application was private. Then the useful parts broke.
GitHub still needed to send push and pull-request events. Linear still needed to send webhooks. Health monitors and agent callbacks still needed a destination. A private control plane with no public application port cannot receive a POST from a public service.
Opening another inbound route would have recreated the boundary we had just removed. So we inverted it.
A mailbox outside the wall
Outpost is the small public component that remains. It runs on Vercel and accepts four kinds of event: GitHub, Linear, health, and agent callbacks. The first three use HMAC signatures. Agent callbacks use a bearer token.
I think of it as a mailbox. Public systems drop authenticated bytes into it. Outpost verifies the sender, stores the event in a durable queue, and waits. It has no connection to Command Center's Neon database and runs no Command Center business logic. Its queue lives in a separate, disposable Postgres database.
Command Center initiates the connection. A worker on the private host makes an authenticated long poll to Outpost, claims a batch of queued events, and pulls them across the boundary. Outpost uses atomic row claims so workers cannot take the same item. Per-source deduplication makes provider retries idempotent.
The important part happens after the pull. Command Center verifies each event's signature again.
Outpost's first check protects the public receiver. The second check means the private control plane does not treat the relay as an authority. If Outpost is buggy or compromised, it still cannot create an unauthenticated event that Command Center will accept. Transport and authorization remain separate decisions.
Once verified, Command Center runs the matching handler, writes the result, then acknowledges the queue item. Failed items can be marked dead. Claims left behind by a crashed worker are returned to the pending queue by a periodic reclaim job once they have sat unacknowledged for more than five minutes. A deployment or temporary network interruption can delay an event without silently dropping it.
The queue bounds our dependency on Command Center being available. Re-verification bounds our trust in the public relay. We needed both.
Deployment crosses the same boundary
The deploy loop uses the same path.
A push to main reaches the organisation-level GitHub webhook at Outpost. Outpost verifies and queues it. Command Center pulls it, verifies it again, and passes it to the GitHub handler. The handler writes a local deploy trigger. A systemd path unit sees that file and starts the deployment service outside the application process, so the app can restart without killing its own deploy.
There is no separate public CI callback into the private host. A deployment is another authenticated event collected through the mailbox. The activity feed records whether it succeeded or failed. If the automatic path fails, the fallback is still ordinary SSH over the Tailscale hostname and a local deployment script.
That reuse mattered. The boundary became a product primitive rather than a special case built for one webhook.
What this does not solve
The private boundary removes a large public surface. It does not make the system invulnerable.
A compromised device already inside the tailnet retains access. Tailscale controls who can reach the host, not what a trusted device can do after compromise.
The public Hetzner address still answers SSH on port 22, behind an allowlist. Root SSH compromise remains total compromise and sits outside the tailnet story.
Outpost is public by design. If a webhook signing secret leaks, an attacker can produce an event that passes both signature checks. A verification bug can create the same kind of foothold.
The deployment supply chain remains uncomfortable too. Each deploy runs pnpm install and next build on the host that holds the project's secret references. A poisoned dependency would execute inside the private boundary. Moving the boundary does not contain that build.
Those limits are part of the decision. We traded a convenient public application for a much smaller public receiver, outbound collection, and a few operational costs we can see clearly.
The public application was decommissioned. The mailbox stayed.