Skip to content

FIX Remediate SFGE transitive dependency vulnerabilities - #512

Open
nikhil-mittal-165 wants to merge 3 commits into
devfrom
nikhil.mittal/sfge-cve-remediation
Open

nikhil-mittal-165 wants to merge 3 commits into
devfrom
nikhil.mittal/sfge-cve-remediation

Conversation

@nikhil-mittal-165

@nikhil-mittal-165 nikhil-mittal-165 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Removes every known-vulnerable library from the Java runtime that @salesforce/code-analyzer-sfge-engine ships (dist/java-lib), so that dependency firewalls (Sonatype Repository Firewall flagged 27 CVEs) stop blocking the engine.

Shipped Java libraries (dist/java-lib) Before (dev) After
JARs 97 35
Libraries with known advisories 24 0
Distinct advisories (GHSA and CVE aliases counted separately) 84 0

Scanner: osv-scanner scan source --no-ignore --x-plugins=java/archive dist/java-lib. This inspects JAR contents, including shaded/relocated classes. The 84 vs Sonatype's 27 is mostly alias double counting plus advisories published since the Sonatype scan.

No work item yet. validate_pr_title fails because the title has no @W-xxxxxxxx@. The title will be updated once a work item exists.


Background

Exploitability: not exploitable in SFGE

These are dependency-hygiene findings, not vulnerabilities reachable in SFGE. SFGE parses Apex from disk into an embedded, in-memory TinkerGraph, analyses it, and writes JSON.

Component Why the vulnerable code is not reachable
Netty, AWS SDK Present only through gremlin-driver (remote Gremlin client). SFGE opens no ports and makes no remote connections; 0 references to gremlin.driver, Cluster, DriverRemoteConnection, withRemote.
Jackson (bundled inside gremlin-shaded) Used only for GraphSON/Gryo/GraphML import/export. SFGE never reads or writes graphs.
commons-collections 3.x Deserialization gadget chain needs an attacker-controlled Java serialized stream. SFGE does no Java deserialization; it uses commons-collections4.
commons-configuration2, commons-beanutils No configuration is loaded; the single graph entry point is TinkerGraph.open() with no arguments (GraphUtil.getGraph()).
Log4j log4j2.xml uses only PatternLayout (Console, RollingFile, two custom appenders). No Syslog/Socket/RFC5424/XML layouts, no JNDI. The only lookup is ${sys:sfge_log_name}, which the TS wrapper sets.

We remove them anyway, because "not exploitable" does not get the engine through other companies' firewalls.

Root cause

  1. Unused gremlin-driver. It has been there since the SFGE Java layer was copied in (4e77be1, Mar 2025) and is the only source of Netty and the AWS SDK (about 60 JARs).
  2. Stale transitive libraries from gremlin-core: commons-configuration2 2.9.0, commons-beanutils 1.9.4, commons-collections 3.2.2, commons-lang3 3.12.0.
  3. Gremlin 4.0.0-beta.1. It came in with fb51918 (Oct 2025, titled "Updated node dependencies"). Every 4.0 beta bundles Jackson 2.15.2 inside gremlin-shaded, and a dependency constraint cannot patch that.
  4. Java dependencies are outside the monthly refresh. gradle/libs.versions.toml had not changed since Oct 21, 2025; the monthly release only refreshes npm.

Changes

gradle/libs.versions.toml

Library Before After Note
gremlin-driver 4.0.0-beta.1 removed Unused
netty-all (constraint) 4.1.127.Final removed Netty is no longer on the classpath
tinkergraph-gremlin 4.0.0-beta.1 3.7.7 See "Why 3.7.7"
log4j-api, log4j-core 2.25.1 2.25.5 Latest 2.25.x
commons-configuration2 2.9.0 (transitive) 2.15.1 New constraint
commons-beanutils 1.9.4 (transitive) 1.11.0 New constraint
snakeyaml 2.0 (transitive) 2.7 New constraint
commons-lang3 3.12.0 (transitive) 3.21.0 Now declared directly
commons-text 1.10.0 (transitive) 1.15.0 Now declared directly

sfge/build.gradle.kts

  • Dropped implementation(libs.gremlin.driver) and the netty-all constraint.
  • Two exclusions on tinkergraph-gremlin:
    • commons-collections:commons-collections (3.x has no patched release).
    • org.apache.tinkerpop:gremlin-shaded (bundles vulnerable Jackson and Kryo that cannot be patched by constraint).
  • Constraints that force patched commons-configuration2, commons-beanutils and snakeyaml.
  • commons-lang3 and commons-text are now declared explicitly. SFGE imports both (ApexStringValue uses commons-text), but they previously arrived only transitively.
  • New verifyNoBannedDependencies task, wired into build and check. It fails if Netty, the AWS SDK, gremlin-driver, gremlin-util, gremlin-shaded or commons-collections 3.x reappear on the runtime classpath, for example after a Gremlin upgrade.

New sfge/src/main/java/org/apache/tinkerpop/shaded/jackson/core/JsonProcessingException.java

A minimal stand-in class (extends IOException, contains no Jackson code). TinkerGraph names this type in a catch clause, so the JVM must be able to load it, even though no Jackson code ever runs. Without it, every test fails with NoClassDefFoundError at TinkerGraph.<init>. The Javadoc explains when to remove it.

New sfge/src/test/java/com/salesforce/graph/ops/NoVulnerableBundledLibrariesTest.java

Checks four things: the in-memory graph still opens; the real shaded Jackson implementation (ObjectMapper, JsonFactory) is absent; commons-collections 3.x is absent; Netty and gremlin-driver are absent.

package.json

0.25.0 → 0.26.0-SNAPSHOT, as required by validate_packages.


Why Gremlin 3.7.7 rather than a newer version

Version Outcome
4.0.0-beta.1 / beta.2 / beta.3 All bundle Jackson 2.15.2 in gremlin-shaded, and are pre-release.
3.8.2 (latest stable) 7 SFGE tests fail (TraversalUtilTest ×6, InboundEmailHandlerSupplierTest), so traversal behaviour differs. The same tests pass on 4.0.0-beta.1, 3.7.7 and 3.7.4. Moving to 3.8 needs its own investigation.
3.7.7 All tests pass. SFGE ran on the 3.7 line (3.7.4) until Oct 2025.

Even 3.7.7 bundles Jackson 2.18.9, which has 7 advisories fixed only in 2.18.11. That is why gremlin-shaded is excluded regardless of the Gremlin version.


Testing

Unit and integration tests

  • ./gradlew test jacocoTestReport jacocoTestCoverageVerification: full Java suite passes (~4,600 tests), 80% coverage gate passes.
  • npm run build and npm run test-typescript: 4 suites, 100 tests pass. engine.test.ts launches the real JVM on sample workspaces and compares results with gold files.
  • CI run_tests passes on ubuntu, macOS and windows.
  • Negative test for the guard: re-adding gremlin-driver makes verifyNoBannedDependencies fail and list gremlin-driver, gremlin-util, netty-* and commons-collections:3.2.2.

Real-world comparison on NPSP (Nonprofit Success Pack)

Both engine builds were run on SalesforceFoundation/NPSP main (1e9e619). Scope: force-app, 1,061 Apex files (~319K lines), all 7 SFGE rules. Each build ran java com.salesforce.Sfge execute with the same input file and the same defaults as the TS wrapper (8 threads, 30 s per-path timeout), one at a time on the same machine (16 cores, 64 GB).

Full run, default settings

dev (Gremlin 4.0.0-beta.1) This PR (Gremlin 3.7.7)
Wall time 649.5 s 649.3 s
CPU time 3,336 s 3,485 s (+4%)
Peak memory 17.3 GB 17.5 GB (+1%)
Real violations 217 231
Timeouts / engine warnings 123 122

Per rule:

Rule dev PR
ApexFlsViolation 62 63
AvoidDatabaseOperationInLoop 5 5
AvoidMultipleMassSchemaLookups 46 57
DatabaseOperationsMustUseWithSharing 82 84
MissingNullCheckOnSoqlVariable 20 20
UnimplementedType 2 2
  • 215 real violations are identical, field for field.
  • 2 are only in dev and 16 only in the PR. Every one comes from one of 7 entry points that hit the 30 s timeout in one run and finished in the other. For example, EP_ManageEPTemplate_CTRL.addTask timed out on dev, so its 10 violations are missing there; BGE_BatchGiftEntryTab_CTRL.getTableModel timed out on the PR, so its 2 violations are missing there.

Rerun of those 7 entry points with a 10-minute timeout (nothing can time out)

dev PR
Real violations 18 18, identical
Engine warnings 5 5
Wall time 110.9 s 93.8 s

The only remaining difference is one StackOverflowError engine warning. It is at the same location, rule and message on both engines; only the top frames of the stack trace differ, because the overflow happens at a slightly different depth.

Conclusion: on NPSP the two Gremlin versions find the same violations. The full-run differences come entirely from entry points that sit close to the 30 s timeout.

Run-to-run noise check (dev run twice)

dev run 1 dev run 2 PR
Wall time 649.5 s 637.2 s 649.3 s
CPU time 3,336 s 3,576 s 3,485 s
Peak memory 17.3 GB 17.4 GB 17.5 GB
Real violations 217 222 231
Timeouts / engine warnings 123 121 122
  • The unchanged dev build differs from itself: 5 extra violations in run 2 (4 from HH_Container_LCTRL.addContactAddresses, one of the same borderline entry points, and 1 from RD2_ElevateInformation_CTRL.getAppConfigurationData), and 8 different timeouts.
  • The PR's CPU time sits between the two dev runs, so the +4% seen in the first comparison is ordinary run-to-run variation. Wall time and memory are effectively the same.

Overall: no functional or performance regression was observed on NPSP.

Reproduce: the runner (run_sfge.py) and comparison (compare.py) scripts are available on request. They build the same sfgeInput.json the wrapper writes and diff every field of every result.


Not covered / follow-ups

  • Sonatype firewall re-scan of a build from this branch still needs doing. That is the scanner that matters to customers; this PR was checked with osv-scanner only.
  • npm audit still flags monorepo dev tooling (eslint, jest and similar). Nothing in this package's runtime npm dependencies (engine-api, semver) is flagged.
  • Gremlin 3.8.x: investigate the 7 failing traversal tests before moving.
  • Process: add gradle/libs.versions.toml and sfge/build.gradle.kts to the monthly dependency-update checklist; consider an osv-scanner CI gate on dist/java-lib.
  • Stub removal: remove the JsonProcessingException stand-in and the gremlin-shaded exclusion once a Gremlin release bundles a patched Jackson (≥ 2.18.11).

🤖 Generated with Claude Code

Removes all known-vulnerable libraries from the SFGE Java runtime classpath
(osv-scanner on dist/java-lib: 84 advisories across 24 artifacts -> 0; 97 JARs -> 35).

- Remove unused gremlin-driver (and the netty-all constraint). It was the sole
  source of Netty and AWS SDK JARs.
- Move Gremlin from 4.0.0-beta.1 to stable 3.7.7. Every 4.0 beta bundles Jackson
  2.15.2 inside gremlin-shaded. (3.8.2 changes traversal behavior and breaks tests.)
- Exclude gremlin-shaded (only used for GraphSON/Gryo/GraphML IO, which SFGE does
  not use; its bundled Jackson cannot be patched via constraints). Add a minimal
  JsonProcessingException stand-in because TinkerGraph references the type.
- Exclude commons-collections 3.x (no patched release; SFGE uses collections4).
- Upgrade log4j to 2.25.5; force patched commons-configuration2, commons-beanutils
  and snakeyaml; declare commons-lang3 and commons-text explicitly.
- Add verifyNoBannedDependencies build check and NoVulnerableBundledLibrariesTest
  to prevent regressions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@git2gus

git2gus Bot commented Oct 27, 2026

Copy link
Copy Markdown

Git2Gus App is installed but the .git2gus/config.json doesn't have right values. You should add the required configuration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@nikhil-mittal-165

Copy link
Copy Markdown
Contributor Author

Self-review using the refreshed Code Analyzer regression-review skill.

Finding

[P2] Make the copied Java dependency directory converge on the runtime classpath

copyDependencies copies the new runtime classpath into dist/java-lib but does not remove JARs left by an earlier build. The new verifyNoBannedDependencies task inspects Gradle resolution, not the files that will be packed. An incremental build after dev can therefore retain the removed Netty, AWS, Gremlin, shaded Jackson, or commons-collections JARs and still pass the new guard. Make the output syncing/deletion explicit, or validate the final directory/package rather than only runtimeClasspath.

tasks.register<Copy>("copyDependencies") {
from(configurations.runtimeClasspath) // This includes all runtime dependencies
into(javaLibDir) // Target directory for dependencies
}
tasks.build {
dependsOn("copyDependencies") // Ensure dependencies are copied during the build process

Manual validation before merge

Required:

  • Build once from dev, switch to this branch without cleaning, rebuild, pack, and scan the tarball. The packed dist/java-lib must contain the same dependency set as a clean build and none of the banned artifacts.
  • Run the published-package form of SFGE on a representative Apex workspace, including every SFGE rule, with Java 11 and the supported current LTS. Compare violations, warnings/timeouts, exit behavior, and logs with dev.
  • Exercise graph creation/traversal paths that load TinkerGraph beyond the single constructor smoke case, so the Jackson stand-in cannot hide another lazy linkage failure.

Recommended:

  • Re-scan the final tarball with the customer-relevant Sonatype firewall as noted in the PR, in addition to OSV.
  • Repeat the large-workspace comparison once with a timeout high enough to remove timeout noise.

Evidence checked: dependency graph/build changes, package contents path, new guard/tests, CI matrix reported by GitHub, approval, and the NPSP comparison supplied in the PR description. I did not independently reproduce the long NPSP run.

…cies

copyDependencies only added files to dist/java-lib, so an incremental build
after switching branches could keep jars that were removed from the runtime
classpath (Netty, AWS, shaded Jackson, etc.) and still pass the banned
dependency check. Delete everything except sfge-*.jar first so the packed
directory always matches a clean build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@nikhil-mittal-165

Copy link
Copy Markdown
Contributor Author

Addressed the P2 finding in 41605b9: copyDependencies now removes everything in dist/java-lib except sfge-*.jar before copying, so an incremental build packs the same dependency set as a clean one. Verified locally by planting a stale jar and rebuilding. The manual validation items (Java 11/LTS runs, TinkerGraph paths, tarball scan) are still outstanding.

🤖 Generated with Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants