diff --git a/CLAUDE.md b/CLAUDE.md
index b9aedb0..2862380 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -148,7 +148,7 @@ each project's **code-verified current state**, and the **gaps** between. See
| Config + validation (options / startup validation) | Adopted (lib `0.1.0`, on the feed; **all 4 apps**, pushed — the "local only / not yet pushed" caveat was stale) | Shared `ZB.MOM.WW.Configuration` lib | [`components/configuration/`](components/configuration/) | [`ZB.MOM.WW.Configuration/`](ZB.MOM.WW.Configuration/) |
| Audit (event model + writer seam) | Adopted (lib `0.1.0`, on the feed; **all 4 apps**, pushed to origin) | Shared `ZB.MOM.WW.Audit` lib | [`components/audit/`](components/audit/) | [`ZB.MOM.WW.Audit/`](ZB.MOM.WW.Audit/) |
| Galaxy Repository (object-hierarchy SQL browse + gRPC service) | **Adopted** (feed `0.1.0` + `0.2.0`; consumed at `0.2.0` by HistorianGateway **and mxaccessgw**, whose Server wires `AddZbGalaxyRepository` — verified 2026-07-18; the prior "mxaccessgw adoption is a follow-on" claim was stale) | Shared `ZB.MOM.WW.GalaxyRepository` lib | _(design in histsdk + design doc 2026-06-23)_ | [`ZB.MOM.WW.GalaxyRepository/`](ZB.MOM.WW.GalaxyRepository/) |
-| Secrets (encrypted store + `${secret:}` resolution) | Built (libs `0.1.2`, **published to the Gitea feed**; **HistorianGateway adopted + live-proven** 2026-07-16; **mxaccessgw adopted G-4/G-5/G-6 + merged to `origin/main` @ `e088dfa`** 2026-07-16, box-verified fail-closed + encrypt-at-rest; **ScadaBridge adopted G-3/G-4/G-5/G-6 + merged to `origin/main` @ `128f1596`** 2026-07-16, **G-3 live-proven vs the real production MxGateway gateway** `wonder-app-vd03:5120` (secret-ref ApiKey → Connected + browsed real Galaxy); **OtOpcUa adopted G-2/G-4/G-5/G-6 + merged to `origin/master` @ `872cf7e3`** (lmxopcua) 2026-07-16 — the last app, so **all four now adopted**; Layer-B driver secrets (Galaxy API key + OpcUaClient `Password`/`UserCertificatePassword`) resolve fail-closed at driver session-open, `AddZbSecrets` registered unconditionally so driver-only nodes work; **G-2 live-proven vs the real production MxGateway** `wonder-app-vd03:5120` — a `secret:`-ref Galaxy ApiKey resolved through OtOpcUa's real `GalaxyDriverBrowser` → dummy key `MxGatewayAuthenticationException`, real key → browsed the real Galaxy root (10 nodes); temp key minted+revoked+deleted); **G-8 KEK-rotation BUILT (lib bumped `0.1.2`→`0.1.3`, 2026-07-17):** `Rewrap` DEK primitive + CAS-guarded `ApplyRewrapAsync` + `KekRotationService.RewrapAllAsync` + `secret rewrap-all` CLI + operator runbook (`ZB.MOM.WW.Secrets/docs/operations/kek-rotation.md`), full suite green + CLI smoke + adversarial crypto review PASS (TOCTOU closed via compare-and-swap). **G-7 clustered replication DESIGNED + PLANNED** — fork resolved to **build the shared SQL-Server `ISecretStore`** (Akka replicator deferred phase-2), executable plan `docs/plans/2026-07-17-secrets-g7-*`. **Corrected 2026-07-18:** G-8 + the G-7 plan **ARE committed and pushed** on `main` (`d82d345`) — the "not yet committed/pushed" note was stale. The real open gap is that **`0.1.3` is NOT on the feed** (feed tops out at `0.1.2`, where all 4 apps are pinned), so KEK rotation is unconsumable until published; G-7 has no `ISecretStore` SQL implementation yet | Shared `ZB.MOM.WW.Secrets` lib (3 packages + CLI) | [`components/secrets/`](components/secrets/) | [`ZB.MOM.WW.Secrets/`](ZB.MOM.WW.Secrets/) |
+| Secrets (encrypted store + `${secret:}` resolution) | Built (libs **`0.1.3`**, **published to the Gitea feed**; **HistorianGateway adopted + live-proven** 2026-07-16; **mxaccessgw adopted G-4/G-5/G-6 + merged to `origin/main` @ `e088dfa`** 2026-07-16, box-verified fail-closed + encrypt-at-rest; **ScadaBridge adopted G-3/G-4/G-5/G-6 + merged to `origin/main` @ `128f1596`** 2026-07-16, **G-3 live-proven vs the real production MxGateway gateway** `wonder-app-vd03:5120` (secret-ref ApiKey → Connected + browsed real Galaxy); **OtOpcUa adopted G-2/G-4/G-5/G-6 + merged to `origin/master` @ `872cf7e3`** (lmxopcua) 2026-07-16 — the last app, so **all four now adopted**; Layer-B driver secrets (Galaxy API key + OpcUaClient `Password`/`UserCertificatePassword`) resolve fail-closed at driver session-open, `AddZbSecrets` registered unconditionally so driver-only nodes work; **G-2 live-proven vs the real production MxGateway** `wonder-app-vd03:5120` — a `secret:`-ref Galaxy ApiKey resolved through OtOpcUa's real `GalaxyDriverBrowser` → dummy key `MxGatewayAuthenticationException`, real key → browsed the real Galaxy root (10 nodes); temp key minted+revoked+deleted); **G-8 KEK-rotation BUILT (lib bumped `0.1.2`→`0.1.3`, 2026-07-17):** `Rewrap` DEK primitive + CAS-guarded `ApplyRewrapAsync` + `KekRotationService.RewrapAllAsync` + `secret rewrap-all` CLI + operator runbook (`ZB.MOM.WW.Secrets/docs/operations/kek-rotation.md`), full suite green + CLI smoke + adversarial crypto review PASS (TOCTOU closed via compare-and-swap). **G-7 clustered replication DESIGNED + PLANNED** — fork resolved to **build the shared SQL-Server `ISecretStore`** (Akka replicator deferred phase-2), executable plan `docs/plans/2026-07-17-secrets-g7-*`. **Corrected 2026-07-18:** G-8 + the G-7 plan **ARE committed and pushed** on `main` (`d82d345`) — the "not yet committed/pushed" note was stale. **`0.1.3` PUBLISHED to the feed 2026-07-18** (restore-verified), carrying a **transitive security pin** (`SQLitePCLRaw.lib.e_sqlite3` → `2.1.12`, advisory GHSA-2m69-gcr7-jv3q) that `0.1.2` lacked. All 4 apps still pinned at `0.1.2` → bump to consume KEK rotation + the security fix. G-7 has no `ISecretStore` SQL implementation yet | Shared `ZB.MOM.WW.Secrets` lib (3 packages + CLI) | [`components/secrets/`](components/secrets/) | [`ZB.MOM.WW.Secrets/`](ZB.MOM.WW.Secrets/) |
| LocalDb (embedded cache + 2-node sync) | Built (3 pkgs `0.1.0`, **published to the Gitea feed** 2026-07-18, restore-verified from a scratch consumer; no app adoption yet) | Shared `ZB.MOM.WW.LocalDb` lib | [`docs/plans/2026-07-17-localdb-design.md`](docs/plans/2026-07-17-localdb-design.md) | [`ZB.MOM.WW.LocalDb/`](ZB.MOM.WW.LocalDb/) |
The auth component is fully populated: a normalized [`spec`](components/auth/spec/SPEC.md), a
diff --git a/README.md b/README.md
index 310a30d..5762302 100644
--- a/README.md
+++ b/README.md
@@ -64,13 +64,13 @@ Status verified 2026-07-18 against the feed + consumer branches (see [Roadmap](#
| Component | Feed | Adoption | Folder |
|---|---|---|---|
-| Auth (login / identity / authz) | `0.1.0`–`0.1.4` | all four (pins drift `0.1.1`–`0.1.4`) | [`components/auth/`](components/auth/) |
+| Auth (login / identity / authz) | `0.1.0`–`0.1.5` | all four (pins drift `0.1.1`–`0.1.4`; `0.1.5` = security fix, unconsumed) | [`components/auth/`](components/auth/) |
| UI Theme (layout / tokens / components) | `0.2.0`–`0.3.1` | all four @ `0.3.1` | [`components/ui-theme/`](components/ui-theme/) |
| Audit (event model + writer seam) | `0.1.0` | all four (DEEP) | [`components/audit/`](components/audit/) |
| Config + validation | `0.1.0` | all four | [`components/configuration/`](components/configuration/) |
| Observability (metrics / traces / logs) | `0.1.0` | all four | [`components/observability/`](components/observability/) |
| Health (readiness / liveness / active-node) | `0.1.0` | all four | [`components/health/`](components/health/) |
-| Secrets (encrypted store + `${secret:}`) | `0.1.0`–`0.1.2` | all four @ `0.1.2` (lib `0.1.3` unpublished) | [`components/secrets/`](components/secrets/) |
+| Secrets (encrypted store + `${secret:}`) | `0.1.0`–`0.1.3` | all four @ `0.1.2` (`0.1.3` published, not yet consumed) | [`components/secrets/`](components/secrets/) |
| Galaxy Repository (SQL browse + gRPC) | `0.1.0`, `0.2.0` | HistorianGateway + mxaccessgw | _(design in `docs/plans/`)_ |
| LocalDb (embedded cache + 2-node sync) | `0.1.0` | none yet | _(design in `docs/plans/`)_ |
@@ -169,14 +169,16 @@ ZB_LDAP_IT=1 dotnet test # requires a reachable GLAuth (e.g. a sister repo's i
- ✅ Galaxy Repository — `ZB.MOM.WW.GalaxyRepository` `0.2.0` on the feed; consumed by HistorianGateway
**and mxaccessgw** (which wires `AddZbGalaxyRepository` in its Server; previously documented as a
pending follow-on — also stale).
-- ✅ Secrets — `ZB.MOM.WW.Secrets` (+ `.Abstractions`, `.Ui`) published through `0.1.2`; all four apps on `0.1.2`.
+- ✅ Secrets — `ZB.MOM.WW.Secrets` (+ `.Abstractions`, `.Ui`) published through **`0.1.3`** (KEK rotation,
+ 2026-07-18); all four apps still pinned at `0.1.2`.
- ✅ LocalDb — `ZB.MOM.WW.LocalDb` (+ `.Contracts`, `.Replication`) built at `0.1.0` and **published to the
feed 2026-07-18** (restore-verified from a scratch consumer).
**Open**
-- ⬜ Publish `ZB.MOM.WW.Secrets` `0.1.3` (KEK rotation — built, feed still tops out at `0.1.2`) and execute
- the G-7 clustered-replication plan — [`components/secrets/GAPS.md`](components/secrets/GAPS.md).
+- ⬜ Execute the G-7 clustered-replication plan (shared SQL-Server `ISecretStore`) — [`components/secrets/GAPS.md`](components/secrets/GAPS.md).
+- ⬜ Bump the three Auth consumers to `0.1.5` to pick up the SQLitePCLRaw security fix
+ (mxaccessgw + HistorianGateway `0.1.4`→`0.1.5`, ScadaBridge `0.1.3`→`0.1.5`).
- ⬜ Adopt `ZB.MOM.WW.LocalDb` in an app — no consumer references it yet.
- ⬜ Give LocalDb a `components/localdb/` entry; it is the one shared lib whose design lives only in
[`docs/plans/2026-07-17-localdb-design.md`](docs/plans/2026-07-17-localdb-design.md).
diff --git a/ZB.MOM.WW.Auth/Directory.Build.props b/ZB.MOM.WW.Auth/Directory.Build.props
index c3bb10e..cb4931c 100644
--- a/ZB.MOM.WW.Auth/Directory.Build.props
+++ b/ZB.MOM.WW.Auth/Directory.Build.props
@@ -5,7 +5,7 @@
enable
enable
latest
- 0.1.4
+ 0.1.5
true
diff --git a/ZB.MOM.WW.Auth/Directory.Packages.props b/ZB.MOM.WW.Auth/Directory.Packages.props
index 907fce8..a61d3d3 100644
--- a/ZB.MOM.WW.Auth/Directory.Packages.props
+++ b/ZB.MOM.WW.Auth/Directory.Packages.props
@@ -2,6 +2,8 @@
true
+
+ true
@@ -10,6 +12,9 @@
+
+
diff --git a/ZB.MOM.WW.Secrets/Directory.Packages.props b/ZB.MOM.WW.Secrets/Directory.Packages.props
index e8fbd27..a08993c 100644
--- a/ZB.MOM.WW.Secrets/Directory.Packages.props
+++ b/ZB.MOM.WW.Secrets/Directory.Packages.props
@@ -2,6 +2,8 @@
true
+
+ true
@@ -10,6 +12,9 @@
+
+
diff --git a/ZB.MOM.WW.Secrets/build/push.sh b/ZB.MOM.WW.Secrets/build/push.sh
new file mode 100755
index 0000000..5454f4e
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/build/push.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# push.sh — pack and push the ZB.MOM.WW.Secrets NuGet packages to the Gitea feed.
+#
+# Required environment variables:
+# GITEA_NUGET_SOURCE — full URL of the Gitea NuGet feed
+# e.g. https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json
+# GITEA_NUGET_KEY — Gitea access token with package:write permission
+#
+# Usage:
+# export GITEA_NUGET_SOURCE="https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json"
+# export GITEA_NUGET_KEY="your-gitea-token"
+# ./build/push.sh
+
+set -euo pipefail
+
+: "${GITEA_NUGET_SOURCE:?set GITEA_NUGET_SOURCE to your Gitea NuGet feed URL}"
+: "${GITEA_NUGET_KEY:?set GITEA_NUGET_KEY to your Gitea access token}"
+
+dotnet pack -c Release -o ./artifacts
+
+dotnet nuget push "./artifacts/*.nupkg" \
+ --source "$GITEA_NUGET_SOURCE" \
+ --api-key "$GITEA_NUGET_KEY" \
+ --skip-duplicate
diff --git a/components/auth/GAPS.md b/components/auth/GAPS.md
index 0b3edf3..0bd7c35 100644
--- a/components/auth/GAPS.md
+++ b/components/auth/GAPS.md
@@ -3,7 +3,16 @@
Divergence of each project from [`spec/SPEC.md`](spec/SPEC.md), and the ordered backlog to
reach the shared `ZB.MOM.WW.Auth` library. Status legend: ⛔ gap · 🟡 partial · ✅ matches.
-> **Current state (verified 2026-07-18):** feed carries `0.1.0`–`0.1.4`; consumer pins are
+> **`0.1.5` PUBLISHED 2026-07-18 — security fix.** `Auth.ApiKeys` `0.1.4` and earlier ship a
+> high-severity transitive vuln: `Microsoft.Data.Sqlite 10.0.7` pulls
+> `SQLitePCLRaw.lib.e_sqlite3 2.1.11` (GHSA-2m69-gcr7-jv3q). `0.1.5` pins the patched `2.1.12`
+> (same approach as `ZB.MOM.WW.LocalDb`); the `.nuspec` declares it directly, so consumers get
+> the fix on bump without pinning themselves. **No API change** — `0.1.5` is `0.1.4` plus the pin.
+> **All three ApiKeys consumers are still on vulnerable versions** (mxaccessgw `0.1.4`,
+> HistorianGateway `0.1.4`, ScadaBridge `0.1.3`) → bump to `0.1.5`. Note ScadaBridge's
+> `0.1.3`→`0.1.5` jump also crosses the `0.1.4` `ExpiresUtc` verifier change.
+>
+> **Current state (verified 2026-07-18):** feed carries `0.1.0`–`0.1.5`; consumer pins are
> OtOpcUa `0.1.1`, ScadaBridge `0.1.3`, MxAccessGateway `0.1.4`, **HistorianGateway `0.1.4`**
> (a fourth consumer this file predates, taking all four packages). OtOpcUa is three minors
> behind — it does not get the `0.1.4` API-key-expiry enforcement. Library suite: **215 passing,
diff --git a/components/secrets/GAPS.md b/components/secrets/GAPS.md
index 9fc5e12..bb58f67 100644
--- a/components/secrets/GAPS.md
+++ b/components/secrets/GAPS.md
@@ -7,13 +7,20 @@ construction).
## Current state (verified 2026-07-18)
-- **Source is at `0.1.3`; the feed is at `0.1.2`.** `ZB.MOM.WW.Secrets/Directory.Build.props`
- declares `0.1.3` and the G-8 KEK-rotation work (`KekRotationService`,
- `RewrapReport`, `secret rewrap-all`, `docs/operations/kek-rotation.md`) is **committed and
- pushed** on `main` (`d82d345`). The Gitea feed carries only `0.1.0`–`0.1.2` for all three
- packages. **→ `0.1.3` is unpublished: no app can consume KEK rotation yet.** (An earlier note
- claiming G-8 was "not yet committed/pushed" was stale — it is committed; only the *publish*
- is outstanding.)
+- **`0.1.3` PUBLISHED to the Gitea feed 2026-07-18** (all three packages; restore-verified from a
+ scratch consumer). Carries the G-8 KEK-rotation work (`KekRotationService`, `RewrapReport`,
+ `secret rewrap-all`, `docs/operations/kek-rotation.md`), committed on `main` at `d82d345`.
+ (An earlier note claiming G-8 was "not yet committed/pushed" was stale.)
+- **`0.1.3` also carries a transitive security fix that `0.1.2` does not.** Packing surfaced
+ NU1903: `Microsoft.Data.Sqlite 10.0.7` pulls `SQLitePCLRaw.lib.e_sqlite3 2.1.11`, which has a
+ **high-severity** advisory (GHSA-2m69-gcr7-jv3q). Fixed the way `ZB.MOM.WW.LocalDb` already
+ did — `CentralPackageTransitivePinningEnabled` + a pin to the patched `2.1.12`. Bumping
+ `Microsoft.Data.Sqlite` does **not** help (even `10.0.10` still pulls `2.1.11`); the pin is the
+ fix. Verified: the shipped `.nuspec` declares `SQLitePCLRaw.lib.e_sqlite3 >= 2.1.12` as a direct
+ dependency, so **consumers get the patched native lib without needing their own pin**, and a
+ fresh consumer restoring `0.1.3` scans clean.
+- **All four apps are still pinned at `0.1.2`** — they get neither KEK rotation nor the security
+ fix until bumped.
- **All four apps are pinned to `0.1.2`** — OtOpcUa, MxAccessGateway, ScadaBridge take
`Secrets` + `.Abstractions` + `.Ui`; HistorianGateway takes `Secrets` + `.Ui`.
- **G-7 clustered replication is designed + planned, not built** — plan at
@@ -23,7 +30,7 @@ construction).
### Open
-- ⬜ Publish `ZB.MOM.WW.Secrets` `0.1.3` (+ `.Abstractions`, `.Ui`) to the Gitea feed.
+- ⬜ Bump the four apps `0.1.2` → `0.1.3` to pick up KEK rotation + the SQLitePCLRaw security fix.
- ⬜ Execute the G-7 plan (shared SQL-Server `ISecretStore`).
- ⬜ Per-app live wonder gates for the G-8 rotation path.