fix(alerts): redact destination config from the testDestination response - #31634
fix(alerts): redact destination config from the testDestination response#31634manerow wants to merge 3 commits into
Conversation
✅ PR checks passedThe linked issue has a description and all required Shipping project fields set. Thanks! |
✅ Playwright Results — workflow succeededValidated commit ✅ 981 passed · ❌ 0 failed · 🟡 1 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 55m 56s ⏱️ Max setup 3m 6s · max shard execution 20m 10s · max shard-job elapsed before upload 23m 25s · reporting 6s 🌐 205.78 requests/attempt · 2.42 app boots/UI scenario · 15.12% common-shard skew Optimization targets still in progress:
🟡 1 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
|
| Count | Rule |
|---|---|
| 10 | sonarjs/no-duplicate-string |
| 3 | react-hooks/exhaustive-deps |
| 2 | openmetadata-imports/no-lower-layer-page-imports |
| 2 | sonarjs/cyclomatic-complexity |
| 1 | i18next/no-literal-string |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/Alerts/DestinationFormItem/DestinationFormItem.component.tsx:52:9 |
react-hooks/exhaustive-deps |
The 'destinations' logical expression could make the dependencies of useMemo Hook (at line 97) change on every render. Move it inside the useMemo callback. Alte |
| 🟡 | src/components/Alerts/DestinationFormItem/DestinationFormItem.component.tsx:110:18 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx:65:56 |
i18next/no-literal-string |
disallow literal string: Icon |
| 🟡 | src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx:101:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx:173:23 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 11 times. |
| 🟡 | src/components/Alerts/DestinationFormItem/DestinationFormItem.test.tsx:242:45 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 7 times. |
| 🟡 | src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx:38:1 |
openmetadata-imports/no-lower-layer-page-imports |
Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here. |
| 🟡 | src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx:67:9 |
react-hooks/exhaustive-deps |
The 'destinations' logical expression could make the dependencies of useMemo Hook (at line 99) change on every render. To fix this, wrap the initialization of ' |
| 🟡 | src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx:67:9 |
react-hooks/exhaustive-deps |
The 'destinations' logical expression could make the dependencies of useCallback Hook (at line 133) change on every render. To fix this, wrap the initialization |
| 🟡 | src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx:78:20 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/Alerts/DestinationFormItemV2/DestinationFormItemV2.component.tsx:149:55 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:37:1 |
openmetadata-imports/no-lower-layer-page-imports |
Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here. |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:40:62 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 11 which is greater than 10 authorized.","cost":1,"secondaryLocations":[{"line":40,"column":61,"endLine":40,"endColumn" |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:57:16 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:425:66 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 16 which is greater than 10 authorized.","cost":6,"secondaryLocations":[{"line":425,"column":65,"endLine":425,"endColum |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:428:16 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 13 times. |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:429:22 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 13 times. |
| 🟡 | src/utils/Alerts/AlertsUtilPure.ts:442:21 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 5 times. |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed
Code Review ✅ Approved 1 resolved / 1 findingsRedacts plaintext credentials and query strings from the testDestination response while returning failed deliveries with an explicit status. No issues found. ✅ 1 resolved✅ Security: Failure reason may echo endpoint URL with query-param credentials
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|
|
🚦 Removed from the merge queue —
|



Describe your changes:
Fixes #31633
POST /v1/events/subscriptions/testDestinationreturned the caller's ownSubscriptionDestinationobjects, so the 200 response echoedconfigback verbatim, including the plaintext webhookauthType.clientSecret/clientId(OAuth2),secretKey(bearer), and any credential-bearingheadersorqueryParams. The caller already holds that config, so nothing in the response needs to carry it.The endpoint now returns one entry per requested destination, in request order, with
configredacted and onlystatusDetailspopulated. Destinations whose delivery threwEventPublisherExceptionwere previously logged and dropped from the array; they are now returned with aFailedstatus and a reason, which both fixes a real bug (a connection failure produced no status banner at all) and makes the response length and order match the request.On the UI side the destination rows locate their result by deep-equality on
{type, category, config}against live form state, so they can no longer rely on the echoed config. The three call sites now keep the config they submitted and take onlystatusDetailsfrom the response, via a new shared helpergetDestinationsWithTestStatusinAlertsUtilPure.Type of change:
High-level design:
The UI's deep-equality match is not only correlation, it is also a staleness guard: each row re-reads its values from live form state, so editing a field after a test correctly clears the banner. Attaching status by index prop instead would have regressed that behaviour and touched three child components plus their tests. Rebuilding
destinationsWithStatusfrom the submitted array preserves child behaviour bit-for-bit while removing the dependency on the wire config entirely, since the submitted array is exactly what the server used to echo.Redaction rather than masking: masking the secret fields would still break the deep-equality match, and precisely for the destinations that carry secrets. A
SubscriptionDestinationwithoutconfigis the typed result this endpoint should have returned in the first place.The response shape stays
SubscriptionDestination[](the field is simply absent), so no schema change and no client typing change is required.Deliberately out of scope, listed in the issue: webhook auth fields still lack
"format": "password"soPasswordEntityMaskeris a no-op on them;Fernet.encryptWebhookSecretKeystill gates ontype == WEBHOOKleaving Slack/MsTeams/GChat auth in plaintext at rest;TestDestinationStatus.entitystill carries the remote response body.Tests:
Use cases covered
config, and the success banner still renders on the correct row.Failedstatus with a reason instead of vanishing from the response.Unit tests
openmetadata-ui/src/main/resources/ui/src/utils/Alerts/AlertsUtilPure.test.ts(3 tests) covering config retention, positional pairing, and a short/missing result array.DestinationFormItem.test.tsxto the new config-less contract.yarn test src/components/Alerts/DestinationFormItem src/components/Alerts/DestinationFormItemV2→ 65 passed.Backend integration tests
EventSubscriptionResourceIT#test_testDestinationRedactsConfigAndReportsFailures, which posts a reachable OAuth2 webhook plus an unreachable one against an in-JVMHttpServer, and asserts the raw response body contains neither the sentinel secret norclientSecretnorauthType, that both entries come back in request order withconfig == null, and that the unreachable one reportsFailedwith a reason.Response echoed the webhook client secret; against the fixed server it passes.mvn test -pl openmetadata-integration-tests -Dtest=EventSubscriptionResourceIT→ 243 tests, 0 failures.Ingestion integration tests
Not applicable (no ingestion changes).
Playwright (UI) tests
playwright/e2e/Flow/NotificationAlerts.spec.tswithexpect(testResult.config).toBeUndefined()to pin redaction end-to-end. The spec's existing length-2 assertion also becomes deterministic, since a transport failure no longer removes an entry.Manual testing performed
Verification so far is the automated coverage above (backend IT run locally against the built server, jest, and the Playwright assertion); I have not yet driven this through a browser session end-to-end. Happy to attach a recording if reviewers would like one before merge.
UI screen recording / screenshots:
No visual change to the success path: the same banner renders in the same place, sourced from the submitted config instead of the echoed one. The one user-visible improvement is that an unreachable destination now renders a
Failedbanner where it previously rendered nothing.Checklist:
Fixes #31633above.Greptile Summary
The PR prevents
/testDestinationfrom echoing credential-bearing destination configuration while preserving request-order status correlation in the UI.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Sequence Diagram
sequenceDiagram participant UI as Alerts UI participant API as testDestination endpoint participant Dest as Destination publisher UI->>API: Submitted destinations with config loop In request order API->>Dest: sendTestMessage() Dest-->>API: Status or delivery exception API->>API: Build status and remove config end API-->>UI: Redacted destination results UI->>UI: Pair statuses with submitted configs by indexReviews (3): Last reviewed commit: "fix(ui): sort imports in the AlertsUtilP..." | Re-trigger Greptile
Context used (4)