The Agentic Tekton
/ WORK · STEALTH LABS

Confidential by absence, not by defense.

Stealth Labs is the name for a small internal platform that hosts things not ready to be talked about yet: demo projects, early experiments, the occasional confidentiality-bound prototype. Its job is to keep each one private and hard to link back to its owner, for a handful of low-traffic apps, without a second AWS account or a per-seat zero-trust product license. What stays private is the specifics — which projects it hosts, and the platform's own infrastructure identifiers — not the fact that it exists; see the write-up for how that disclosure line gets drawn. What's below, and what's linked, is the pattern the platform runs on, generalized into a public repo built specifically so this page has something real to point at.

The problem

The obvious answers are a VPN or a second AWS account per project. Both work, and both cost more than a handful of low-traffic, low-stakes apps justify: a VPN means a client to install and a tunnel to keep alive, a second account means a second bill and a second IAM boundary to maintain. The narrower fix is to remove the thing that has to be defended rather than defend it well. A security group with an inbound rule is something that can be misconfigured, scanned, or left open by accident. A security group with no inbound rule at all cannot be any of those things, because there is nothing there to get wrong.

The architecture

Each hosted app runs on ECS Fargate with a cloudflared sidecar in the same task. The task's security group has zero ingress rules — the only path in is the outbound-initiated Cloudflare Tunnel the sidecar opens itself, gated by Cloudflare Access before any request reaches AWS at all. Step through the request path below.

INTERNET CLOUDFLARE AWS · ZERO-INGRESS SG Person GitHub / Google Access Allow-list check Tunnel Edge cloudflared Opens tunnel out App localhost hop
A person hits <app>.<base_domain>. Cloudflare's edge terminates TLS and checks Access before anything reaches AWS.
ComponentResourceRole
Compute ECS Fargate task App container + cloudflared sidecar, one task, shared network namespace.
Network VPC, public subnet, zero-ingress SG No NAT, no inbound rule at all — nothing needs one, so none exists.
Tunnel Cloudflare Tunnel The only path a request can take from the internet into the task.
Auth edge Cloudflare Access + two IdPs GitHub and Google OAuth, gating the tunnel hostname before AWS is ever touched.
CI identity GitHub Actions OIDC role Short-lived STS credentials scoped to one repo — no long-lived AWS keys.
Reuse unit infra/terraform/modules/hosted-app/ A second app is one more module block, not a second copy of the pattern.

The tradeoff, named honestly

Removing the inbound path is a single control, not a layered one. There's no NAT gateway, no private subnet tier, nothing standing behind the zero-ingress security group if Cloudflare Access itself is ever misconfigured or a tunnel token leaks. For a small number of low-traffic, cost-constrained personal or demo projects, that's a fair trade — a NAT gateway costs roughly $32/month minimum before data processing charges, more than a single app's Fargate task (about $5-15/month), and there's no compliance regime pushing back on it. It would be the wrong call for a workload that needs defense-in-depth independent of any single vendor; the repo's own well-architected notes say so explicitly, including the residual risk this pattern does not close (unrestricted egress) and what it deliberately doesn't solve (account-level multi-tenancy isolation, high availability). The same notes are just as direct about two operational risks worth reading before adapting this repo: Terraform state holds the tunnel and OAuth secrets in plaintext, so the backend needs to be encrypted and access-controlled, not just locked; and each app's CI trust follows its GitHub repo's current name, not an immutable ID, so a renamed or transferred repo needs that trust re-checked.

Why this is worth doing

The value isn't the individual security group, it's writing the whole pattern once as a reusable Terraform module and reusing it per project instead of hand-building a stack each time. A second hosted app becomes one more module block in main.tf, not a fork that slowly drifts from the original as each project accumulates its own patches. And because each app's desired_count is its own on/off switch, an idle app costs almost nothing — the task definition, ECR repo, DNS record, and Access policy all still exist at zero traffic, and setting it back to 1 brings it live behind Access in about a minute with no infrastructure to stand up.

Read the pattern, or read the repo

The full narrative, including what actually broke once and how it got fixed, is in The Cost of Staying Invisible. The Terraform itself — terraform validate-clean, not deployed anywhere, nothing outside the repo referenced by any module source — is public.