fix(guide): default served-audiences to customer — fail safe, not fail open
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Successful in 2m18s

The environment cap decided what a Guide instance serves, but defaulted to ALL
four tiers. That makes the safe configuration the one you have to remember: a
missing or misspelled GUIDE_SERVED_AUDIENCES in production would serve the
Architect and Claude tiers to any MSP_ADMIN — and MSP_ADMIN is a customer-facing
role (QDS log in as MSP_ADMIN). One forgotten env line was all it took.

Default is now `customer`. Environments opt UP explicitly:
  production   -> unset (customer only: modules + Customer tab)
  bcos.dev/DLX -> customer,internal,dev,bcos (set in that instance's compose)

bcos.dev's compose was pinned to all four tiers first (hiveiq-openmetal-dev
b2d4e00, already live on .251) so this change cannot narrow it.

Also updates platform.guide-access, which documented the old default and told
readers the opposite of what the code now does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 15:19:26 -04:00
parent e54b994391
commit 451c475b91
3 changed files with 34 additions and 8 deletions
+3 -2
View File
@@ -5,8 +5,9 @@ name: CD - Develop (guide → bcos.dev)
# not enough. This automates that on every push to develop: build → push :dev → deploy to bcos.dev.
#
# Auto-deploy is intentional and scoped to the DEV environment only (docs, low blast radius).
# Audience filtering is runtime (GUIDE_SERVED_AUDIENCES): bcos.dev serves all four tiers; prod
# (a future cd-main, gated) serves only customer,internal — so dev/bcos content never leaves dev.
# Audience filtering is runtime (GUIDE_SERVED_AUDIENCES). The app default is `customer` (fail-safe);
# bcos.dev opts UP to all four tiers explicitly in its compose. Production leaves it unset and so
# serves customer only — internal/dev/bcos content never leaves dev, even if a config line is missed.
# Auto-deploy live: BCOS_DEV_DEPLOY_KEY provisioned (scoped forced-command key on .251).
@@ -23,9 +23,18 @@ internal.secret=${INTERNAL_SECRET:dev-internal-secret}
cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:5188}
# Audience tiers this instance serves (per-environment cap).
# bcos.dev / DLX = all four; production = customer,internal (dev/bcos never leave dev).
guide.served-audiences=${GUIDE_SERVED_AUDIENCES:customer,internal,dev,bcos}
# Audience tiers this instance serves (per-environment cap), checked BEFORE any role
# gating — a tier that is not served never leaves the process, whatever the caller's role.
#
# The default is deliberately the SAFEST tier, not the fullest: a missing or misspelled
# GUIDE_SERVED_AUDIENCES must never be the reason internal content reaches a customer.
# MSP_ADMIN is a customer-facing role (QDS log in as MSP_ADMIN), so a fail-open default
# would expose the Architect/Claude tiers to customers on one forgotten env line.
#
# Each environment opts UP explicitly:
# production -> customer (leave unset; the default is correct)
# bcos.dev/DLX -> customer,internal,dev,bcos (set in that instance's compose)
guide.served-audiences=${GUIDE_SERVED_AUDIENCES:customer}
# In-guide feedback → Gitea issues (server-side token; reaches Gitea via public route)
gitea.base-url=${GITEA_BASE_URL:https://hiveiq-gitea.directlx.dev}
@@ -92,6 +92,22 @@ not document itself apart from this page.
## Environment cap
`guide.served-audiences` (env `GUIDE_SERVED_AUDIENCES`, `application.properties`) caps which tiers
an instance serves at all, *before* the role check. It currently defaults to **all four tiers**
(`customer,internal,dev,bcos`). If the Guide is ever deployed to production, set this explicitly —
the default serves everything.
an instance serves at all, *before* the role check. A tier that is not served never leaves the
process — role gating can only narrow the result further, never widen it past the cap.
The default is **`customer`** — the safest tier, not the fullest. Each environment opts *up*
explicitly:
| Environment | `GUIDE_SERVED_AUDIENCES` | Serves |
|---|---|---|
| **Production** | *(unset — default)* | `customer` only: modules + the Customer tab |
| **bcos.dev / DLX** | `customer,internal,dev,bcos` | all four tiers |
**Why the default is the strict one:** MSP_ADMIN is a customer-facing role (QDS log in as
MSP_ADMIN), so a fail-open default would mean one forgotten env line exposes the Architect and
Claude tiers to a customer. The safe case has to be the lazy case. bcos.dev carries the permissive
value explicitly in its compose.
**Note:** the cap governs what is *served*, not what is *present*. All content ships inside the
image regardless of tier, so it is readable by anyone with registry access or a shell in the
container. The cap is a serving boundary, not an at-rest one.