Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
896b1f9
fix(security): validate profile image URLs to prevent SSRF
Sharaf0 Apr 29, 2026
f1bff31
docs: add updating-datahub entry for profile image URL validation
Sharaf0 Apr 29, 2026
cde0d35
fix(frontend): keep edit profile modal open on save failure Move onC…
Sharaf0 Apr 29, 2026
165cd29
fix(frontend): reset form state on failed profile update
Sharaf0 Apr 29, 2026
e0dd72c
fix(security): allow empty/blank pictureLink in UrlValidator
Sharaf0 Apr 29, 2026
0f87933
feat: make UrlValidator configurable via application.yaml
Sharaf0 Apr 30, 2026
afeb023
test: classify urlValidation config properties as non-sensitive
Sharaf0 Apr 30, 2026
79e4866
docs(observe): rework assertions docs around Anomaly Detection and fe…
AdrianMachado May 8, 2026
9c53761
docs(athena): refine permissions policy (#17341)
dennisliu-wag May 9, 2026
d5cf03d
feat(stateful-ingestion): add per-entity-type deletion summary to ing…
aviraj-gour May 11, 2026
c902e48
refactor: replace buildSchemes() with hand-written setAllowHttp
Sharaf0 May 11, 2026
5b924d4
refactor(ingest/bigquery): stop writing service account secret to env…
rajatoss May 11, 2026
47f3894
feat(smoke-test): add degree and skip_cache params to search_across_l…
sachetansabhahit May 11, 2026
5e8a744
test(playwright): add changes required for glossary playwright tests …
v-tarasevich-blitz-brain May 11, 2026
0570a83
feat(ingest/athena): add upstream lineage for Iceberg/Glue (#16842)
alokr-dhub May 11, 2026
d8970db
docs(ingestion): Add capability for operations (#17366)
AdrianMachado May 11, 2026
5cd96bf
docs(executor): add ingestion executor security and hardening guide (…
david-leifker May 11, 2026
f2c8c76
fix(search): compute SP diffs independently of mappingsDiff [PFP-3594…
alexjst May 11, 2026
f5dd5b8
fix(deps): bump Vert.x to 4.5.27 for CVE-2026-6860 (#17386)
david-leifker May 11, 2026
79917cb
feat(search): remove Criterion singular value field (#17365)
david-leifker May 11, 2026
2cf39ae
perf(frontend): reduce listRecommendations fan-out and consolidate ho…
ani-malgari May 12, 2026
7585b6a
Merge branch 'upstream/master' into fix/security-validate-profile-ima…
Sharaf0 May 12, 2026
85caa39
Merge remote-tracking branch 'upstream/master' into fix/security-vali…
Sharaf0 May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ buildscript {
ext.openLineageVersion = '1.33.0'
ext.awsSdk2Version = '2.30.33'
ext.micrometerVersion = '1.16.4'
ext.vertxVersion = '4.5.24' // CVE-2026-1002 (e.g. from fabric8 kubernetes-httpclient-vertx)
// CVE-2026-1002; CVE-2026-6860 / GHSA-3g76-f9xq-8vp6 (SNI cache growth); fabric8 kubernetes-httpclient-vertx
ext.vertxVersion = '4.5.27'
// CVE-2026-41417 (netty-codec-http): 4.1.133+ or 4.2.13+; CVE-2026-42577 (netty-transport-native-epoll): 4.2.13+
ext.nettyVersion = '4.2.13.Final' // align all io.netty modules (excl. netty-tcnative line)
// CVE-2026-40542 / GHSA: SCRAM-SHA-256 authentication verification (httpclient5 5.6 → 5.6.1+)
Expand Down
8 changes: 4 additions & 4 deletions datahub-frontend/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ io.prometheus:prometheus-metrics-exposition-formats:1.4.3=runtimeClasspath,testR
io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=runtimeClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.vertx:vertx-auth-common:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-core:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-client:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-common:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-auth-common:4.5.27=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-core:4.5.27=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-client:4.5.27=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-common:4.5.27=runtimeClasspath,testRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.json:jakarta.json-api:2.1.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
Expand Down
8 changes: 4 additions & 4 deletions datahub-graphql-core/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,10 @@ io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,runtimeCla
io.swagger.core.v3:swagger-annotations:2.2.30=runtimeClasspath,testRuntimeClasspath
io.vavr:vavr-match:0.10.2=runtimeClasspath,testRuntimeClasspath
io.vavr:vavr:0.10.2=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-auth-common:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-core:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-client:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-common:4.5.24=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-auth-common:4.5.27=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-core:4.5.27=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-client:4.5.27=runtimeClasspath,testRuntimeClasspath
io.vertx:vertx-web-common:4.5.27=runtimeClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.2=runtimeClasspath,testRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.json:jakarta.json-api:2.1.3=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,7 @@ public static Criterion criterionFromFilter(final FacetFilterInput filter) {
condition = Condition.EQUAL;
}

final List<String> values;
if (filter.getValues() == null && filter.getValue() != null) {
values = Collections.singletonList(filter.getValue());
} else {
values = filter.getValues();
}
final List<String> values = filter.getValues();

return buildCriterion(filter.getField(), condition, filter.getNegated(), values);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public CompletableFuture<ListServiceAccountsResult> get(DataFetchingEnvironment
ImmutableList.of(
new FacetFilterInput(
SUB_TYPES_FIELD,
null,
ImmutableList.of(ServiceAccountUtils.SERVICE_ACCOUNT_SUB_TYPE),
false,
FilterOperator.EQUAL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,9 @@ public CompletableFuture<SearchResults> get(DataFetchingEnvironment environment)
* filter determine which urns we filter search results on. Otherwise, if no output port filter is
* found, return all asset urns as per usual.
*/
// Package-private for unit tests.
@Nonnull
private List<Urn> getUrnsToFilterOn(
List<Urn> getUrnsToFilterOn(
@Nonnull final List<Urn> assetUrns,
@Nonnull final Set<String> outputPortUrns,
@Nullable final List<FacetFilterInput> filters) {
Expand All @@ -240,7 +241,10 @@ private List<Urn> getUrnsToFilterOn(
// optionally get entities that explicitly are or are not output ports
List<Urn> urnsToFilterOn = assetUrns;
if (isOutputPort.isPresent()) {
if (isOutputPort.get().getValue().equals("true")) {
List<String> outputPortVals = isOutputPort.get().getValues();
if (outputPortVals != null
&& !outputPortVals.isEmpty()
&& "true".equals(outputPortVals.get(0))) {
urnsToFilterOn = outputPortUrns.stream().map(UrnUtils::getUrn).collect(Collectors.toList());
} else {
urnsToFilterOn =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,11 @@ private Urn getLatestSuccessfulExecutionRequestUrn(
List.of(
new FacetFilterInput(
INGESTION_SOURCE_FIELD,
null,
List.of(ingestionSourceUrn),
false,
FilterOperator.EQUAL),
new FacetFilterInput(
RESULT_STATUS_FIELD,
null,
List.of(STATUS_SUCCESS),
false,
FilterOperator.EQUAL)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ private void addAccessibleIngestionSourceFilter(
filters.add(
new FacetFilterInput(
EXECUTION_REQUEST_INGESTION_SOURCE_FIELD,
null,
sourceUrns.stream().map(Urn::toString).toList(),
false,
FilterOperator.EQUAL));
Expand All @@ -151,7 +150,6 @@ private List<Urn> getUrnsOfIngestionSources(
? List.of(
new FacetFilterInput(
INGESTION_SOURCE_SOURCE_TYPE_FIELD,
null,
ImmutableList.of(INGESTION_SOURCE_SOURCE_TYPE_SYSTEM),
!systemSources,
FilterOperator.EQUAL))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,7 @@ private Filter buildFilters(@Nullable String maybeResourceUrn, List<AndFilterInp
new AndFilterInput(
List.of(
new FacetFilterInput(
"target",
null,
ImmutableList.of(maybeResourceUrn),
false,
FilterOperator.EQUAL))));
"target", ImmutableList.of(maybeResourceUrn), false, FilterOperator.EQUAL))));
}

return ResolverUtils.buildFilter(null, filters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ private Filter buildFilters(@Nonnull final ListQueriesInput input) {
andConditions.add(
new FacetFilterInput(
QUERY_SOURCE_FIELD,
null,
ImmutableList.of(input.getSource().toString()),
false,
FilterOperator.EQUAL));
Expand All @@ -145,7 +144,6 @@ private Filter buildFilters(@Nonnull final ListQueriesInput input) {
andConditions.add(
new FacetFilterInput(
QUERY_ENTITIES_FIELD,
null,
ImmutableList.of(input.getDatasetUrn()),
false,
FilterOperator.EQUAL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public CompletableFuture<ListUsersResult> get(final DataFetchingEnvironment envi
ImmutableList.of(
new FacetFilterInput(
SUB_TYPES_FIELD,
null,
ImmutableList.of(SERVICE_ACCOUNT_SUB_TYPE),
true, // negated = true to exclude service accounts
FilterOperator.EQUAL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ public CompletableFuture<SearchResults> get(DataFetchingEnvironment environment)
ImmutableList.of(
new FacetFilterInput(
SUB_TYPES_FIELD,
null,
ImmutableList.of(SERVICE_ACCOUNT_SUB_TYPE),
true, // negated = true to exclude service accounts
FilterOperator.EQUAL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ private Filter buildFilters() {
andConditions.add(
new FacetFilterInput(
VIEW_TYPE_FIELD,
null,
ImmutableList.of(DataHubViewType.GLOBAL.name()),
false,
FilterOperator.EQUAL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ private Filter buildFilters(
final List<FacetFilterInput> andConditions = new ArrayList<>();
andConditions.add(
new FacetFilterInput(
CREATOR_URN_FIELD, null, ImmutableList.of(creatorUrn), false, FilterOperator.EQUAL));
CREATOR_URN_FIELD, ImmutableList.of(creatorUrn), false, FilterOperator.EQUAL));
if (viewType != null) {
andConditions.add(
new FacetFilterInput(
VIEW_TYPE_FIELD, null, ImmutableList.of(viewType), false, FilterOperator.EQUAL));
VIEW_TYPE_FIELD, ImmutableList.of(viewType), false, FilterOperator.EQUAL));
}
filterCriteria.setAnd(andConditions);

Expand Down
6 changes: 0 additions & 6 deletions datahub-graphql-core/src/main/resources/search.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -528,12 +528,6 @@ input FacetFilterInput {
"""
field: String!

"""
Value of the field to filter by. Deprecated in favor of `values`, which should accept a single element array for a
value
"""
value: String @deprecated(reason: "Prefer `values` for single elements")

"""
Values, one of which the intended field should match.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public void testCriterionFromFilter() throws Exception {
criterionFromFilter(
new FacetFilterInput(
"tags",
null,
ImmutableList.of("urn:li:tag:abc", "urn:li:tag:def"),
false,
FilterOperator.EQUAL));
Expand All @@ -64,16 +63,16 @@ public void testCriterionFromFilter() throws Exception {
buildCriterion(
"tags", Condition.EQUAL, ImmutableList.of("urn:li:tag:abc", "urn:li:tag:def")));

// this is the legacy pathway
Criterion valueCriterion =
Criterion singleValueCriterion =
criterionFromFilter(
new FacetFilterInput("tags", "urn:li:tag:abc", null, true, FilterOperator.EQUAL));
assertEquals(valueCriterion, buildCriterion("tags", Condition.EQUAL, true, "urn:li:tag:abc"));
new FacetFilterInput(
"tags", ImmutableList.of("urn:li:tag:abc"), true, FilterOperator.EQUAL));
assertEquals(
singleValueCriterion, buildCriterion("tags", Condition.EQUAL, true, "urn:li:tag:abc"));

// check that both being null doesn't cause a NPE. this should never happen except via API
// interaction
// check that null values doesn't cause a NPE
Criterion doubleNullCriterion =
criterionFromFilter(new FacetFilterInput("tags", null, null, true, FilterOperator.EQUAL));
criterionFromFilter(new FacetFilterInput("tags", null, true, FilterOperator.EQUAL));
assertEquals(
doubleNullCriterion, buildCriterion("tags", Condition.EQUAL, true, ImmutableList.of()));
}
Expand Down Expand Up @@ -246,7 +245,7 @@ public void testBuildFacetFilters_valid() {
List<FacetFilterInput> inputs =
ImmutableList.of(
new FacetFilterInput(
"field.keyword", "v1", ImmutableList.of("v1"), false, FilterOperator.EQUAL));
"field.keyword", ImmutableList.of("v1"), false, FilterOperator.EQUAL));
Map<String, String> result = buildFacetFilters(inputs, Set.of("field.keyword"));
assertEquals(result.get("field.keyword"), "v1");
}
Expand All @@ -255,8 +254,7 @@ public void testBuildFacetFilters_valid() {
public void testBuildFacetFilters_invalidField() {
List<FacetFilterInput> inputs =
ImmutableList.of(
new FacetFilterInput(
"invalid", "v1", ImmutableList.of("v1"), false, FilterOperator.EQUAL));
new FacetFilterInput("invalid", ImmutableList.of("v1"), false, FilterOperator.EQUAL));
buildFacetFilters(inputs, Set.of("field.keyword"));
}

Expand All @@ -276,7 +274,7 @@ public void testBuildFilter_orFilters() {
@Test
public void testBuildFilter_andFiltersOnly() {
FacetFilterInput and =
new FacetFilterInput("f.keyword", "v", ImmutableList.of("v"), false, FilterOperator.EQUAL);
new FacetFilterInput("f.keyword", ImmutableList.of("v"), false, FilterOperator.EQUAL);
Filter f = buildFilter(ImmutableList.of(and), null);
assertNotNull(f);
assertNotNull(f.getOr());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.linkedin.datahub.graphql.resolvers.dataproduct;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.linkedin.common.urn.Urn;
import com.linkedin.datahub.graphql.generated.FacetFilterInput;
import com.linkedin.entity.client.EntityClient;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.mockito.Mockito;
import org.testng.annotations.Test;

public class ListDataProductAssetsResolverTest {

private static final Urn ASSET_A = Urn.createFromTuple("dataset", "urn-a");
private static final Urn ASSET_B = Urn.createFromTuple("dataset", "urn-b");

@Test
public void testGetUrnsToFilterOn_noFilters_returnsAllAssets() {
ListDataProductAssetsResolver resolver =
new ListDataProductAssetsResolver(Mockito.mock(EntityClient.class));

List<Urn> result =
resolver.getUrnsToFilterOn(
ImmutableList.of(ASSET_A, ASSET_B), ImmutableSet.of(ASSET_A.toString()), null);

assertEquals(result.size(), 2);
assertTrue(result.contains(ASSET_A));
assertTrue(result.contains(ASSET_B));
}

@Test
public void testGetUrnsToFilterOn_outputPortTrue_filtersToOutputPortUrnsOnly() {
ListDataProductAssetsResolver resolver =
new ListDataProductAssetsResolver(Mockito.mock(EntityClient.class));

FacetFilterInput filter = new FacetFilterInput();
filter.setField("isOutputPort");
filter.setValues(ImmutableList.of("true"));

List<Urn> result =
resolver.getUrnsToFilterOn(
ImmutableList.of(ASSET_A, ASSET_B),
ImmutableSet.of(ASSET_A.toString()),
new ArrayList<>(Collections.singletonList(filter)));

assertEquals(result.size(), 1);
assertEquals(result.get(0), ASSET_A);
}

@Test
public void testGetUrnsToFilterOn_outputPortFalse_excludesOutputPorts() {
ListDataProductAssetsResolver resolver =
new ListDataProductAssetsResolver(Mockito.mock(EntityClient.class));

FacetFilterInput filter = new FacetFilterInput();
filter.setField("isOutputPort");
filter.setValues(ImmutableList.of("false"));

List<Urn> result =
resolver.getUrnsToFilterOn(
ImmutableList.of(ASSET_A, ASSET_B),
ImmutableSet.of(ASSET_A.toString()),
new ArrayList<>(Collections.singletonList(filter)));

assertEquals(result.size(), 1);
assertEquals(result.get(0), ASSET_B);
}

@Test
public void testGetUrnsToFilterOn_outputPortEmptyValues_excludesOutputPorts() {
ListDataProductAssetsResolver resolver =
new ListDataProductAssetsResolver(Mockito.mock(EntityClient.class));

FacetFilterInput filter = new FacetFilterInput();
filter.setField("isOutputPort");
filter.setValues(ImmutableList.of());

List<Urn> result =
resolver.getUrnsToFilterOn(
ImmutableList.of(ASSET_A, ASSET_B),
ImmutableSet.of(ASSET_A.toString()),
new ArrayList<>(Collections.singletonList(filter)));

assertEquals(result.size(), 1);
assertEquals(result.get(0), ASSET_B);
}

@Test
public void testGetUrnsToFilterOn_outputPortNullValues_excludesOutputPorts() {
ListDataProductAssetsResolver resolver =
new ListDataProductAssetsResolver(Mockito.mock(EntityClient.class));

FacetFilterInput filter = new FacetFilterInput();
filter.setField("isOutputPort");
filter.setValues(null);

List<Urn> result =
resolver.getUrnsToFilterOn(
ImmutableList.of(ASSET_A, ASSET_B),
ImmutableSet.of(ASSET_A.toString()),
new ArrayList<>(Collections.singletonList(filter)));

assertEquals(result.size(), 1);
assertEquals(result.get(0), ASSET_B);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,11 @@ private EntityClient getMockedEntityClient(SearchResult filterSearchResult) thro
List.of(
new FacetFilterInput(
"ingestionSource",
null,
List.of(TEST_INGESTION_SOURCE_URN),
false,
FilterOperator.EQUAL),
new FacetFilterInput(
"executionResultStatus",
null,
List.of("SUCCESS"),
false,
FilterOperator.EQUAL)),
Expand Down
Loading