NEW @W-23949583@ [Bug Bounty / H1] Salesforce Graph Engine initialization on Windows - #503
Conversation
… java.exe shadowing (CWE-427) SFGE's RuntimeJavaVersionIdentifier spawned 'java -version' with no cwd option. On Windows, the bare-'java' auto-detect fallback let child_process resolve an attacker-committed java.exe from the inherited scanned-repo cwd ahead of the trusted PATH entry, executing attacker code before rule listing began (CWE-427 search-path element). Pin the spawn's cwd to the engine's installed module directory (__dirname), which is installer-controlled and never the scanned repo. Absolute-path and PATH-resolved java commands are unaffected, preserving behavior for legitimate java_command configs. Mirrors the Flow engine's PythonCommandExecutor cwd pin (commit a69a132). Adds regression tests (fake-java probe reports its spawn cwd; planted repo-local java sentinel) proving the child runs from the trusted dir and a repo-local java is never invoked.
…sub-engines) (CWE-427) PMD's RuntimeJavaVersionIdentifier is an independent copy of the SFGE identifier (shared by both the PMD and CPD sub-engines) and had the same CWE-427 flaw: 'java -version' was spawned with no cwd option, so on Windows the bare-'java' auto-detect fallback could resolve an attacker-committed java.exe from the inherited scanned-repo cwd ahead of the trusted PATH entry. Pin the spawn's cwd to the engine's installed module directory (__dirname). A single pin covers both sub-engines. Absolute-path and PATH-resolved java commands are unaffected, preserving behavior for legitimate java_command configs. Mirror of the SFGE fix. Adds a dedicated JavaVersionIdentifier.test.ts with regression tests (fake-java probe reports its spawn cwd; planted repo-local java sentinel) proving the child runs from the trusted dir and a repo-local java is never invoked.
|
Git2Gus App is installed but the |
sf code-analyzer rules executes a repository-local java.exe during Salesforce Graph Engine initialization on WindowsThe new CWE-427 regression tests spawned .sh fake-java scripts as the java command. Windows cannot directly execute .sh files, so cp.spawn threw "spawn UNKNOWN" and the windows-latest CI job failed - the fix's own tests broke on the very OS the fix targets. identifyJavaVersion spawns the java command directly (no interpreter wrapper, unlike the Flow engine's python), so a fake-java probe can't be a portable script. Replace the script-based tests with a jest spy on cp.spawn asserting the spawn cwd is pinned to the trusted module directory (__dirname) - the exact mechanism that closes the vector - and it runs identically on every platform. Drop the now-unused .sh scripts and condense the verbose source/test comments.
Review — CWE-427 fix looks incomplete 🔴Thanks for tackling this. The 🔴 CRITICAL — the real engine spawn is unpinned
const javaProcess = spawn(this.javaCommand, allJavaArgs); // no cwdReachability (read at head
Suggested fix: pin 🟠 HIGH — the manual "planted-java" evidence doesn't exercise the vulnerable pathTwo independent reasons it can't confirm the fix:
Suggestion: re-run end-to-end on 🟡 MEDIUM — tests assert an argument, not the security propertyThe new tests spy on 🟡 MEDIUM — fix duplicated instead of centralized
🟡 MEDIUM —
|
…(CWE-427) The version-probe fix in this PR left the actual rule-listing/execution spawn (JavaCommandExecutor.exec) inheriting the scanned-repo cwd, so on Windows a repo-local java.exe still shadowed the real java for PMD/CPD/SFGE rule work. Pin cwd to __dirname; every java arg (classpaths + I/O files) is absolute, so this is behavior-preserving. Mirrors the Flow-engine cwd-shadowing fix in #495 (W-23791879). Adds a spawn-cwd regression test plus a skipped windows-latest integration stub. Ref W-23949583.
The new pmd JavaVersionIdentifier test file only exercised the CWE-427 cwd pin; the exported version-parsing helper had zero coverage (unlike the sfge copy, whose suite already tests it). Add the same parse cases so pmd's copy is covered too.
|
Thanks @namrata — this made the fix materially more complete. Addressing each point: 1. The fix had to cover the executor, not just the version probe. You're right. This is behavior-preserving: every arg we hand Java (classpaths, I/O files) is 2. Tests.
|
namrata111f
left a comment
There was a problem hiding this comment.
Re-review @ 8add761 — all findings addressed ✅
Thanks for the fast, thorough turnaround. Verified each item at source (not just commit messages):
- 🔴 CRITICAL (engine spawn) — fixed.
JavaCommandExecutornow spawns with{cwd: __dirname}(java-utils.ts), so the shared rule-listing/execution path for PMD, CPD, and SFGE is pinned — not just the version probe. Comment correctly notes all java args are absolute, so it's behavior-preserving. - 🟠 HIGH (test evidence) — fixed. The macOS anecdote is replaced with a real Windows-gated integration test: plants a genuine
hostname.exeasjava.exein an attacker-controlled dir,chdirs there, and asserts the real PATHjavabanner reached stdout. Nicely handles the Node ≥18.20.2.bat/.cmdEINVALtrap by requiring a real.exe, skips on non-Windows, and runs on thewindows-latestCI leg. I traced it adversarially: remove the{cwd}pin and the planted exe runs, failing the assertion — so it's a genuine regression guard. - 🟡 MEDIUM (argument-only assertions) — fixed. Added
expect(TRUSTED_DIR).not.toEqual(process.cwd())plus the end-to-end test above. - 🟡 MEDIUM (
_extractJavaVersionFromcoverage) — fixed. pmd suite now covers v8/11/12/14/17,_JAVA_OPTIONSnoise, and garbage→null.
Both JavaVersionIdentifier probe pins remain intact. The only open item is the optional consolidation of the duplicated RuntimeJavaVersionIdentifier into the shared java-utils.ts — fine as a follow-up, not a blocker.
The CWE-427 fix is now complete and correctly tested. Approving. 🟢
Cross-platform safety evidence — behavior-preserving everywhere, fix verified on real WindowsPosting a consolidated safety analysis for this change. The only runtime behavior introduced by this PR is pinning the working directory of the Java child processes to the engine's own install dir ( 1. Why the change cannot alter behavior on any OS (root cause)Two independent facts make the cwd pin a no-op for legitimate scans on every platform:
The remaining Net effect on the two possible cases, on every OS:
For every legitimate repo the resolved 2. Behavior preserved — proven by CI on all three OSesThe engine integration suites assert exact expected violations against fixtures. If pinning the cwd changed engine output in any way, those assertions would fail. They pass unchanged on (The multi-second/-minute runtimes confirm these are genuine Java scans, not mocks; no CI/env skip guards are present on them.) 3. macOS — real-world scan on a large production repo (NPSP)I ran the same NPSP workspace through all engines twice, from two different working directories — from
The entire delta between the two runs ( 4. Windows — the fix works and closes the vector, on a real Windows runner
(a) No regression on Windows — the affected package suites built + linted + passed with a real JDK: The two version-identifier files also report 100% coverage on Windows. (b) The security property itself, on real Windows — the CWE-427 regression test is gated Full matrix: 5. PrecedentThis mirrors the already-merged #495 ("Flow engine cwd shadowing fix", Summary
|
Summary
Pins trusted working directory on java -version spawns to block repository-local java.exe shadowing attacks (CWE-427) in both SFGE and PMD engines during Salesforce Graph Engine initialization on Windows.
GUS Ticket W-23949583 — [Bug Bounty / H1]
sf code-analyzer rulesexecutes a repository-localjava.exeduring Salesforce Graph Engine initialization on WindowsChanges
Test Evidence
Env repair: Node.js was missing (broken Homebrew Cellar); restored via brew reinstall node (v26.7.0) to enable npm/jest/sf. Setup: setup_local_dev.py built core + linked all 9 packages + CLI to sf successfully. Unit tests: sfge java-version-identifier.test.ts 8/8 pass (incl. existing _extractJavaVersionFrom), pmd JavaVersionIdentifier.test.ts 2/2 pass — both new CWE-427 regression suites green. Full sfge suite 32/101 (69 fail) and pmd suite 58/139 (81 fail) all Category B: this machine's 'java' is a broken Azul wrapper emitting a sudo/password warning with no parseable version line, so config.ts attemptToAutoDetectJavaCommand (unmodified) throws CouldNotLocateJava and PMD/SFGE Java procs never start. Integration baseline: dreamhouse scan completed, 616 violations/165 files, valid JSON, no crash (retire-js/regex/apexguru/eslint/flow executed; pmd/sfge skipped on broken-Java env; apexguru warned 503 down-for-maintenance). Feature-specific: planted malicious java+java.exe at scanned-repo root, cleared JAVA_HOME/JRE_HOME/JDK_HOME to force bare-java fallback, ran linked CLI from inside workspace — planted executable NEVER invoked (no pwned sentinel); cwd:__dirname pin confirmed present in built dist/ of both engines. No Category A failures; no fix commits needed; branch unchanged.
Fix attempts: 0
Integration: PASS - dreamhouse baseline 616 violations no crash; planted-java attack test proves repo-local java never executed via real CLI
Known external failures (not blocking): sfge/pmd engine tests fail: broken Azul java wrapper prints no parseable version line (env issue, not our code); apexguru 503 down-for-maintenance (external service)
Test Status: PASS