From 67de24c3da794995bbf588c7498acf892cfca7a2 Mon Sep 17 00:00:00 2001 From: ajitku3 Date: Mon, 10 Aug 2026 09:39:11 +0530 Subject: [PATCH 1/3] docs: design FPV-1237 cluster validation fix Co-authored-by: Cursor --- ...-1237-mercury-cluster-validation-design.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md diff --git a/docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md b/docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md new file mode 100644 index 00000000000..a65d57896cd --- /dev/null +++ b/docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md @@ -0,0 +1,25 @@ +# FPV-1237 Mercury Cluster Validation Design + +## Goal + +Prevent an untrusted Mercury `ActiveClusterStatusEvent` from remapping a service to a cluster that belongs to a different service. + +## Scope + +Change `ServicesV2.switchActiveClusterIds()` in `@webex/webex-core`. The Mercury event handler and server-side publish authorization are outside this client-side defense-in-depth fix. + +## Design + +Treat pushed active-cluster values as hints. For every `[serviceName, clusterId]` pair, find the cluster in the current U2C catalog and require its `serviceName` to equal the map key. If any cluster is missing or belongs to another service, reject the entire pushed map and force an authoritative U2C catalog refresh. Apply the map only when every pair is valid. + +This preserves valid cluster migrations, retains the existing refresh behavior for unknown clusters, and prevents partial application of mixed valid and invalid input. + +## Testing + +Add a webex-core unit regression test that uses an existing cluster ID belonging to a different service. Verify that `initServiceCatalogs(true)` is called and `_updateActiveServices()` is not called. Retain the existing tests for accepted matching clusters and missing clusters. + +Run the targeted ServicesV2 unit tests, the `@webex/webex-core` unit suite, and the webex-core source build with Node.js 22.14. + +## Security Considerations + +The patch validates both identifier existence and service ownership at the trust boundary. It does not authenticate Mercury events; authoritative publisher authorization remains a server-side requirement. No credentials, tokens, cryptographic keys, or certificates are added. From 8d147a2c11988429dde40e6b8a1e1e3ec425250d Mon Sep 17 00:00:00 2001 From: ajitku3 Date: Mon, 10 Aug 2026 09:53:39 +0530 Subject: [PATCH 2/3] fix(webex-core): validate Mercury cluster migrations Co-authored-by: Cursor --- ...-10-fpv-1237-mercury-cluster-validation.md | 99 +++++++++++++++++++ .../src/lib/services-v2/services-v2.ts | 11 +-- .../test/unit/spec/services-v2/services-v2.ts | 11 +++ 3 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md diff --git a/docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md b/docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md new file mode 100644 index 00000000000..efdb2808c16 --- /dev/null +++ b/docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md @@ -0,0 +1,99 @@ +# FPV-1237 Mercury Cluster Validation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Prevent Mercury events from remapping a service to a cluster owned by another service. + +**Architecture:** Validate every pushed `[serviceName, clusterId]` pair against the current ServicesV2 U2C catalog. If any pair is missing or mismatched, discard the entire push and refresh the authoritative catalog. + +**Tech Stack:** TypeScript, Ampersand/WebexPlugin, Mocha, Sinon, `@webex/test-helper-chai`, Yarn 3, Node.js 22.14. + +## Global Constraints + +- Match the production change supplied in the FPV-1237 `fix.patch`. +- Preserve valid migration behavior and existing unknown-cluster refresh behavior. +- Do not modify the Mercury event handler or add dependencies. +- Do not add credentials, tokens, keys, or certificates. + +--- + +### Task 1: Reject cross-service cluster remapping + +**Files:** +- Modify: `packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts` +- Modify: `packages/@webex/webex-core/src/lib/services-v2/services-v2.ts` + +**Interfaces:** +- Consumes: `switchActiveClusterIds(newActiveClusters: ActiveServices): Promise` +- Produces: Validation that only catalog clusters whose `serviceName` matches the map key are applied. + +- [ ] **Step 1: Write the failing regression test** + +Add this case under `describe('#switchActiveClusterIds')`: + +```ts +it('fetches the catalog and does not update active services when id belongs to another service', async () => { + services._updateActiveServices = sinon.stub(); + + await services.switchActiveClusterIds({ + conversation: 'urn:TEAM:me-central-1_d:mercury', + }); + + assert.calledOnceWithExactly(services.initServiceCatalogs, true); + assert.notCalled(services._updateActiveServices); +}); +``` + +- [ ] **Step 2: Run the targeted test and verify RED** + +Run: + +```bash +yarn workspace @webex/webex-core test:unit --targets services-v2/services-v2.ts +``` + +Expected: the new test fails because `_updateActiveServices` is called and `initServiceCatalogs` is not called. + +- [ ] **Step 3: Apply the supplied production patch** + +Replace ID-only validation with service-aware validation: + +```ts +const invalidEntries = Object.entries(newActiveClusters).some(([serviceName, clusterId]) => { + const service = this._services.find((s) => s.id === clusterId); + + return !service || service.serviceName !== serviceName; +}); +``` + +Use `invalidEntries` to select the existing `initServiceCatalogs(true)` refresh path and update the warning to state that pushed IDs are unknown or do not match their service. + +- [ ] **Step 4: Run the targeted test and verify GREEN** + +Run: + +```bash +yarn workspace @webex/webex-core test:unit --targets services-v2/services-v2.ts +``` + +Expected: all ServicesV2 unit tests pass. + +- [ ] **Step 5: Run package verification** + +Run: + +```bash +yarn workspace @webex/webex-core test:unit +yarn workspace @webex/webex-core build:src +``` + +Expected: both commands exit successfully. + +- [ ] **Step 6: Commit the fix** + +```bash +git add packages/@webex/webex-core/src/lib/services-v2/services-v2.ts \ + packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts \ + docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md +git commit -m "fix(webex-core): validate Mercury cluster migrations" +``` diff --git a/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts b/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts index d6b19b5f5ab..8902a814ab9 100644 --- a/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts +++ b/packages/@webex/webex-core/src/lib/services-v2/services-v2.ts @@ -501,16 +501,15 @@ const Services = WebexPlugin.extend({ switchActiveClusterIds(newActiveClusters: ActiveServices): Promise { this.logger.info('services: switching active cluster ids'); - const newActiveClusterIds = Object.values(newActiveClusters); + const invalidEntries = Object.entries(newActiveClusters).some(([serviceName, clusterId]) => { + const service = this._services.find((s) => s.id === clusterId); - const missingClusterIds = newActiveClusterIds.some((clusterId) => { - // if the clusterId does not exist in the catalog, fetch the catalog - return !this._services.find((service) => service.id === clusterId); + return !service || service.serviceName !== serviceName; }); - if (missingClusterIds) { + if (invalidEntries) { this.logger.warn( - 'services: some cluster ids do not exist in the catalog, fetching the catalog' + 'services: some cluster ids are unknown or do not match their service, fetching the catalog' ); // fetch the catalog diff --git a/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts b/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts index 1e6ba3fd7e3..b70c87c865b 100644 --- a/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts +++ b/packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts @@ -607,6 +607,17 @@ describe('webex-core', () => { assert.calledOnce(services.initServiceCatalogs); }); + + it('fetches the catalog and does not update active services when id belongs to another service', async () => { + services._updateActiveServices = sinon.stub(); + + await services.switchActiveClusterIds({ + conversation: 'urn:TEAM:me-central-1_d:mercury', + }); + + assert.calledOnceWithExactly(services.initServiceCatalogs, true); + assert.notCalled(services._updateActiveServices); + }); }); describe('#updateCatalog', () => { From 8d8d2dbab2223ef919d4f77b97705d19dad68080 Mon Sep 17 00:00:00 2001 From: ajitku3 Date: Mon, 10 Aug 2026 10:22:07 +0530 Subject: [PATCH 3/3] chore: remove FPV implementation notes Co-authored-by: Cursor --- ...-10-fpv-1237-mercury-cluster-validation.md | 99 ------------------- ...-1237-mercury-cluster-validation-design.md | 25 ----- 2 files changed, 124 deletions(-) delete mode 100644 docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md delete mode 100644 docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md diff --git a/docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md b/docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md deleted file mode 100644 index efdb2808c16..00000000000 --- a/docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md +++ /dev/null @@ -1,99 +0,0 @@ -# FPV-1237 Mercury Cluster Validation Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Prevent Mercury events from remapping a service to a cluster owned by another service. - -**Architecture:** Validate every pushed `[serviceName, clusterId]` pair against the current ServicesV2 U2C catalog. If any pair is missing or mismatched, discard the entire push and refresh the authoritative catalog. - -**Tech Stack:** TypeScript, Ampersand/WebexPlugin, Mocha, Sinon, `@webex/test-helper-chai`, Yarn 3, Node.js 22.14. - -## Global Constraints - -- Match the production change supplied in the FPV-1237 `fix.patch`. -- Preserve valid migration behavior and existing unknown-cluster refresh behavior. -- Do not modify the Mercury event handler or add dependencies. -- Do not add credentials, tokens, keys, or certificates. - ---- - -### Task 1: Reject cross-service cluster remapping - -**Files:** -- Modify: `packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts` -- Modify: `packages/@webex/webex-core/src/lib/services-v2/services-v2.ts` - -**Interfaces:** -- Consumes: `switchActiveClusterIds(newActiveClusters: ActiveServices): Promise` -- Produces: Validation that only catalog clusters whose `serviceName` matches the map key are applied. - -- [ ] **Step 1: Write the failing regression test** - -Add this case under `describe('#switchActiveClusterIds')`: - -```ts -it('fetches the catalog and does not update active services when id belongs to another service', async () => { - services._updateActiveServices = sinon.stub(); - - await services.switchActiveClusterIds({ - conversation: 'urn:TEAM:me-central-1_d:mercury', - }); - - assert.calledOnceWithExactly(services.initServiceCatalogs, true); - assert.notCalled(services._updateActiveServices); -}); -``` - -- [ ] **Step 2: Run the targeted test and verify RED** - -Run: - -```bash -yarn workspace @webex/webex-core test:unit --targets services-v2/services-v2.ts -``` - -Expected: the new test fails because `_updateActiveServices` is called and `initServiceCatalogs` is not called. - -- [ ] **Step 3: Apply the supplied production patch** - -Replace ID-only validation with service-aware validation: - -```ts -const invalidEntries = Object.entries(newActiveClusters).some(([serviceName, clusterId]) => { - const service = this._services.find((s) => s.id === clusterId); - - return !service || service.serviceName !== serviceName; -}); -``` - -Use `invalidEntries` to select the existing `initServiceCatalogs(true)` refresh path and update the warning to state that pushed IDs are unknown or do not match their service. - -- [ ] **Step 4: Run the targeted test and verify GREEN** - -Run: - -```bash -yarn workspace @webex/webex-core test:unit --targets services-v2/services-v2.ts -``` - -Expected: all ServicesV2 unit tests pass. - -- [ ] **Step 5: Run package verification** - -Run: - -```bash -yarn workspace @webex/webex-core test:unit -yarn workspace @webex/webex-core build:src -``` - -Expected: both commands exit successfully. - -- [ ] **Step 6: Commit the fix** - -```bash -git add packages/@webex/webex-core/src/lib/services-v2/services-v2.ts \ - packages/@webex/webex-core/test/unit/spec/services-v2/services-v2.ts \ - docs/superpowers/plans/2026-08-10-fpv-1237-mercury-cluster-validation.md -git commit -m "fix(webex-core): validate Mercury cluster migrations" -``` diff --git a/docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md b/docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md deleted file mode 100644 index a65d57896cd..00000000000 --- a/docs/superpowers/specs/2026-08-10-fpv-1237-mercury-cluster-validation-design.md +++ /dev/null @@ -1,25 +0,0 @@ -# FPV-1237 Mercury Cluster Validation Design - -## Goal - -Prevent an untrusted Mercury `ActiveClusterStatusEvent` from remapping a service to a cluster that belongs to a different service. - -## Scope - -Change `ServicesV2.switchActiveClusterIds()` in `@webex/webex-core`. The Mercury event handler and server-side publish authorization are outside this client-side defense-in-depth fix. - -## Design - -Treat pushed active-cluster values as hints. For every `[serviceName, clusterId]` pair, find the cluster in the current U2C catalog and require its `serviceName` to equal the map key. If any cluster is missing or belongs to another service, reject the entire pushed map and force an authoritative U2C catalog refresh. Apply the map only when every pair is valid. - -This preserves valid cluster migrations, retains the existing refresh behavior for unknown clusters, and prevents partial application of mixed valid and invalid input. - -## Testing - -Add a webex-core unit regression test that uses an existing cluster ID belonging to a different service. Verify that `initServiceCatalogs(true)` is called and `_updateActiveServices()` is not called. Retain the existing tests for accepted matching clusters and missing clusters. - -Run the targeted ServicesV2 unit tests, the `@webex/webex-core` unit suite, and the webex-core source build with Node.js 22.14. - -## Security Considerations - -The patch validates both identifier existence and service ownership at the trust boundary. It does not authenticate Mercury events; authoritative publisher authorization remains a server-side requirement. No credentials, tokens, cryptographic keys, or certificates are added.