Web surface posture — pre-launch lock-down
Author: 2026-04-28 surface-area review (Kristerpher + Claude) Status: living document — update when surfaces are added or postures change.
Principle
Pre-launch, default to least exposure. Operator surfaces (console, vault, internal docs) are CF Access gated. Customer-facing surfaces that are not yet ready for customers (Antlers app, customer docs, support portal) get a CF Access gate too — invite-only access via Cloudflare Zero Trust until GA. The marketing site (getraxx.com) is also CF Access gated pre-launch (operator decision 2026-05-11 UTC) — gate removes on launch day (2026-05-23 UTC) per docs/ops/runbooks/getraxx-launch-day-cf-access-removal.md. The company brand site (moosequest.net) stays public.
Heroku origin URLs (*.herokuapp.com) bypass Cloudflare entirely. The Cloudflare-origin guard middleware (#252 / PR #443 + console port) rejects any request that did not transit Cloudflare.
Surface matrix
| Surface | Audience | Posture today | Target posture | Action |
|---|---|---|---|---|
raxx.app |
Antlers customer app | public, noindex | CF Access (Zero Trust) until GA | Operator: add CF Access policy on raxx-app Pages project |
www.raxx.app |
redirect | DNS NXDOMAIN | redirect → raxx.app apex |
Operator: CNAME + page rule |
api.raxx.app |
Antlers ↔ API | CF Access | CF Access until GA → public + auth-required at GA | No change today |
api-staging.raxx.app |
pre-launch testing | CF Access ✓ + Heroku origin EXPOSED | Same + origin guard ON | Operator: enable FLAG_ENFORCE_CF_ORIGIN=true on raxx-api-staging |
console.raxx.app |
operator only | CF Access ✓ + Heroku origin EXPOSED | Same + origin guard ON (this PR ports the middleware) | Merge this PR + enable FLAG_ENFORCE_CF_ORIGIN=true on raxx-console-prod |
vault.raxx.app |
bot identities + operator | CF Access ✓ | No change | — |
getraxx.com |
marketing (pre-launch beta) | CF Access gated (2026-05-11) | public + WAF + rate limit at launch (2026-05-23) | Remove gate: terraform destroy in terraform/modules/cf-access-getraxx/ — see docs/ops/runbooks/getraxx-launch-day-cf-access-removal.md |
internal-docs.raxx.app |
internal docs + design (branded URL) | CF Access ✓ (via #572) | No change | — |
raxx-mockups.pages.dev |
internal docs + design (origin, still gated) | CF Access ✓ | No change | — |
raxx-app.pages.dev |
preview deploys | CF Access ✓ | No change | — |
raxx-api-prod-…herokuapp.com |
should be unreachable | partially exposed (502) | unreachable | Operator: enable FLAG_ENFORCE_CF_ORIGIN=true on raxx-api-prod (after staging soak) |
raxx-api-staging-…herokuapp.com |
should be unreachable | fully exposed (200) | unreachable | Operator: enable flag on raxx-api-staging |
raxx-console-prod-…herokuapp.com |
should be unreachable | fully exposed (200) | unreachable | Merge this PR + enable flag on raxx-console-prod |
queue.raxx.app |
Queue identity/billing service | EXPOSED — no CF proxy yet | CF proxy + WAF rules + CfOriginGuard ON | SRE: terraform apply in terraform/queue/ → confirm CF-RAY on /health → soak → FLAG_ENFORCE_QUEUE_CF_ORIGIN=true. Operator CF dashboard actions required before SRE step (see below) |
queue-staging.raxx.app |
Queue staging | EXPOSED | CF proxy + WAF rules + CfOriginGuard ON | Same as prod, staging first |
raxx-queue-prod-….herokuapp.com |
should be unreachable | fully exposed | unreachable | SRE: enable FLAG_ENFORCE_QUEUE_CF_ORIGIN=true after CF proxy soak |
support.raxx.app |
customer support portal | CF Pages raxx-support infra shell live (#653) |
public + indexable on /; app-level auth on /tickets/; robots.txt disallows /tickets/ |
#653 done (infra); card 4 = full portal UI; card 3 = API routes |
docs.raxx.app |
customer docs | doesn't exist | public + indexable | #423 (sub of #104) |
status.raxx.app |
public status / health page | CF Pages project raxx-status + holding page (#602) |
public, no CF Access, indexable | #602 done; React app ships in #604 |
moosequest.net |
brand / studio | public ✓ | public, registrar locked | #217 |
repo (raxx-app/TradeMasterAPI) |
engineering | private ✓ | No change | — |
Operator runbook — ordered actions
1. Enable origin guard on staging API (start the soak)
heroku config:set FLAG_ENFORCE_CF_ORIGIN=true --app raxx-api-staging
heroku ps:restart --app raxx-api-staging
Verify after restart:
# Direct origin → 403 (the change worked)
curl -i https://raxx-api-staging-1a19fb3873b9.herokuapp.com/api/system/status
# CF-proxied → still 200 (CF Access protects this so you'll see the access redirect; that's correct)
curl -i https://api-staging.raxx.app/api/system/status
Soak ≥24 h. Watch heroku logs --app raxx-api-staging --tail | grep direct_origin_blocked for false positives.
2. Enable origin guard on prod API (after staging soak passes)
heroku config:set FLAG_ENFORCE_CF_ORIGIN=true --app raxx-api-prod
heroku ps:restart --app raxx-api-prod
Same verify pattern.
3. Enable origin guard on prod console (after this PR ships)
heroku config:set FLAG_ENFORCE_CF_ORIGIN=true --app raxx-console-prod
heroku ps:restart --app raxx-console-prod
Verify: curl https://raxx-console-prod-ff30a22abccb.herokuapp.com/health still returns 200 (allowlisted), but curl https://raxx-console-prod-ff30a22abccb.herokuapp.com/secrets now returns 403.
4. CF Access gate raxx.app (pre-launch)
Cloudflare Zero Trust → Access → Applications → + Add an application → Self-hosted
- Application name:
Raxx Antlers (pre-launch) - Domain:
raxx.app, plus*.raxx.appif you want to cover subdomains broadly - Session duration: 24h
- Identity providers: pick whichever email you use (Google Workspace if your
kris@moosequest.netis on it) - Policy 1 — operator: include emails:
kris@moosequest.net(+ any ops support emails) - Policy 2 — Founders waitlist (optional): include email domains or specific allowlisted addresses pulled from your waitlist
- Save
When you go GA, either delete the application or change the policy to "Allow everyone" (effectively no gate).
5. Fix getraxx.com DNS
Cloudflare DNS panel → confirm:
getraxx.comA or CNAME points at the CF Pages project (getraxx.pages.dev)- Proxied (orange cloud)
- TLS edge cert provisioned
If the CF Pages project is missing, run wrangler pages project create getraxx then deploy via the existing pipeline.
6. Add www.raxx.app redirect
Cloudflare DNS panel:
wwwCNAME →raxx.app(proxied)- Page rule (or transform rule on the Free plan):
www.raxx.app/*→ 301 →https://raxx.app/$1
7. Future surfaces
support.raxx.app— when #449 lands, gate/admin/*only.docs.raxx.app— when #423 ships, fully public + indexable.status.raxx.app— live as of #602 (holding page); React app (#604) replaces it. Stays public, no Access gate, indexable forever.
8. Queue surface — new controls (#3593, implemented 2026-06-16)
Status: implemented, rollout pending per ADR-0078.
Controls implemented in PR for #3593:
| Control | Implementation | Status |
|---|---|---|
| CF origin guard | CfOriginGuard Drogon filter (FLAG_ENFORCE_QUEUE_CF_ORIGIN) |
Implemented, default OFF — enable after CF proxy confirmed live |
| Internal API rate limit | WAF rule AC2a: block GET /api/v1/internal/* at 120 req/min/IP |
Implemented in terraform/queue/waf.tf — apply pending |
| Stripe ASN anomaly log | WAF rule AC2b: log webhook POST from non-Stripe ASN (WH-3) | Implemented in terraform/queue/waf.tf — apply pending |
| INT-3 detection | Sentry event on CallerIdentity::unknown in InternalAuthFilter |
Implemented — active when QUEUE_SENTRY_ENABLED is compiled in |
SRE rollout sequence (ADR-0078 §8):
terraform applyinterraform/queue/(SRE) — applies DNS proxy + WAF rules.- Confirm CF proxy live:
curl -sI https://queue.raxx.app/health | grep CF-RAYmust return a ray ID. - Soak with
FLAG_ENFORCE_QUEUE_CF_ORIGINOFF (≥48h, observedirect_origin_blockedlog rate). - Audit caller URLs: all Raptor/Console/Velvet calls must use
queue.raxx.app, notraxx-queue-prod.herokuapp.com. - Enable on staging:
heroku config:set FLAG_ENFORCE_QUEUE_CF_ORIGIN=true --app raxx-queue-staging >/dev/null 2>&1 && heroku ps:restart --app raxx-queue-staging - Verify staging:
curl -i https://raxx-queue-staging.herokuapp.com/health→ 403 (direct blocked);curl -i https://queue-staging.raxx.app/health→ 200. - Enable on prod:
heroku config:set FLAG_ENFORCE_QUEUE_CF_ORIGIN=true --app raxx-queue-prod >/dev/null 2>&1 && heroku ps:restart --app raxx-queue-prod - Update
terraform/queue/variables.tfqueue_prod_cf_origin_enforced = true.
Operator CF-dashboard items (operator-action, not automated):
- Bot Fight Mode (BFM): Cloudflare dashboard →
raxx.appzone → Security → Settings → Bot Fight Mode → ON. Required beforeterraform applyso WAF skip rules (Priority 1/1b) are active from first apply. - TLS mode: Cloudflare dashboard →
raxx.appzone → SSL/TLS → Overview → Full (strict). Required to prevent downgrade attacks at the CF → Heroku origin leg. (Heroku provides a valid TLS cert via SNI for*.herokuapp.com.) - Both items apply to the entire
raxx.appzone. If already set for other surfaces, no change needed.
What's NOT in this doc
- WAF rules per surface (rate limits, bot fight mode) — that's a follow-up once these baseline gates are in place.
- mTLS or service-token-only access on bot endpoints — already in place at
vault.raxx.appvia CF Access service tokens; replicate to other internal-only surfaces if traffic patterns demand. - Customer-side authentication mechanics inside Antlers (passkeys, etc.) — that's app-level, not surface-level.
Refs
-
252 / PR #443 — Cloudflare-origin guard for
backend_v2 - This PR — same guard ported to console
-
217 — moosequest.net registrar lock
-
449 — FreeScout /
support.raxx.app -
423 — customer docs /
docs.raxx.app -
104 — public docs site foundation
docs/architecture/break-glass-auth.md(#334) — how an operator regains access if CF Access locks them out-
3593 — Queue billing surface WAF edge hardening + origin guard + detection wiring
docs/architecture/queue-cf-edge-design.md— full layered defense designdocs/architecture/adr/0078-queue-cf-edge-protection.md— ADR