# Environment Variables — SCADA/OT family

> Cross-project audit of every environment variable used or read by the sister projects
> and the shared `ZB.MOM.WW.*` libraries. Compiled **2026-06-03** by sweeping C# reads,
> Docker/compose, `launchSettings.json`, shell/PowerShell scripts, and CI for each repo.
> This is a **summary index** — when a value matters operationally, confirm against the
> cited `file:line` in the owning repo (paths below are relative to each project root).

## Scope

| Project | Root | Covered |
|---|---|---|
| OtOpcUa | `~/Desktop/OtOpcUa` | Host, Galaxy/Historian drivers, docker-dev, tests, CI |
| MxAccessGateway | `~/Desktop/MxAccessGateway` | Server (x64), Worker (x86), client CLI, tests, pack script |
| ScadaBridge | `~/Desktop/ScadaBridge` | Host (Central/Site), CLI, `docker/`, `docker-env2/`, `infra/`, tests |
| Shared libs | `~/Desktop/scadaproj/ZB.MOM.WW.*` | Auth, Theme, Health, Telemetry, Configuration, Audit (code + build scripts) |

## How env vars reach these apps

All four .NET apps call `AddEnvironmentVariables()`, so **any** configuration key is overridable
from the environment using the **double-underscore (`__`) → colon (`:`)** convention
(`ScadaBridge__InboundApi__ApiKeyPepper` overrides `ScadaBridge:InboundApi:ApiKeyPepper`). Array
indices use a trailing `__0`, `__1` (`Cluster__SeedNodes__0`). Because *every* options key is
technically settable this way, the tables below split into:

- **Direct reads / operationally-set** — `Environment.GetEnvironmentVariable(...)` in code, or
  values actually set in compose/launchSettings/scripts. These are the ones you'll really touch.
- **Config keys overridable via `__`** — the validated/notable options that are normally in
  `appsettings*.json` but are commonly (or required to be) supplied via environment in containers.
  Not every options key is reproduced — only validated, secret, or container-set ones.

> **Secrets:** rows marked 🔒 are secrets. Per the Auth/Config normalization, peppers/keys/passwords
> are **per-environment secrets injected out-of-band** (secret store / orchestrator), never committed.
> The dev-only values that *do* appear in compose are explicitly insecure placeholders for the local
> clusters — see `scadabridge-local-deploy-gotchas` and `docs/operations/inbound-api-key-reissue.md`.

---

## 1. OtOpcUa

### 1.1 Direct reads / operationally-set (runtime)

| Variable | Where | Purpose | Req? / default | Process |
|---|---|---|---|---|
| `OTOPCUA_ROLES` | `src/Server/.../Host/Program.cs:31` | Comma-list of roles (admin, driver) for conditional wiring | optional | Host |
| `ASPNETCORE_ENVIRONMENT` | `Host/Properties/launchSettings.json:9` | ASP.NET Core environment | optional / `Production` | Host |
| `ASPNETCORE_URLS` | `docker-dev/docker-compose.yml` | Kestrel bind address/port | optional | Host |
| `GALAXY_MXGW_API_KEY` 🔒 | `docker-dev/docker-compose.yml`; resolved in `GalaxyDriver.cs:466` | mxaccessgw API key for the Galaxy driver | required if Galaxy driver deployed | Driver (Galaxy) |
| `OTOPCUA_CONFIG_CONNECTION` 🔒 | `Configuration/DesignTimeDbContextFactory.cs:25` | SQL connection for EF design-time (`dotnet ef`) | required for migrations tooling | build-time |
| `OTOPCUA_HISTORIAN_PIPE` | `Driver.Historian.Wonderware/Program.cs:32` | Named-pipe name for the Historian sidecar IPC | required | Historian sidecar |
| `OTOPCUA_ALLOWED_SID` | `…/Program.cs:34` | Windows SID allowed to connect to the sidecar | required | Historian sidecar |
| `OTOPCUA_HISTORIAN_SECRET` 🔒 | `…/Program.cs:36` | Shared secret for named-pipe auth | required | Historian sidecar |
| `OTOPCUA_HISTORIAN_ENABLED` | `…/Program.cs:48` | Init the Historian SDK (else pipe-only) | optional / `false` | Historian sidecar |
| `OTOPCUA_HISTORIAN_SERVER` | `…/Program.cs:89` | Wonderware Historian host | optional / `localhost` | Historian sidecar |
| `OTOPCUA_HISTORIAN_PORT` | `…/Program.cs:90` | Historian port | optional / `32568` | Historian sidecar |
| `OTOPCUA_HISTORIAN_INTEGRATED` | `…/Program.cs:91` | Use Windows Integrated Security | optional / `true` | Historian sidecar |
| `OTOPCUA_HISTORIAN_USER` | `…/Program.cs:92` | SQL user (when not integrated) | optional | Historian sidecar |
| `OTOPCUA_HISTORIAN_PASS` 🔒 | `…/Program.cs:93` | SQL password (when not integrated) | optional | Historian sidecar |
| `OTOPCUA_HISTORIAN_TIMEOUT_SEC` | `…/Program.cs:94` | SQL command timeout (s) | optional / `30` | Historian sidecar |
| `OTOPCUA_HISTORIAN_MAX_VALUES` | `…/Program.cs:95` | Max values per read query | optional / `10000` | Historian sidecar |
| `OTOPCUA_HISTORIAN_COOLDOWN_SEC` | `…/Program.cs:96` | Failure cooldown before retry (s) | optional / `60` | Historian sidecar |
| `OTOPCUA_HISTORIAN_SERVERS` | `…/Program.cs:99` | Comma-list of historian servers for failover | optional | Historian sidecar |
| `OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED` | `…/Program.cs:125` | Enable alarm-event writer to historian | optional / `true` when enabled | Historian sidecar |

> The Galaxy API key supports prefix resolution (`GalaxyDriver.cs:466`): `env:VAR` (read another
> env var), `file:PATH` (read a file), `dev:LITERAL` (dev only), or an unprefixed literal.

### 1.2 Config keys overridable via `__` (set in `docker-dev/docker-compose.yml`)

| Variable | Purpose | Req? |
|---|---|---|
| `ConnectionStrings__ConfigDb` 🔒 | SQL connection for the OtOpcUa config DB | required |
| `Cluster__Hostname` / `Cluster__Port` / `Cluster__PublicHostname` | Akka remoting bind/advertise | required |
| `Cluster__SeedNodes__0` | Initial seed node URL | required |
| `Cluster__Roles__0` (`__1`) | Cluster role assignment (admin/driver) | required |
| `Security__Jwt__SigningKey` 🔒 | JWT signing key (≥32 bytes) | required |
| `Security__Jwt__Issuer` / `Security__Jwt__Audience` | JWT claims | required |
| `Security__Ldap__*` | LDAP host/bind (in `appsettings.admin*.json`) | per-deploy |
| `Authentication__Ldap__DevStubMode` | Dev LDAP stub (any user → FleetAdmin) — **removed from docker-dev**; `docker-dev` now binds the shared GLAuth on `10.100.0.35:3893` | optional / `false` |

### 1.3 Docker infra & test fixtures (OtOpcUa)

- **docker-dev SQL:** `ACCEPT_EULA=Y`, `SA_PASSWORD` 🔒 (`OtOpcUa!Dev123` dev-only), `MSSQL_PID=Developer`.
- **docker-dev seed (`seed/entrypoint.sh`):** `SQL_HOST` / `SQL_USER` / `SQL_PASSWORD` 🔒 / `SQL_DATABASE` (defaults `sql`/`sa`/`OtOpcUa!Dev123`/`OtOpcUa`).
- **Integration compose:** SQL (`SA_PASSWORD` 🔒 `OtOpcUa!Harness123`) + OpenLDAP (`LDAP_ROOT`, `LDAP_ADMIN_USERNAME`, `LDAP_ADMIN_PASSWORD` 🔒, `LDAP_USERS`, `LDAP_PASSWORDS` 🔒, `LDAP_USER_DC`) — this OpenLDAP instance is **integration-test-only**; the standard DEV auth is the shared GLAuth at `10.100.0.35:3893` (`dc=zb,dc=local`, see `scadaproj/infra/glauth/`).
- **Test-fixture overrides (all optional, default to the shared host `10.100.0.35`):** `OPCUA_SIM_ENDPOINT`, `MODBUS_SIM_ENDPOINT`, `MODBUS_SIM_PROFILE`, `AB_SERVER_ENDPOINT`, `AB_SERVER_PROFILE`, `S7_SIM_ENDPOINT`, `OTOPCUA_FOCAS_SIM_ENDPOINT`, `OTOPCUA_FOCAS_SIM_PROFILE`, `TWINCAT_HOST`, `TWINCAT_NETID`, `TWINCAT_PORT`, `AB_LEGACY_ENDPOINT`, `AB_LEGACY_CIP_PATH`, `AB_LEGACY_COMPOSE_PROFILE`, `OTOPCUA_CONFIG_TEST_SERVER`, `OTOPCUA_CONFIG_TEST_SA_PASSWORD` 🔒, `OTOPCUA_HARNESS_USE_SQL`, `OTOPCUA_HARNESS_USE_LDAP`, `MXGW_ENDPOINT`, `D1_SMOKE_OUT`.
- **CI (`.github/workflows/v2-*.yml`):** `DOTNET_NOLOGO=1`, `DOTNET_CLI_TELEMETRY_OPTOUT=1`.

---

## 2. MxAccessGateway

> Ships **no** Docker/compose assets — it's a Windows-native app (x64 .NET 10 Server + x86 .NET 4.8 Worker).
> The Server spawns the Worker via `ProcessStartInfo.Environment`, passing the two `MXGATEWAY_WORKER_*` vars below.

### 2.1 Direct reads / operationally-set (runtime)

| Variable | Where | Purpose | Req? / default | Process |
|---|---|---|---|---|
| `MXGATEWAY_WORKER_NONCE` 🔒 | Server `WorkerProcessLauncher.cs:180` → Worker `WorkerOptionsParser.cs:78` | Per-session handshake nonce (kept off the command line) | required (generated per session) | Server→Worker |
| `MXGATEWAY_WORKER_PIPE_CONNECT_ATTEMPT_TIMEOUT_MS` | Server `WorkerProcessLauncher.cs:181` → Worker `WorkerPipeClient.cs:255` | Per-attempt named-pipe connect timeout | optional / `2000` | Server→Worker |
| `ASPNETCORE_CONTENTROOT` | `GatewayApplication.cs:130` | Content-root override (logs/wwwroot) | optional / auto | Server |
| `ASPNETCORE_ENVIRONMENT` | `Server/Properties/launchSettings.json:10` | Environment selector | optional / `Production` | Server |

### 2.2 Config keys overridable via `__` (notable)

| Variable | Purpose | Req? |
|---|---|---|
| `MxGateway__ApiKeyPepper` 🔒 | HMAC pepper for API-key secrets in the SQLite auth DB | required when auth Mode=ApiKey |
| `MxGateway__Authentication__Mode` / `__SqlitePath` / `__PepperSecretName` | Auth mode, auth DB path, pepper config-key name | per-deploy |
| `MxGateway__Worker__ExecutablePath` / `__WorkingDirectory` / `__StartupTimeoutSeconds` / `__PipeConnectAttemptTimeoutMilliseconds` | x86 worker launch config | per-deploy |
| `MxGateway__Sessions__*` / `MxGateway__Events__QueueCapacity` | Session pool & event queue tuning | optional |
| `MxGateway__Galaxy__ConnectionString` 🔒 | SQL connection for Galaxy browse RPCs | per-deploy |
| `MxGateway__Alarms__*` / `MxGateway__Dashboard__*` | Alarm monitor & dashboard config | optional |
| `MxGateway__Telemetry__Exporter` / `__OtlpEndpoint` | OpenTelemetry exporter selection / OTLP endpoint | optional |
| `MxGateway__Tls__SelfSignedCertPath` | Self-signed PFX path | optional |
| `Kestrel__Endpoints__Http__Url` / `__Protocols`, `Kestrel__Endpoints__Dashboard__Url` | gRPC (h2c) + dashboard endpoints | per-deploy |

### 2.3 Client CLI, tests & build script (MxGateway)

- **Client CLI / smoke tests:** `MXGATEWAY_ENDPOINT` (default `http://localhost:5000`), `MXGATEWAY_API_KEY` 🔒 (`MxGatewayClientCli.cs:289`).
- **Live-test opt-in gates (set to `1` to enable; otherwise skipped):** `MXGATEWAY_RUN_LIVE_MXACCESS_TESTS`, `MXGATEWAY_RUN_LIVE_LDAP_TESTS`.
- **Live-test params (optional):** `MXGATEWAY_LIVE_MXACCESS_WORKER_EXE`, `_ITEM`, `_CLIENT_NAME`, `_EVENT_TIMEOUT_SECONDS`, `_WRITE_SECURED_USER`, `_WRITE_SECURED_PASSWORD` 🔒, `MXGATEWAY_LIVE_GALAXY_CONN` 🔒.
- **Pack/publish (`scripts/pack-clients.ps1`):** `GITEA_USERNAME`, `GITEA_TOKEN` 🔒 (required with `-Publish`), `JAVA_HOME` (Java client build).

---

## 3. ScadaBridge

> Role is selected by `SCADABRIDGE_CONFIG` (`Central`|`Site`), which picks `appsettings.{role}.json`
> (falls back to `DOTNET_ENVIRONMENT`, then `Production`). The pre-host `StartupValidator` /
> `ConfigPreflight` enforces the **required** keys below and fails fast if any are missing/invalid.

### 3.1 Direct reads (C#)

| Variable | Where | Purpose | Req? / default | Scope |
|---|---|---|---|---|
| `SCADABRIDGE_CONFIG` | `Host/Program.cs:31` | Role selector → appsettings file | optional (→ `DOTNET_ENVIRONMENT` → `Production`) | both |
| `DOTNET_ENVIRONMENT` | `Host/Program.cs:32` | Fallback role/env selector | optional | both |
| `ASPNETCORE_ENVIRONMENT` | `Host/Program.cs:245` | Dev-mode check | optional | both |
| `SCADABRIDGE_DESIGNTIME_CONNECTIONSTRING` 🔒 | `ConfigurationDatabase/DesignTimeDbContextFactory.cs:48` | EF tooling connection (`dotnet ef`) | optional (build-time) | design-time |
| `SCADABRIDGE_MANAGEMENT_URL` | `CLI/CliConfig.cs:72` | Management API URL for the CLI | optional | CLI |
| `SCADABRIDGE_FORMAT` | `CLI/CliConfig.cs:76` | CLI default output format | optional / `json` | CLI |
| `SCADABRIDGE_USERNAME` | `CLI/CliConfig.cs:81` | CLI LDAP username (safer than `--password`) | optional | CLI |
| `SCADABRIDGE_PASSWORD` 🔒 | `CLI/CliConfig.cs:85` | CLI LDAP password | optional | CLI |

### 3.2 Config keys required at startup (`__` form, enforced by `StartupValidator`)

| Variable | Purpose | Constraint | Scope |
|---|---|---|---|
| `ScadaBridge__Node__Role` | `Central` or `Site` | required | all |
| `ScadaBridge__Node__NodeHostname` | Hostname advertised to cluster | required, non-empty | all |
| `ScadaBridge__Node__RemotingPort` | Akka remoting TCP port | required, 1–65535 (default 8081) | all |
| `ScadaBridge__Cluster__SeedNodes__0` / `__1` | Akka seed addresses | required, ≥2 entries | all |
| `ScadaBridge__Database__ConfigurationDb` 🔒 | SQL config-DB connection | required | Central |
| `ScadaBridge__Security__JwtSigningKey` 🔒 | Cookie-JWT HMAC key | required, ≥32 chars | Central |
| `ScadaBridge__Security__Ldap__Server` | LDAP host | required | Central |
| `ScadaBridge__InboundApi__ApiKeyPepper` 🔒 | Peppered-HMAC inbound API-key pepper | **required, ≥16 chars** | Central |
| `ScadaBridge__Node__SiteId` | Site identifier | required | Site |
| `ScadaBridge__Database__SiteDbPath` | Site-local SQLite path | required | Site |
| `ScadaBridge__Node__GrpcPort` | gRPC streaming port | default 8083; ≠ remoting/metrics | Site |
| `ScadaBridge__Node__MetricsPort` | Prometheus `/metrics` port | default 8084; ≠ remoting/grpc | Site |

> LDAP service-account fields (`ScadaBridge__Security__Ldap__ServiceAccountDn`,
> `__ServiceAccountPassword` 🔒, `__SearchBase`) are validated **post-host** by `LdapOptionsValidator`
> for Central, not in the fail-fast pre-host pass.

### 3.3 Config keys overridable via `__` (optional, not validated)

Large surface — all bound from the role appsettings and overridable via env. Grouped by module
(each prefixed `ScadaBridge__`):

- **Node:** `Node__NodeName`. **Database:** `Database__MachineDataDb` 🔒, `Database__SkipMigrations`.
- **Security:** `Security__Ldap__Port|Transport|AllowInsecure`, `Security__JwtExpiryMinutes`, `Security__IdleTimeoutMinutes`, `Security__JwtRefreshThresholdMinutes`, `Security__RequireHttpsCookie`.
- **Cluster (SBR):** `Cluster__SplitBrainResolverStrategy|StableAfter|HeartbeatInterval|FailureDetectionThreshold|MinNrOfMembers|DownIfAlone`.
- **Communication:** `Communication__DeploymentTimeout|LifecycleTimeout|QueryTimeout|TransportHeartbeatInterval|TransportFailureThreshold`, `Communication__CentralContactPoints__0…` (Site→Central ClusterClient).
- **HealthMonitoring:** `HealthMonitoring__ReportInterval|OfflineTimeout|CentralOfflineTimeout`.
- **InboundApi:** `InboundApi__DefaultMethodTimeout|MaxRequestBodyBytes`.
- **Notification (SMTP):** `Notification__SmtpServer|SmtpPort|AuthMode|FromAddress|ConnectionTimeoutSeconds|MaxConcurrentConnections`.
- **NotificationOutbox (Central):** `NotificationOutbox__DispatchInterval|DispatchBatchSize|StuckAgeThreshold|TerminalRetention|PurgeInterval|DeliveredKpiWindow`.
- **Transport (Central):** `Transport__SourceEnvironment|BundleSessionTtlMinutes|MaxBundleSizeMb|MaxBundleEntryDecompressedMb|MaxBundleEntryCount|MaxBundleEntryCompressionRatio|MaxUnlockAttemptsPerSession|MaxUnlockAttemptsPerIpPerHour|Pbkdf2Iterations`.
- **Logging:** `Logging__MinimumLevel`.
- **DataConnection (Site):** `DataConnection__ReconnectInterval|TagResolutionRetryInterval|WriteTimeout|StableConnectionThreshold`.
- **StoreAndForward (Site):** `StoreAndForward__SqliteDbPath|ReplicationEnabled|DefaultRetryInterval|DefaultMaxRetries`.
- **SiteEventLog (Site):** `SiteEventLog__RetentionDays|MaxStorageMb|DatabasePath|PurgeInterval`.
- **SiteRuntime (Site):** `SiteRuntime__StartupBatchSize|StartupBatchDelayMs|MaxScriptCallDepth|ScriptExecutionTimeoutSeconds|StreamBufferSize|ScriptExecutionThreadCount`.
- **Telemetry (Site):** `Telemetry__Exporter|OtlpEndpoint`.

### 3.4 Docker / infra / build (ScadaBridge)

- **`docker/docker-compose.yml`** (3-site cluster) — Central: `SCADABRIDGE_CONFIG=Central`, `ASPNETCORE_ENVIRONMENT=Development`, `ASPNETCORE_URLS=http://+:5000`, `ScadaBridge__InboundApi__ApiKeyPepper` 🔒 = `dev-only-insecure-pepper-docker-cluster-0001`; Sites: `SCADABRIDGE_CONFIG=Site`.
- **`docker-env2/docker-compose.yml`** (1-site cluster) — same shape, pepper 🔒 = `dev-only-insecure-pepper-env2-cluster-0001` (distinct per environment).
- **`infra/docker-compose.yml`** — MSSQL (`ACCEPT_EULA=Y`, `MSSQL_SA_PASSWORD` 🔒 `ScadaBridge_Dev1#`, `MSSQL_PID=Developer`), Mailpit SMTP (`MP_SMTP_AUTH_ACCEPT_ANY=1`, `MP_SMTP_AUTH_ALLOW_INSECURE=1`, `MP_MAX_MESSAGES=500`), REST API (`API_NO_AUTH=0`, `PORT=5200`).
- **`docker/Dockerfile` build args:** `NUGET_GITEA_USER` / `NUGET_GITEA_PASS` 🔒 → injected as `NuGetPackageSourceCredentials_dohertj2-gitea`. **`docker/build.sh`** reads `MXGW_NUGET_USER` / `MXGW_NUGET_PASS` 🔒 from host env (blank ⇒ anonymous feed).
- **Seed/init scripts** (`docker*/init-db.sh`, `seed-sites.sh`) hardcode the dev SA password 🔒 `ScadaBridge_Dev1#`.
- **`launchSettings.json`** profiles set `DOTNET_ENVIRONMENT`, `ASPNETCORE_ENVIRONMENT`, `SCADABRIDGE_CONFIG` (`Central`/`Site`).

---

## 4. Shared libraries (`ZB.MOM.WW.*`)

The libraries deliberately read almost nothing from the environment directly — config flows through
strongly-typed options bound by the **consuming** app. Notable exceptions:

| Variable | Where | Purpose | Req? / default |
|---|---|---|---|
| `GITEA_NUGET_SOURCE` | `ZB.MOM.WW.{Auth,Theme,Audit}/build/push.sh:16` | Gitea NuGet feed URL for `dotnet nuget push` | required to publish |
| `GITEA_NUGET_KEY` 🔒 | `…/build/push.sh:17` | Gitea token (`package:write`) | required to publish |
| `ZB_LDAP_IT` | `ZB.MOM.WW.Auth/tests/.../GLAuthIntegrationTests.cs:48` | Gate flag (`1`) to run the live LDAP test | optional (skipped if unset) |
| `ZB_LDAP_SERVER` / `_PORT` / `_BASE` / `_SVC_DN` / `_SVC_PW` 🔒 / `_USER` / `_PW` 🔒 / `_USERATTR` | `…/GLAuthIntegrationTests.cs:52-59` | Live LDAP test connection params | optional (defaults: `localhost`/`3893`/`dc=zb,dc=local`/…); point at the shared GLAuth (`10.100.0.35:3893`, `dc=zb,dc=local`) for the live test |

**Telemetry:** `ZB.MOM.WW.Telemetry` does **not** read standard `OTEL_*` env vars — OTel identity/exporter
come from `ZbTelemetryOptions` passed to `AddZbTelemetry()`. It only reads system properties
(`Environment.MachineName`, `Environment.ProcessId` in `ZbResource.cs:20`) to form
`service.instance.id` / `host.name`. Consuming apps that want OTLP wire it via their own
`…Telemetry__OtlpEndpoint` config key (see MxGateway §2.2, ScadaBridge §3.3).

**Health, Configuration, Audit, Theme:** no direct environment-variable reads (code or build).

---

## 5. Cross-cutting cheat sheet

### Standard framework vars (honored everywhere)
`ASPNETCORE_ENVIRONMENT`, `ASPNETCORE_URLS`, `ASPNETCORE_CONTENTROOT`, `DOTNET_ENVIRONMENT`,
`DOTNET_NOLOGO`, `DOTNET_CLI_TELEMETRY_OPTOUT`.

### Build/publish (Gitea NuGet feed) — naming differs per repo
| Repo | Vars |
|---|---|
| Shared libs | `GITEA_NUGET_SOURCE`, `GITEA_NUGET_KEY` 🔒 |
| MxGateway pack | `GITEA_USERNAME`, `GITEA_TOKEN` 🔒 |
| ScadaBridge image build | `MXGW_NUGET_USER`, `MXGW_NUGET_PASS` 🔒 (→ `NUGET_GITEA_USER`/`NUGET_GITEA_PASS` build args) |

### Secrets inventory (🔒) — inject out-of-band, never commit real values
- **Peppers:** `ScadaBridge__InboundApi__ApiKeyPepper` (≥16, Central-only), `MxGateway__ApiKeyPepper`.
- **Signing/JWT keys:** `Security__Jwt__SigningKey` (OtOpcUa), `ScadaBridge__Security__JwtSigningKey`.
- **API keys / nonces:** `GALAXY_MXGW_API_KEY`, `MXGATEWAY_API_KEY`, `MXGATEWAY_WORKER_NONCE`, `OTOPCUA_HISTORIAN_SECRET`.
- **DB / LDAP / SMTP passwords:** all `*SA_PASSWORD`/`MSSQL_SA_PASSWORD`, `ConnectionStrings__ConfigDb`, `ScadaBridge__Database__ConfigurationDb`, `*Ldap*Password`, `SCADABRIDGE_PASSWORD`, `OTOPCUA_HISTORIAN_PASS`.
- **Feed tokens:** `GITEA_NUGET_KEY`, `GITEA_TOKEN`, `MXGW_NUGET_PASS`/`NUGET_GITEA_PASS`.

> The only secret-typed values that legitimately appear in source are the **dev-only, insecure**
> local-cluster placeholders in `docker*/docker-compose.yml` and the dev SA passwords in the
> `infra`/seed scripts — usable for the local stacks only, never as real secrets.
