Skip to content

Fixes for supported ciphersuites#27089

Open
mohityadav766 wants to merge 11 commits intomainfrom
pen-test
Open

Fixes for supported ciphersuites#27089
mohityadav766 wants to merge 11 commits intomainfrom
pen-test

Conversation

@mohityadav766
Copy link
Copy Markdown
Member

@mohityadav766 mohityadav766 commented Apr 6, 2026

Describe your changes:

Fixes for Pentesting

I worked on ... because ...

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

Summary by Gitar

  • Dependencies:
    • Added yarn.lock file to lock package versions for reproducible builds

This will update automatically on new commits.

@mohityadav766 mohityadav766 self-assigned this Apr 6, 2026
@mohityadav766 mohityadav766 requested a review from a team as a code owner April 6, 2026 12:25
@mohityadav766 mohityadav766 added the safe to test Add this label to run secure Github workflows on PRs label Apr 6, 2026
Copilot AI review requested due to automatic review settings April 6, 2026 12:25
Comment on lines +95 to +99
public static String sanitize(String description) {
if (description == null) {
return null;
}
return MARKDOWN_POLICY.sanitize(description);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 Security: DescriptionSanitizer not applied on PATCH/PUT updates

The new DescriptionSanitizer is only called during entity creation (EntityRepository.java:1192) and task resolution (EntityRepository.java:8253). However, PATCH and PUT operations that update an entity's description bypass sanitization entirely — EntityUpdater.updateDescription() (line ~6624) records the change without calling DescriptionSanitizer.sanitize(). An attacker can create a clean entity, then update its description via PATCH/PUT with a malicious XSS payload, defeating the stored-XSS protection.

The fix should also sanitize descriptions in the update path. One approach is to sanitize in EntityUpdater.updateDescription() before recording the change, or to add sanitization in prepareInternal() so it covers both create and update flows.

Suggested fix:

// In EntityUpdater.updateDescription() (~line 6616), sanitize before recording:
private void updateDescription() {
  String origDesc = DescriptionSanitizer.sanitize(original.getDescription());
  String updatedDesc = DescriptionSanitizer.sanitize(updated.getDescription());
  // ... use sanitized values for comparison and recording
}

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

|| ex instanceof IllegalArgumentException
|| ex instanceof BadRequestException) {
return getResponse(Response.status(Response.Status.BAD_REQUEST).build(), ex);
return getResponse(BAD_REQUEST, "Invalid request parameter");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Edge Case: Generic error message loses diagnostic info for BadRequest

The change at line 53 replaces the original exception message with a hardcoded "Invalid request parameter" for all ProcessingException, IllegalArgumentException, and BadRequestException cases. While this correctly prevents information leakage, it makes debugging significantly harder — a client won't know which parameter was invalid or why. Consider returning a safe subset of the exception message (e.g., from BadRequestException which already contains user-facing messages) while stripping internal details only for ProcessingException and IllegalArgumentException.

Suggested fix:

// Differentiate between exception types:
if (ex instanceof BadRequestException) {
  return getResponse(BAD_REQUEST, ex.getMessage());
} else {
  return getResponse(BAD_REQUEST, "Invalid request parameter");
}

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

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

This PR applies a set of hardening changes driven by penetration testing, focusing on reducing impersonation vectors in feed APIs, tightening error responses, adding additional security headers, and introducing server-side HTML sanitization for descriptions.

Changes:

  • Remove caller-controlled from requirement from feed thread/post creation requests and derive author from the authenticated principal.
  • Add server-side description sanitization (OWASP HTML Sanitizer) and reduce error-detail leakage in exception mappers.
  • Add configurable COEP/COOP/CORP security headers and update example TLS protocol/cipher configuration.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/package.json Bumps dompurify dependency version.
openmetadata-spec/src/main/resources/json/schema/api/feed/createThread.json Removes from from required fields for thread creation request.
openmetadata-spec/src/main/resources/json/schema/api/feed/createPost.json Removes from from required fields for post creation request.
openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java Adds a new OWASP-based sanitizer for stored description content.
openmetadata-service/src/main/java/org/openmetadata/service/resources/feeds/PostMapper.java Sets post author from authenticated user rather than request payload.
openmetadata-service/src/main/java/org/openmetadata/service/resources/feeds/FeedMapper.java Sets thread createdBy from authenticated user rather than request payload.
openmetadata-service/src/main/java/org/openmetadata/service/OpenMetadataApplication.java Adjusts JSON processing exception mapper detail behavior.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java Applies description sanitization in selected write paths.
openmetadata-service/src/main/java/org/openmetadata/service/exception/JsonMappingExceptionMapper.java Replaces detailed mapping error message with a generic one.
openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogGenericExceptionMapper.java Replaces certain detailed error messages with generic ones.
openmetadata-service/src/main/java/org/openmetadata/service/config/web/CrossOriginResourcePolicyHeaderFactory.java Adds CORP header factory.
openmetadata-service/src/main/java/org/openmetadata/service/config/web/CrossOriginOpenerPolicyHeaderFactory.java Adds COOP header factory.
openmetadata-service/src/main/java/org/openmetadata/service/config/web/CrossOriginEmbedderPolicyHeaderFactory.java Adds COEP header factory.
openmetadata-service/src/main/java/org/openmetadata/service/config/OMWebConfiguration.java Wires new header factories into web config.
openmetadata-service/src/main/java/org/openmetadata/service/config/OMWebBundle.java Applies new headers when enabled via config.
conf/openmetadata.yaml Adds config knobs for new headers and updates commented TLS examples.

"crypto-random-string-with-promisify-polyfill": "^5.0.0",
"diff": "^8.0.3",
"dompurify": "^3.2.7",
"dompurify": "^3.3.2",
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

package.json bumps dompurify to ^3.3.2, but yarn.lock does not contain an entry for dompurify@^3.3.2 (it still has dompurify@^3.2.7). With yarn install --frozen-lockfile, this will fail CI. Please regenerate and commit the lockfile update (or keep the semver range aligned with the existing lock entry).

Suggested change
"dompurify": "^3.3.2",
"dompurify": "^3.2.7",

Copilot uses AI. Check for mistakes.
Comment on lines 12 to 18
"from": {
"description": "Name of the User posting the message",
"type": "string"
}
},
"required": ["message", "from"],
"required": ["message"],
"additionalProperties": false
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The request schema no longer requires from, but the from property is still present and documented as the posting user. Since the backend now derives author from the authenticated principal, keeping this field in the request contract is misleading and invites clients to think impersonation is supported. Consider removing from from the schema (or explicitly marking it ignored/deprecated).

Copilot uses AI. Check for mistakes.
Comment on lines 68 to 73
},
"default": null
}
},
"required": ["message", "from", "about"],
"required": ["message", "about"],
"additionalProperties": false
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The request schema no longer requires from, but the from property is still present and documented as the posting user. Since the backend now derives createdBy from the authenticated principal, this field is misleading and suggests caller-controlled authorship. Consider removing from from the schema (or explicitly marking it ignored/deprecated).

Copilot uses AI. Check for mistakes.
Comment on lines 1188 to 1193
entity.setId(UUID.randomUUID());
entity.setName(request.getName());
entity.setDisplayName(request.getDisplayName());
entity.setDescription(request.getDescription());
entity.setDescription(
org.openmetadata.service.util.DescriptionSanitizer.sanitize(request.getDescription()));
entity.setOwners(owners);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

Description sanitization is only applied in copy(...) (create/PUT-like flows) and the description task workflow. Description changes coming from PATCH update paths still flow through updateDescription() without sanitization, leaving a stored-XSS bypass. Apply DescriptionSanitizer for all description writes (e.g., sanitize updated.getDescription() before recording/persisting).

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +53
.allowElements("a")
.allowAttributes("href")
.matching(
(elementName, attributeName, value) -> {
if (value.startsWith("http://")
|| value.startsWith("https://")
|| value.startsWith("mailto:")
|| value.startsWith("#")) {
return value;
}
return null;
})
.onElements("a")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

The href allowlist blocks all relative URLs (e.g., /path, ./doc, ../doc). Since descriptions are Markdown, relative links are a standard use case and will be stripped by this policy. Consider allowing safe relative URLs (while still blocking javascript: and other dangerous schemes).

Copilot uses AI. Check for mistakes.
.allowAttributes("target")
.onElements("a")
.allowAttributes("rel")
.onElements("a")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

target is allowed on <a> without enforcing a safe rel (e.g., noopener noreferrer). That enables reverse-tabnabbing if users include target="_blank" links. Consider either disallowing target or enforcing a safe rel value when target is present (similar to the existing email HtmlSanitizer which enforces link rels).

Suggested change
.onElements("a")
.onElements("a")
.requireRelsOnLinks("noopener", "noreferrer")

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +100
/**
* Sanitizes a markdown/HTML description string by removing dangerous elements (script, iframe,
* event handlers like onerror/onclick) while preserving safe markdown-generated HTML.
*
* @param description the raw description from user input
* @return sanitized description safe for storage and rendering, or null if input is null
*/
public static String sanitize(String description) {
if (description == null) {
return null;
}
return MARKDOWN_POLICY.sanitize(description);
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

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

This new sanitizer is security-critical but has no unit tests. There are existing sanitizer tests (e.g., NotificationChannelUtilitiesTest for HtmlSanitizer). Please add tests that cover allowed elements/attributes, blocked script/event handlers, and URL protocol/relative-link behavior to prevent regressions.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 63%
63.91% (59751/93479) 43.65% (31233/71550) 46.75% (9395/20093)

@harshach harshach changed the title Fixes for PenTest Fixes for supported ciphersuites Apr 7, 2026
Copilot AI review requested due to automatic review settings April 9, 2026 09:28
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

Copilot reviewed 16 out of 18 changed files in this pull request and generated 6 comments.

Comment on lines 1207 to +1211
entity.setId(UUID.randomUUID());
entity.setName(request.getName());
entity.setDisplayName(request.getDisplayName());
entity.setDescription(request.getDescription());
entity.setDescription(
org.openmetadata.service.util.DescriptionSanitizer.sanitize(request.getDescription()));
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

DescriptionSanitizer is applied in EntityRepository.copy(...), but most entity create flows use EntityMapper.copy(...) (default method) which still sets description from the request without sanitization. This likely leaves the primary stored-XSS vector unmitigated for newly created/updated entities; consider moving the sanitization into the shared EntityMapper.copy(...) (or a single common persistence path) so all entity descriptions are consistently sanitized.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +67
// Images (src must be http/https/data URI)
.allowElements("img")
.allowAttributes("src")
.matching(
(elementName, attributeName, value) -> {
if (value.startsWith("http://")
|| value.startsWith("https://")
|| value.startsWith("data:image/")) {
return value;
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

DescriptionSanitizer currently allows img sources starting with data:image/, which includes SVG (data:image/svg+xml) and can be risky in some renderers/browsers. Consider restricting data URIs to a safe allowlist of raster types (png/jpeg/gif/webp) or dropping data URIs entirely, and prefer using allowUrlProtocols(...)/built-in helpers for URL validation to avoid missing edge cases.

Copilot uses AI. Check for mistakes.
Comment on lines 50 to 54
} else if (ex instanceof ProcessingException
|| ex instanceof IllegalArgumentException
|| ex instanceof BadRequestException) {
return getResponse(Response.status(Response.Status.BAD_REQUEST).build(), ex);
return getResponse(BAD_REQUEST, "Invalid request parameter");
} else if (ex instanceof UnableToExecuteStatementException) {
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

Switching ProcessingException/IllegalArgumentException/BadRequestException handling to getResponse(BAD_REQUEST, ...) changes the response headers: getResponse(Status, String) always adds WWW-Authenticate: om-auth, which is misleading on 400s and may affect clients. Consider using an overload that doesn't set WWW-Authenticate for non-401 statuses, or set it conditionally only for UNAUTHORIZED.

Copilot uses AI. Check for mistakes.
Comment on lines 123 to +125
# validatePeers: true
# supportedProtocols: SSLv3
# supportedCipherSuites: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
# supportedProtocols: TLSv1.2,TLSv1.3
# supportedCipherSuites: TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The commented supportedProtocols / supportedCipherSuites examples are now comma-separated strings. If a user uncomments them, YAML/Jackson will treat them as a single string value rather than a list/array, leading to misconfiguration. Prefer YAML list syntax (e.g., [TLSv1.2, TLSv1.3] and [TLS_AES_256_GCM_SHA384, ...]) to match Dropwizard/Jetty config expectations.

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 116
"cronstrue": "^2.53.0",
"crypto-random-string-with-promisify-polyfill": "^5.0.0",
"diff": "^8.0.3",
"dompurify": "^3.2.7",
"dompurify": "^3.3.2",
"elkjs": "^0.9.3",
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

dompurify was bumped in package.json, but yarn.lock doesn’t appear to have been regenerated to reflect the new ^3.3.2 descriptor (it still contains an entry for dompurify@^3.2.7 resolving to 3.3.3). Please update the lockfile so installs/CI are deterministic and match the intended version/resolution.

Copilot uses AI. Check for mistakes.
Comment on lines +699 to +707
cross-origin-embedder-policy:
enabled: ${WEB_CONF_CROSS_ORIGIN_EMBEDDER_POLICY_ENABLED:-false}
option: ${WEB_CONF_CROSS_ORIGIN_EMBEDDER_POLICY_OPTION:-"require-corp"}
cross-origin-resource-policy:
enabled: ${WEB_CONF_CROSS_ORIGIN_RESOURCE_POLICY_ENABLED:-false}
option: ${WEB_CONF_CROSS_ORIGIN_RESOURCE_POLICY_OPTION:-"same-origin"}
cross-origin-opener-policy:
enabled: ${WEB_CONF_CROSS_ORIGIN_OPENER_POLICY_ENABLED:-false}
option: ${WEB_CONF_CROSS_ORIGIN_OPENER_POLICY_OPTION:-"same-origin"}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The PR title mentions only supported cipher suites, but this change set also adds XSS sanitization, exception message hardening, feed author spoofing prevention, and new Cross-Origin headers. Consider updating the PR title/description to reflect the broader security scope so it’s easier to track in release notes and future audits.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 10, 2026 10:54
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

Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.

Comment on lines 141 to 154
if (formatFor === 'server') {
modifiedHtmlString =
modifiedHtmlString = getSanitizeContent(
blockEditorExtensionsClassBase.serializeContentForBackend(
modifiedHtmlString
);
)
);

// Replace markers with actual entity links
entityLinkMap.forEach((entityLink, marker) => {
modifiedHtmlString = modifiedHtmlString.replace(marker, entityLink);
});
} else {
modifiedHtmlString = getSanitizeContent(modifiedHtmlString);
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

formatContent(..., 'server') sanitizes the HTML before restoring the mention/hashtag entity-link markers. Because the marker replacement re-inserts a [#text](href) payload after sanitization, a malicious href taken from the original <a> tag could bypass DOMPurify and end up stored/rendered. Validate/normalize the extracted href (e.g., allowlist http/https + same-origin) before embedding it into the entity-link string, or perform sanitization after replacement in a way that still sanitizes the embedded URL.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +71
// Images (src must be http/https/data URI)
.allowElements("img")
.allowAttributes("src")
.matching(
(elementName, attributeName, value) -> {
if (value.startsWith("http://")
|| value.startsWith("https://")
|| value.startsWith("data:image/")) {
return value;
}
return null;
})
.onElements("img")
.allowAttributes("alt", "title", "width", "height")
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

DescriptionSanitizer allows img src values starting with data:image/, which includes data:image/svg+xml. SVG-in-<img> has had XSS bypasses historically; consider disallowing SVG data URIs (or all data: URIs) and only allowing http/https image URLs or a limited set of safe data:image/{png,jpeg,gif,webp} types.

Copilot uses AI. Check for mistakes.
Comment on lines 14 to 18
"type": "string"
}
},
"required": ["message", "from"],
"required": ["message"],
"additionalProperties": false
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

With from no longer required for CreatePost, any tests or client validations expecting a 4xx when from is omitted should be updated to match the new behavior (server uses the authenticated principal for from).

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 10, 2026 11:27
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.

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

🔴 Playwright Results — 9 failure(s), 26 flaky

✅ 2980 passed · ❌ 9 failed · 🟡 26 flaky · ⏭️ 140 skipped

Shard Passed Failed Flaky Skipped
🔴 Shard 1 452 4 1 2
🔴 Shard 2 638 2 3 32
🟡 Shard 3 640 0 9 26
🟡 Shard 4 620 0 7 47
🔴 Shard 6 630 3 6 33

Genuine Failures (failed on all attempts)

Features/MutuallyExclusiveColumnTags.spec.ts › Should show error toast when adding mutually exclusive tags to column (shard 1)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoContainText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator: getByTestId('alert-message')
Expected substring: �[32m"mutually exclusive"�[39m
Received string:    �[31m"Invalid request parameter"�[39m
Timeout: 15000ms

Call log:
�[2m  - Expect "toContainText" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('alert-message')�[22m
�[2m    18 × locator resolved to <span class="alert-message" data-testid="alert-message">Invalid request parameter</span>�[22m
�[2m       - unexpected value "Invalid request parameter"�[22m

Pages/Policies.spec.ts › Add new policy with invalid condition (shard 1)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  getByTestId('alert-bar')
Expected: �[32m"At least one rule is required in a policy"�[39m
Received: �[31m"Invalid request parameter"�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveText" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('alert-bar')�[22m
�[2m    19 × locator resolved to <div role="alert" data-show="true" data-testid="alert-bar" class="ant-alert ant-alert-error ant-alert-with-description alert-container error show-alert">…</div>�[22m
�[2m       - unexpected value "Invalid request parameter"�[22m

Pages/Roles.spec.ts › Roles page should work properly (shard 1)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  getByTestId('alert-bar')
Expected: �[32m"At least one policy is required in a role"�[39m
Received: �[31m"Invalid request parameter"�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveText" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('alert-bar')�[22m
�[2m    19 × locator resolved to <div role="alert" data-show="true" data-testid="alert-bar" class="ant-alert ant-alert-error ant-alert-with-description alert-container error show-alert">…</div>�[22m
�[2m       - unexpected value "Invalid request parameter"�[22m

Pages/SearchIndexApplication.spec.ts › Search Index Application (shard 1)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoEqual�[2m(�[22m�[32mexpected�[39m�[2m) // deep equality�[22m

Expected: �[32mStringMatching /success|activeError/g�[39m
Received: �[31m"failed"�[39m
Features/DataProductRename.spec.ts › should show error when renaming to a name that already exists (shard 2)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoContainText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator: getByTestId('alert-message')
Expected substring: �[32m"already exists"�[39m
Received string:    �[31m"Invalid request parameter"�[39m
Timeout: 15000ms

Call log:
�[2m  - Expect "toContainText" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('alert-message')�[22m
�[2m    18 × locator resolved to <span class="alert-message" data-testid="alert-message">Invalid request parameter</span>�[22m
�[2m       - unexpected value "Invalid request parameter"�[22m

Features/DataQuality/TestDefinitionPermissions.spec.ts › should prevent all users from modifying system test definition entity type via API (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoContain�[2m(�[22m�[32mexpected�[39m�[2m) // indexOf�[22m

Expected substring: �[32m"System test definitions cannot have their entity type modified"�[39m
Received string:    �[31m"Invalid request parameter"�[39m
Pages/Glossary.spec.ts › Check for duplicate Glossary Term (shard 6)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator: locator('#name_help')
Expected: �[32m"A term with the name 'Pw_test_term' already exists in 'PW_TEST_GLOSSARY' glossary."�[39m
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toHaveText" with timeout 15000ms�[22m
�[2m  - waiting for locator('#name_help')�[22m

Pages/Glossary.spec.ts › Check for duplicate Glossary Term with Glossary having dot in name (shard 6)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator: locator('#name_help')
Expected: �[32m"A term with the name 'Pw_test_term' already exists in '\"PW%'4246787c.Lively0d032aaf\"' glossary."�[39m
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toHaveText" with timeout 15000ms�[22m
�[2m  - waiting for locator('#name_help')�[22m

VersionPages/TestCaseVersionPage.spec.ts › should show the test case version page (shard 6)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  getByTestId('asset-description-container').getByTestId('markdown-parser').getByTestId('diff-added')
Expected: �[32m"test case description changed"�[39m
Received: �[31m"�[7m<p>�[27mtest case description changed�[7m</p>�[27m"�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveText" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('asset-description-container').getByTestId('markdown-parser').getByTestId('diff-added')�[22m
�[2m    17 × locator resolved to <span data-diff="true" contenteditable="false" data-testid="diff-added" class="diff-added text-underline"><p>test case description changed</p></span>�[22m
�[2m       - unexpected value "<p>test case description changed</p>"�[22m

🟡 26 flaky test(s) (passed on retry)
  • Pages/UserCreationWithPersona.spec.ts › Create user with persona and verify on profile (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/ChangeSummaryBadge.spec.ts › Automated badge should appear on entity description with Automated source (shard 2, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should inherit reviewers from glossary when term is created (shard 2, 1 retry)
  • Features/Permissions/GlossaryPermissions.spec.ts › Team-based permissions work correctly (shard 3, 1 retry)
  • Features/QueryEntity.spec.ts › Query Entity (shard 3, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 2 retries)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Features/RestoreEntityInheritedFields.spec.ts › Validate restore with Inherited domain and data products assigned (shard 3, 1 retry)
  • Features/RTL.spec.ts › Verify Following widget functionality (shard 3, 1 retry)
  • Pages/Customproperties-part2.spec.ts › entityReferenceList shows item count, scrollable list, no expand toggle (shard 4, 1 retry)
  • Pages/DataContracts.spec.ts › Create Data Contract and validate for Table (shard 4, 2 retries)
  • Pages/DataContracts.spec.ts › Create Data Contract and validate for Topic (shard 4, 1 retry)
  • Pages/DataContractsSemanticRules.spec.ts › Validate Description Rule Is_Not_Set (shard 4, 1 retry)
  • Pages/DescriptionVisibility.spec.ts › Customized Table detail page Description widget shows long description (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Comprehensive domain rename with ALL relationships preserved (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Verify Domain entity API calls do not include invalid domains field in tag assets (shard 4, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: searchIndex (shard 6, 1 retry)
  • Pages/Lineage/PlatformLineage.spec.ts › Verify domain platform view (shard 6, 1 retry)
  • Pages/Login.spec.ts › Refresh should work (shard 6, 1 retry)
  • Pages/UserDetails.spec.ts › Create team with domain and verify visibility of inherited domain in user profile after team removal (shard 6, 1 retry)
  • Pages/Users.spec.ts › Permissions for table details page for Data Consumer (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

Copilot AI review requested due to automatic review settings April 13, 2026 12:42
@gitar-bot
Copy link
Copy Markdown

gitar-bot bot commented Apr 13, 2026

Code Review 🚫 Blocked 0 resolved / 2 findings

Cipher suite security fix blocked by two critical issues: DescriptionSanitizer is missing from PATCH/PUT update paths, and generic error messages obscure diagnostic information for BadRequest errors.

🚨 Security: DescriptionSanitizer not applied on PATCH/PUT updates

📄 openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java:95-99 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:1192 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:6624 📄 openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java:62-66 📄 openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java:74

The new DescriptionSanitizer is only called during entity creation (EntityRepository.java:1192) and task resolution (EntityRepository.java:8253). However, PATCH and PUT operations that update an entity's description bypass sanitization entirely — EntityUpdater.updateDescription() (line ~6624) records the change without calling DescriptionSanitizer.sanitize(). An attacker can create a clean entity, then update its description via PATCH/PUT with a malicious XSS payload, defeating the stored-XSS protection.

The fix should also sanitize descriptions in the update path. One approach is to sanitize in EntityUpdater.updateDescription() before recording the change, or to add sanitization in prepareInternal() so it covers both create and update flows.

Suggested fix
// In EntityUpdater.updateDescription() (~line 6616), sanitize before recording:
private void updateDescription() {
  String origDesc = DescriptionSanitizer.sanitize(original.getDescription());
  String updatedDesc = DescriptionSanitizer.sanitize(updated.getDescription());
  // ... use sanitized values for comparison and recording
}
⚠️ Edge Case: Generic error message loses diagnostic info for BadRequest

📄 openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogGenericExceptionMapper.java:53

The change at line 53 replaces the original exception message with a hardcoded "Invalid request parameter" for all ProcessingException, IllegalArgumentException, and BadRequestException cases. While this correctly prevents information leakage, it makes debugging significantly harder — a client won't know which parameter was invalid or why. Consider returning a safe subset of the exception message (e.g., from BadRequestException which already contains user-facing messages) while stripping internal details only for ProcessingException and IllegalArgumentException.

Suggested fix
// Differentiate between exception types:
if (ex instanceof BadRequestException) {
  return getResponse(BAD_REQUEST, ex.getMessage());
} else {
  return getResponse(BAD_REQUEST, "Invalid request parameter");
}
🤖 Prompt for agents
Code Review: Cipher suite security fix blocked by two critical issues: DescriptionSanitizer is missing from PATCH/PUT update paths, and generic error messages obscure diagnostic information for BadRequest errors.

1. 🚨 Security: DescriptionSanitizer not applied on PATCH/PUT updates
   Files: openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java:95-99, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:1192, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:6624, openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java:62-66, openmetadata-service/src/main/java/org/openmetadata/service/util/DescriptionSanitizer.java:74

   The new `DescriptionSanitizer` is only called during entity creation (EntityRepository.java:1192) and task resolution (EntityRepository.java:8253). However, PATCH and PUT operations that update an entity's description bypass sanitization entirely — `EntityUpdater.updateDescription()` (line ~6624) records the change without calling `DescriptionSanitizer.sanitize()`. An attacker can create a clean entity, then update its description via PATCH/PUT with a malicious XSS payload, defeating the stored-XSS protection.
   
   The fix should also sanitize descriptions in the update path. One approach is to sanitize in `EntityUpdater.updateDescription()` before recording the change, or to add sanitization in `prepareInternal()` so it covers both create and update flows.

   Suggested fix:
   // In EntityUpdater.updateDescription() (~line 6616), sanitize before recording:
   private void updateDescription() {
     String origDesc = DescriptionSanitizer.sanitize(original.getDescription());
     String updatedDesc = DescriptionSanitizer.sanitize(updated.getDescription());
     // ... use sanitized values for comparison and recording
   }

2. ⚠️ Edge Case: Generic error message loses diagnostic info for BadRequest
   Files: openmetadata-service/src/main/java/org/openmetadata/service/exception/CatalogGenericExceptionMapper.java:53

   The change at line 53 replaces the original exception message with a hardcoded `"Invalid request parameter"` for all `ProcessingException`, `IllegalArgumentException`, and `BadRequestException` cases. While this correctly prevents information leakage, it makes debugging significantly harder — a client won't know which parameter was invalid or why. Consider returning a safe subset of the exception message (e.g., from `BadRequestException` which already contains user-facing messages) while stripping internal details only for `ProcessingException` and `IllegalArgumentException`.

   Suggested fix:
   // Differentiate between exception types:
   if (ex instanceof BadRequestException) {
     return getResponse(BAD_REQUEST, ex.getMessage());
   } else {
     return getResponse(BAD_REQUEST, "Invalid request parameter");
   }

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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

Copilot reviewed 21 out of 24 changed files in this pull request and generated 4 comments.

Comment on lines 115 to 151
if (formatFor === 'server') {
anchorTags.forEach((tag, index) => {
const href = tag.getAttribute('href');
const text = tag.textContent;
const fqn = tag.getAttribute('data-fqn');
const entityType = tag.getAttribute('data-entityType');

const entityLink = `<#E${ENTITY_LINK_SEPARATOR}${entityType}${ENTITY_LINK_SEPARATOR}${fqn}|[${text}](${href})>`;
const marker = `${ENTITY_LINK_MARKER_PREFIX}${index}__`;

entityLinkMap.set(marker, entityLink);
tag.textContent = marker;
});
} else {
anchorTags.forEach((tag) => {
const label = tag.getAttribute('data-label');
const type = tag.getAttribute('data-type');
const prefix = type === 'mention' ? '@' : '#';

tag.textContent = `${prefix}${label}`;
});
}

let modifiedHtmlString = doc.body.innerHTML;

// Apply additional transformations based on format
if (formatFor === 'server') {
modifiedHtmlString =
modifiedHtmlString = getSanitizeContent(
blockEditorExtensionsClassBase.serializeContentForBackend(
modifiedHtmlString
);
)
);

// Replace markers with actual entity links
entityLinkMap.forEach((entityLink, marker) => {
modifiedHtmlString = modifiedHtmlString.replace(marker, entityLink);
});
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

In formatContent(..., 'server'), the HTML is sanitized before restoring entityLinkMap markers back to <#E...|[text](href)>. Since href is taken directly from the parsed <a> tag (line 117) and interpolated into entityLink (line 122), any unsafe URL scheme/value inserted into href will bypass DOMPurify in this flow. Consider restoring the entity links before sanitization and/or validating/normalizing href to an allowlist of protocols before building the entityLink string.

Copilot uses AI. Check for mistakes.
Comment on lines 1223 to 1226
entity.setDisplayName(request.getDisplayName());
entity.setDescription(request.getDescription());
entity.setDescription(
org.openmetadata.service.util.DescriptionSanitizer.sanitize(request.getDescription()));
entity.setOwners(owners);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

Description sanitization is applied when copying entities (create) and in DescriptionTaskWorkflow, but regular PUT/PATCH updates still ultimately persist whatever updated.getDescription() is set to (see updateDescription()), without enforcing sanitization at the persistence layer. This leaves a path for unsanitized description HTML to be stored. Consider sanitizing descriptions in the centralized update flow (e.g., in updateDescription() or wherever request payloads are applied) so all description writes are consistently sanitized.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +71
// Cross-Origin-Embedder-Policy
if (webConfig.getCrossOriginEmbedderPolicyHeaderFactory() != null) {
headers.putAll(webConfig.getCrossOriginEmbedderPolicyHeaderFactory().build());
}

// Cross-Origin-Resource-Policy
if (webConfig.getCrossOriginResourcePolicyHeaderFactory() != null) {
headers.putAll(webConfig.getCrossOriginResourcePolicyHeaderFactory().build());
}

// Cross-Origin-Opener-Policy
if (webConfig.getCrossOriginOpenerPolicyHeaderFactory() != null) {
headers.putAll(webConfig.getCrossOriginOpenerPolicyHeaderFactory().build());
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

OMWebBundle now applies COEP/CORP/COOP headers via the new factories, but error responses/pages appear to set security headers via a separate path (OMErrorPageHandler.setSecurityHeaders(...)) which currently doesn't include these new headers. If these headers are enabled, clients may see inconsistent behavior between normal and error responses. Consider updating the error-page header injection path to include the same cross-origin headers when configured.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +20
public static final String CROSS_ORIGIN_OPENER_POLICY_HEADER = "Cross-Origin-Opener-Policy";

@JsonProperty("option")
private String option = "same-origin";

@Override
protected Map<String, String> buildHeaders() {
return Collections.singletonMap(CROSS_ORIGIN_OPENER_POLICY_HEADER, option);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

These Cross-Origin-* header factories model the header value as a free-form String (option). In this codebase, other header factories (e.g., FrameOptionsHeaderFactory) use enums to constrain/validate supported options. Consider introducing enums for COEP/CORP/COOP options to prevent invalid configuration values and make the supported set explicit.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link
Copy Markdown

@sonarqubecloud
Copy link
Copy Markdown

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.

3 participants