From 451c475b91487b3a9893fea7909f4f02672b49c3 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 16 Jul 2026 15:19:26 -0400 Subject: [PATCH] =?UTF-8?q?fix(guide):=20default=20served-audiences=20to?= =?UTF-8?q?=20`customer`=20=E2=80=94=20fail=20safe,=20not=20fail=20open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitea/workflows/cd-develop.yml | 5 +++-- .../src/main/resources/application.properties | 15 ++++++++++--- .../content/platform/guide-access/overview.md | 22 ++++++++++++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/cd-develop.yml b/.gitea/workflows/cd-develop.yml index ae9ecd3..6502dbc 100644 --- a/.gitea/workflows/cd-develop.yml +++ b/.gitea/workflows/cd-develop.yml @@ -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). diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 293afa8..4a6fb48 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -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} diff --git a/backend/src/main/resources/content/platform/guide-access/overview.md b/backend/src/main/resources/content/platform/guide-access/overview.md index 0aa947b..9e9935e 100644 --- a/backend/src/main/resources/content/platform/guide-access/overview.md +++ b/backend/src/main/resources/content/platform/guide-access/overview.md @@ -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.