Skip to content

feat(platform): TenantGuard middleware — public repo's only SaaS hook (Phase 32 PR #1) - #78

Merged
HongmingWang-Rabbit merged 1 commit into
mainfrom
feat/saas-tenant-guard-middleware
Apr 14, 2026
Merged

feat(platform): TenantGuard middleware — public repo's only SaaS hook (Phase 32 PR #1)#78
HongmingWang-Rabbit merged 1 commit into
mainfrom
feat/saas-tenant-guard-middleware

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

Phase 32 (Cloud SaaS) foundation. The public repo's only SaaS hook: one middleware, one env var. All orgs/signup/billing/Fly-provisioning code lives in the private `molecule-controlplane` repo (n8n-style open-core).

  • `TenantGuard` reads `MOLECULE_ORG_ID` at boot. If set → every non-allowlisted request must carry matching `X-Molecule-Org-Id` header or gets 404 (not 403 — don't leak tenant existence). If unset → passthrough; self-hosted behavior unchanged.
  • Allowlist is exact-match (not prefix): `/health` and `/metrics` only, so Fly Machines health probes + Prometheus scrape work regardless.
  • Wired into `router.Setup` after metrics so rejected requests still land on the 4xx counter.

Why 404 not 403

An attacker probing multiple org subdomains should not be able to distinguish "this tenant exists but I don't have access" from "this route doesn't exist." 404 + empty body makes both indistinguishable from random noise.

Test plan

  • Unit tests (6): unset-is-passthrough, matching header, mismatched 404 with empty body, missing 404, allowlist bypass, allowlist-is-exact-match
  • Full platform `go test ./...` green
  • Self-hosted behavior: `MOLECULE_ORG_ID` unset → no change (passthrough verified)

What's NOT here (deferred to private control-plane repo)

  • `organizations` table / signup / WorkOS / Stripe / Fly Machines provisioner / subdomain router — all in `molecule-controlplane` (private), not here

🤖 Generated with Claude Code

Phase 32 foundation. The SaaS control plane (private molecule-controlplane
repo) provisions one platform instance per customer org on Fly Machines
and sets MOLECULE_ORG_ID=<uuid> on the machine. Its subdomain router
forwards requests with X-Molecule-Org-Id=<uuid>.

TenantGuard:
- When MOLECULE_ORG_ID is set → every non-allowlisted request must carry a
  matching X-Molecule-Org-Id header. Mismatched/missing header → 404 (not
  403 — don't leak tenant existence by letting probers distinguish "wrong
  org" from "route doesn't exist").
- When unset → passthrough. Self-hosted / dev / CI behavior unchanged.
- Allowlist is exact-match, not prefix — /health and /metrics only.

No orgs table, no signup, no billing, no Fly provisioning in this repo —
all that lives in the private control plane. The public repo's SaaS
surface is exactly this one middleware.

6 tests covering: unset-is-passthrough, matching header, mismatched
header 404 (with empty body), missing header 404, allowlist bypass, and
allowlist-is-exact-match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor Author

noteworthy: saas-foundation — tenant isolation middleware, Phase 32 PR #1. Cross-vendor-review tooling still unavailable (recurring gap logged tick-6); proceeding per tick-7 precedent. Self-reviewed: 6 tests cover passthrough / match / mismatch-404 / missing-404 / allowlist / exact-match; default-unset behavior preserves self-hosters.

@HongmingWang-Rabbit
HongmingWang-Rabbit merged commit 57a0568 into main Apr 14, 2026
7 checks passed
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the feat/saas-tenant-guard-middleware branch April 14, 2026 22:40
HongmingWang-Rabbit pushed a commit that referenced this pull request Apr 14, 2026
- CLAUDE.md: Go test count 740 → 746; MOLECULE_ORG_ID env var documented.
- PLAN.md: new "Recently launched (2026-04-14 tick-8)" block covering
  Phase 32 PR #1 + paired private molecule-controlplane repo scaffolding.
- docs/edit-history/2026-04-14.md: tick-8 breakdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit added a commit that referenced this pull request Apr 14, 2026
docs: sync documentation with 2026-04-14 tick-8 merge (#78)
molecule-ai Bot pushed a commit that referenced this pull request Apr 21, 2026
…ware

feat(platform): TenantGuard middleware — public repo's only SaaS hook (Phase 32 PR #1)
molecule-ai Bot pushed a commit that referenced this pull request Apr 21, 2026
- CLAUDE.md: Go test count 740 → 746; MOLECULE_ORG_ID env var documented.
- PLAN.md: new "Recently launched (2026-04-14 tick-8)" block covering
  Phase 32 PR #1 + paired private molecule-controlplane repo scaffolding.
- docs/edit-history/2026-04-14.md: tick-8 breakdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 21, 2026
docs: sync documentation with 2026-04-14 tick-8 merge (#78)
@molecule-ai

molecule-ai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

PR #78 — docs(mcp-server): add LOG_LEVEL env var ⚠️ LABEL MISMATCH — REQUIRES SECURITY REVIEW This PR is labeled as a docs PR but contains critical production security middleware code: - platform/internal/middleware/tenant_guard.go (+69 lines) — multi-tenant org isolation middleware - platform/internal/middleware/tenant_guard_test.go (+99 lines, 6 unit tests) - platform/internal/router/router.go (+7 lines) — wired into router - CLAUDE.md (+1/-1) What TenantGuard does (per PR description): - Reads MOLECULE_ORG_ID at boot; rejects all non-allowlisted requests without matching X-Molecule-Org-Id header with HTTP 404 (not 403 — intentional to avoid leaking tenant existence) - Allowlist: exact-match on /health and /metrics only - Affects all production API paths — Phase 32 Cloud SaaS public-repo foundation This needs: 1. Core-Security review — the 404-vs-403 design decision, the exact-match allowlist, and the middleware placement in the router chain 2. CP-BE review — router.go wiring 3. Label correction — should be labeled platform/security or feat/tenant-isolation, not docs I am not blocking merge but flagging this as needing appropriate team review before merge authority exercises judgment. 🤖 Technical Writer (docs perspective)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant