Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 14 additions & 7 deletions packages/code-analyzer-sfge-engine/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ hamcrest = "3.0"
junit-jupiter = "5.13.4"
commons-cli = "1.10.0"
commons-collections4 = "4.5.0"
tinkergraph-gremlin = "4.0.0-beta.1"
gremlin-driver = "4.0.0-beta.1"
tinkergraph-gremlin = "3.7.7"
antlr-runtime = "3.5.3"
log4j-api = "2.25.1"
log4j-core = "2.25.1"
log4j-api = "2.25.5"
log4j-core = "2.25.5"
gson = "2.13.2"
guava = "33.4.8-jre"
jsr305 = "3.0.2"
Expand All @@ -23,15 +22,19 @@ reflections = "0.10.2"
asm = "9.8"
mockito-core = "5.19.0"
mockito-junit-jupiter = "5.19.0"
netty-all = "4.1.127.Final"
# Versions below force patched releases of dependencies that arrive transitively (via gremlin-core). See sfge/build.gradle.kts.
commons-configuration2 = "2.15.1"
commons-beanutils = "1.11.0"
commons-lang3 = "3.21.0"
commons-text = "1.15.0"
snakeyaml = "2.7"

[libraries]
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
commons-cli = { module = "commons-cli:commons-cli", version.ref = "commons-cli" }
commons-collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "commons-collections4" }
tinkergraph-gremlin = { module = "org.apache.tinkerpop:tinkergraph-gremlin", version.ref = "tinkergraph-gremlin" }
gremlin-driver = { module = "org.apache.tinkerpop:gremlin-driver", version.ref = "gremlin-driver" }
antlr-runtime = { module = "org.antlr:antlr-runtime", version.ref = "antlr-runtime" }
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j-api" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j-core" }
Expand All @@ -44,4 +47,8 @@ asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito-core" }
mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito-junit-jupiter" }
netty-all = { module = "io.netty:netty-all", version.ref = "netty-all" }
commons-configuration2 = { module = "org.apache.commons:commons-configuration2", version.ref = "commons-configuration2" }
commons-beanutils = { module = "commons-beanutils:commons-beanutils", version.ref = "commons-beanutils" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" }
commons-text = { module = "org.apache.commons:commons-text", version.ref = "commons-text" }
snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" }
2 changes: 1 addition & 1 deletion packages/code-analyzer-sfge-engine/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@salesforce/code-analyzer-sfge-engine",
"description": "Plugin package that adds 'Salesforce Graph Engine' as an engine into Salesforce Code Analyzer",
"version": "0.25.0",
"version": "0.26.0-SNAPSHOT",
"author": "The Salesforce Code Analyzer Team",
"license": "BSD-3-Clause",
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",
Expand Down
67 changes: 63 additions & 4 deletions packages/code-analyzer-sfge-engine/sfge/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,18 @@ repositories {
dependencies {
implementation(libs.commons.cli)
implementation(libs.commons.collections4)
implementation(libs.tinkergraph.gremlin)
implementation(libs.gremlin.driver)
implementation(libs.tinkergraph.gremlin) {
// commons-collections 3.x (pulled in by commons-beanutils) has known vulnerabilities and no patched release.
// SFGE and the Gremlin graph layer do not use it, so it is removed from the classpath entirely.
exclude(group = "commons-collections", module = "commons-collections")
// gremlin-shaded bundles (relocated) copies of Jackson and Kryo that are only used for graph import/export
// (GraphSON/Gryo/GraphML). SFGE only builds and traverses an in-memory graph, and the bundled Jackson version
// contains known vulnerabilities that cannot be patched via dependency constraints, so we do not ship it.
exclude(group = "org.apache.tinkerpop", module = "gremlin-shaded")
}
// These are used directly by SFGE source (they previously arrived only transitively), so declare them explicitly.
implementation(libs.commons.lang3)
implementation(libs.commons.text)
implementation(libs.antlr.runtime)
implementation(libs.log4j.api)
implementation(libs.log4j.core)
Expand All @@ -35,9 +45,16 @@ dependencies {
}
implementation(libs.reflections)
implementation(libs.asm)
// Force patched versions of transitive dependencies of gremlin-core (embedded in-memory graph).
constraints {
implementation(libs.netty.all) {
because("Force specific version of transitive dependency")
implementation(libs.commons.configuration2) {
because("Patched version of a transitive dependency of gremlin-core")
}
implementation(libs.commons.beanutils) {
because("Patched version of a transitive dependency of gremlin-core")
}
implementation(libs.snakeyaml) {
because("Patched version of a transitive dependency of gremlin-core")
}
}
implementation(files("lib/apex-jorje-lsp-sfge.jar"))
Expand Down Expand Up @@ -71,11 +88,53 @@ tasks.jar {
tasks.register<Copy>("copyDependencies") {
from(configurations.runtimeClasspath) // This includes all runtime dependencies
into(javaLibDir) // Target directory for dependencies
// The directory is also the output of the jar task (sfge-*.jar), so we can't use Sync. Instead, remove any stale
// dependency jars left over from earlier builds (e.g. ones since removed from the runtime classpath) so that an
// incremental build packs exactly the same dependency set as a clean build.
doFirst {
delete(fileTree(javaLibDir) { exclude("sfge-*.jar") })
}
}
tasks.build {
dependsOn("copyDependencies") // Ensure dependencies are copied during the build process
}

// Fail the build if a library that is known to carry unpatchable vulnerabilities (or that SFGE has no need for) sneaks
// back onto the runtime classpath, e.g., through a Gremlin upgrade. Everything on the runtime classpath gets shipped.
val bannedRuntimeDependencies = mapOf(
"io.netty" to "Only needed by gremlin-driver (remote Gremlin client), which SFGE does not use",
"software.amazon.awssdk" to "Only needed by gremlin-driver (remote Gremlin client), which SFGE does not use",
"org.apache.tinkerpop:gremlin-driver" to "SFGE only uses the embedded in-memory graph",
"org.apache.tinkerpop:gremlin-util" to "SFGE only uses the embedded in-memory graph",
"org.apache.tinkerpop:gremlin-shaded" to "Bundles Jackson/Kryo with known vulnerabilities that cannot be patched via constraints",
"commons-collections:commons-collections" to "3.x has known vulnerabilities and no patched release (SFGE uses commons-collections4)"
)
tasks.register("verifyNoBannedDependencies") {
group = "verification"
description = "Fails if a banned dependency is on the runtime classpath (and therefore would be shipped)."
val resolvedModules = configurations.runtimeClasspath.map { config ->
config.incoming.resolutionResult.allComponents
.mapNotNull { it.moduleVersion }
.map { "${it.group}:${it.name}:${it.version}" }
}
doLast {
val violations = resolvedModules.get().flatMap { coordinates ->
val (group, name) = coordinates.split(":")
bannedRuntimeDependencies.filter { (banned, _) -> banned == group || banned == "$group:$name" }
.map { (_, reason) -> "$coordinates -> $reason" }
}
if (violations.isNotEmpty()) {
throw GradleException("Banned dependencies found on the runtime classpath:\n " + violations.joinToString("\n "))
}
}
}
tasks.check {
dependsOn("verifyNoBannedDependencies")
}
tasks.build {
dependsOn("verifyNoBannedDependencies")
}


// ======== TEST RELATED TASKS =========================================================================================
jacoco {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.apache.tinkerpop.shaded.jackson.core;

import java.io.IOException;

/**
* Minimal stand-in for the relocated Jackson exception type that {@code TinkerGraph} references in a {@code catch}
* clause.
*
* <p>Why this exists: {@code gremlin-shaded} bundles (relocated) copies of Jackson and Kryo. The Jackson version it
* bundles contains known vulnerabilities that cannot be fixed with dependency constraints, and SFGE does not use it
* since SFGE only builds and traverses an in-memory graph (no GraphSON/Gryo/GraphML import or export). So we exclude
* {@code gremlin-shaded} from the runtime classpath (see {@code build.gradle.kts}). However, the JVM must still be able
* to load this one exception class when it verifies {@code TinkerGraph}, even though no Jackson code ever runs.
*
* <p>This class intentionally contains no Jackson code. Remove it (and re-evaluate the {@code gremlin-shaded} exclusion)
* when upgrading to a Gremlin release whose bundled Jackson is patched. {@code NoVulnerableBundledLibrariesTest}
* guards this behavior.
*/
public class JsonProcessingException extends IOException {
public JsonProcessingException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.salesforce.graph.ops;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

/**
* Guards the dependency-hygiene decisions documented in {@code build.gradle.kts}: SFGE only needs an in-memory
* TinkerGraph, and must not ship libraries that carry known vulnerabilities.
*/
public class NoVulnerableBundledLibrariesTest {
@Test
public void inMemoryGraphWorksWithoutGremlinShaded() {
// Fails with NoClassDefFoundError if TinkerGraph needs anything from gremlin-shaded beyond what we provide.
assertNotNull(GraphUtil.getGraph());
}

@Test
public void gremlinShadedJacksonImplementationIsNotOnClasspath() {
assertThrows(
ClassNotFoundException.class,
() -> Class.forName("org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper"));
assertThrows(
ClassNotFoundException.class,
() -> Class.forName("org.apache.tinkerpop.shaded.jackson.core.JsonFactory"));
}

@Test
public void commonsCollections3IsNotOnClasspath() {
assertThrows(
ClassNotFoundException.class,
() -> Class.forName("org.apache.commons.collections.FastHashMap"));
}

@Test
public void nettyAndGremlinDriverAreNotOnClasspath() {
assertThrows(ClassNotFoundException.class, () -> Class.forName("io.netty.channel.Channel"));
assertThrows(
ClassNotFoundException.class,
() -> Class.forName("org.apache.tinkerpop.gremlin.driver.Cluster"));
}
}
Loading