Skip to content

fix(analytics): strip NUL bytes and slim web-analytics payload#28232

Open
harshach wants to merge 3 commits into
mainfrom
harshach/strip-null-bytes-analytics
Open

fix(analytics): strip NUL bytes and slim web-analytics payload#28232
harshach wants to merge 3 commits into
mainfrom
harshach/strip-null-bytes-analytics

Conversation

@harshach
Copy link
Copy Markdown
Collaborator

Describe your changes:

Fix a PSQLException: unsupported Unicode escape sequence raised when PUT /v1/analytics/web/events/collect payloads contained the NUL character (PostgreSQL jsonb cannot store NUL bytes). WebAnalyticEventResource.sanitizeWebAnalyticEventData now strips NULs from all user-supplied string fields on both PageViewData and CustomEvent before the insert. While auditing the path I also trimmed the UI to stop sending fields that no downstream processor reads — removed the global document.body click listener (CustomEvent click data has zero readers; it was the source of the NUL-byte bug and fired on every click) and reduced the PageView payload from 9 fields to 4 (userId, sessionId, url, fullUrl), dropping hostname, language, screenSize, referrer, pageLoadTime. The Data Insights WebAnalyticsUserActivityProcessor and WebAnalyticsEntityViewProcessor only read the kept fields, so DAU/MAU and "Most Viewed Entities" reports remain unchanged.

Type of change:

  • Bug fix
  • Improvement

Tests:

Unit tests

  • Added WebAnalyticEventResourceTest (5 cases): null input, no-NUL fast path, multi-NUL stripping, end-to-end sanitization for PageViewData and CustomEvent.
  • Updated WebAnalyticsUtils.test.ts to assert the new minimum PageView payload and the userId guard; removed obsolete getReferrerPath/trackCustomEvent cases.

Backend integration tests

  • Not applicable (no new API surface; existing WebAnalyticEventResourceIT continues to exercise the endpoint).

Playwright (UI) tests

  • Not applicable (no UI surface area changed; the removed click listener and dropped payload fields are not user-visible).

Manual testing performed

  1. mvn test -pl openmetadata-service -Dtest=WebAnalyticEventResourceTest → 5/5 pass.
  2. yarn test WebAnalyticsUtils → 3/3 pass.
  3. mvn spotless:check and yarn ui-checkstyle:changed both clean.

UI screen recording / screenshots:

Not applicable — no user-visible UI changes.

Checklist:

  • I have read the CONTRIBUTING document.
  • I have added tests (unit) and listed them above.

🤖 Generated with Claude Code

PostgreSQL jsonb rejects strings containing the NUL character, which
broke PUT /v1/analytics/web/events/collect whenever the UI captured
page text containing NULs (e.g. Health Check error messages).
Sanitize NUL characters in WebAnalyticEventResource before the
jsonb insert.

While auditing the path, drop UI fields that have zero downstream
consumers:
- Remove global document.body click listener; CustomEvent click data
  is written, kept 7 days, then deleted with no reader.
- Trim PageView payload from 9 fields to 4 (userId, sessionId, url,
  fullUrl); hostname/language/screenSize/referrer/pageLoadTime are
  never read by any processor.

DAU/MAU and Most Viewed Entities reports are unaffected since the
WebAnalyticsUserActivityProcessor and WebAnalyticsEntityViewProcessor
only read the kept fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 18, 2026 15:32
@harshach harshach requested a review from a team as a code owner May 18, 2026 15:32
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels May 18, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a PSQLException triggered when web analytics payloads contained NUL (\u0000) bytes (PostgreSQL jsonb cannot store them) by sanitizing user-supplied string fields on the server. Additionally trims the UI analytics surface area: removes the global document.body click listener (source of the bug) and the now-unused fields from the PageView payload, keeping only what downstream Data Insights processors consume.

Changes:

  • Add removeNullCharacters / stripNullCharacters helpers and apply them to PageViewData and CustomEvent inside sanitizeWebAnalyticEventData.
  • Drop the track/trackCustomEvent plugin, the body click listener, and unused helpers (getReferrerPath, getPageLoadTime); reduce PageView payload to userId, sessionId, url, fullUrl.
  • Add WebAnalyticEventResourceTest and refresh WebAnalyticsUtils.test.ts to match the new behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java Strip NUL bytes from PageView and CustomEvent fields before persistence.
openmetadata-service/src/test/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResourceTest.java New unit tests covering NUL stripping and sanitization paths.
openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.ts Slim PageView payload, remove trackCustomEvent and unused helpers.
openmetadata-ui/src/main/resources/ui/src/utils/WebAnalyticsUtils.test.ts Update tests to assert minimal PageView payload and userId guard.
openmetadata-ui/src/main/resources/ui/src/components/AppContainer/AppContainer.tsx Remove global click listener that triggered custom-event tracking.

Apply stripNullCharacters to every CustomEvent right after the JSON
conversion, mirroring the PageView path, so a future CustomEventTypes
value cannot silently bypass sanitization. No behavior change today
since CLICK is the only enum value and the else branch throws.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 18, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 62%
62.76% (65532/104401) 43.61% (35933/82394) 46.34% (10560/22788)

@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented May 18, 2026

Code Review ✅ Approved 2 resolved / 2 findings

Strips NUL bytes from web-analytics payloads to prevent PostgreSQL JSONB exceptions and reduces payload size by removing unused telemetry fields. The implementation correctly handles NUL stripping before HTML validation, and no issues were found.

✅ 2 resolved
Bug: NUL stripping must happen before HTML check to avoid false negatives

📄 openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java:612-614
At line 613-614, stripNullCharacters is called before containsHtml, which is correct order. However, there's a subtle issue: the containsHtml check uses Pattern.DOTALL with .*\<[^>]+>.*. If a NUL byte appeared inside an HTML tag (e.g. <scr\u0000ipt>), the stripping would reassemble a valid tag that then passes the HTML check. This is a minor concern since the stripping now happens first — the current ordering is actually correct. Disregard this.

The real issue is: stripNullCharacters(customEventData) is called only inside the CLICK branch (line 612-613). If a new CustomEventTypes value is added in the future, the else branch throws, so currently all reachable custom events are sanitized. This is fine for now but worth noting the coupling.

No actionable finding here — withdrawing.

Edge Case: HTML check on eventValue may pass after NUL stripping reassembles tag

📄 openmetadata-service/src/main/java/org/openmetadata/service/resources/analytics/WebAnalyticEventResource.java:613-615
The containsHtml check (line 614) runs after stripNullCharacters (line 613). If a malicious payload contains a NUL byte splitting an HTML tag (e.g., <scr\u0000ipt>alert(1)</script>), the stripping will reassemble the tag into <script>alert(1)</script>, which containsHtml will correctly reject. So the current ordering (strip first, then validate) is actually the safe order. No issue here — the fix is correct.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud
Copy link
Copy Markdown

@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 15 failure(s), 14 flaky

✅ 4123 passed · ❌ 15 failed · 🟡 14 flaky · ⏭️ 86 skipped

Shard Passed Failed Flaky Skipped
✅ Shard 1 299 0 0 4
🟡 Shard 2 775 0 6 8
🟡 Shard 3 777 0 1 7
🔴 Shard 4 778 1 3 12
🔴 Shard 5 758 14 1 47
🟡 Shard 6 736 0 3 8

Genuine Failures (failed on all attempts)

Pages/Entity.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Entity.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Entity.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Entity.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Entity.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/EntityDataConsumer.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataConsumer.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataConsumer.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataConsumer.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataConsumer.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataSteward.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataSteward.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataSteward.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataSteward.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
Pages/EntityDataSteward.spec.ts › Glossary Term Add, Update and Remove for child entities (shard 5)
�[31mTest timeout of 60000ms exceeded.�[39m
🟡 14 flaky test(s) (passed on retry)
  • Features/ActivityAPI.spec.ts › creates an activity event when tags are added (shard 2, 1 retry)
  • Features/BulkEditEntity.spec.ts › Database Schema (shard 2, 1 retry)
  • Features/DataQuality/ColumnLevelTests.spec.ts › Column Values To Not Match Regex (shard 2, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.ts › should cancel move operation (shard 2, 1 retry)
  • Features/IncidentManager.spec.ts › Resolving incident & re-run pipeline (shard 2, 1 retry)
  • Features/KnowledgeCenter.spec.ts › Article mentions in description should working for Knowledge Center (shard 2, 1 retry)
  • Features/RTL.spec.ts › Verify Following widget functionality (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Email (shard 4, 1 retry)
  • Pages/DataContractsSemanticRules.spec.ts › Validate Description Rule Is_Set (shard 4, 1 retry)
  • Pages/DomainAdvanced.spec.ts › Data product version history shows changes (shard 4, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/ServiceEntity.spec.ts › Delete Database Schema (shard 6, 1 retry)
  • Pages/ServiceEntity.spec.ts › User as Owner Add, Update and Remove (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants