Skip to content

fix: [BUG][code-analyzer] 5.16.0 silently drops custom PMD rulesets that point at a sibling file by relative path - #516

Draft
nikhil-mittal-165 wants to merge 2 commits into
devfrom
fix/issue-2087-pmd-custom-ruleset-cwd-refs
Draft

nikhil-mittal-165 wants to merge 2 commits into
devfrom
fix/issue-2087-pmd-custom-ruleset-cwd-refs

Conversation

@nikhil-mittal-165

Copy link
Copy Markdown
Contributor

Summary

Addresses code-analyzer issue 2087: 5.16.0 silently drops custom PMD rulesets that point at a sibling file by relative path.

Root Cause

Regression from code-analyzer-core PR 503 (CWE-427 hardening): JavaCommandExecutor.exec (code-analyzer-engine-api/src/utils/java-utils.ts) now spawns java with cwd=__dirname (engine install dir) instead of the inherited scanned-repo cwd. The PR assumed pinning cwd was behavior-preserving because all java args are absolute, but PMD resolves rule/ruleset refs written inside a user's custom ruleset (e.g. <rule ref="scanner/full.xml/MyRule"/>) first as a path relative to the process cwd, then as a classpath resource. Previously the cwd lookup found the file; now it fails, the PMD ruleset load errors, and the engine is dropped with a single file-less Critical violation and exit code 0. The config is valid per docs, the cwd-relative ref was undocumented but long-working behavior, and the PMD version (7.26.0) is unchanged between 5.15 and 5.16.

The separate concern that an engine load failure exits 0 is not addressed by this fix.

Fix

Keep the cwd pin (security) and add the scan folder (process.cwd()) to the PMD java classpath so repo-relative rule refs in custom rulesets resolve as classpath resources. Includes a test and a doc note in the custom_rulesets description.

Testing

  • Unit tests were added/updated, but they could NOT be run in the authoring environment (no working Java runtime at that time). Please rely on CI.
  • Lint status was not verified.

Functional Testing Evidence

Synthetic repro (not a real-world project): scanner/full.xml defines MyRule, scanner/subset.xml has <rule ref="scanner/full.xml/MyRule"/>, code-analyzer.yml lists both under engines.pmd.custom_rulesets. Run via node code-analyzer/bin/run.js (sf CLI not installed), Java 17 via Homebrew.

  • Test 1 (this branch): code-analyzer rules --rule-selector pmd:MyRule from repo root -> 1 MyRule pmd 3 (Moderate) Recommended, Full, Subset, Apex, Custom; Found 1 rule(s) from 1 engine(s) - PASS
  • Test 2 (baseline, parent commit e2ac4f9, same repro): Failed to get rules from engine with name pmd ... Cannot load ruleset scanner/full.xml/MyRule: Cannot resolve - bug reproduced, PASS
  • Test 3 (this branch, end-to-end): code-analyzer run --rule-selector pmd:MyRule --workspace src -> pmd:MyRule A.cls:1:25 my rule hit - PASS. The only other violation was an unrelated flow-engine Critical because Python is not installed.

Overall functional status: PASS

Files Changed

  • packages/code-analyzer-pmd-engine/src/pmd-wrapper.ts
  • packages/code-analyzer-pmd-engine/src/messages.ts
  • packages/code-analyzer-pmd-engine/src/config.ts
  • packages/code-analyzer-pmd-engine/test/pmd-engine.test.ts

🤖 Generated with Claude Code

…ustom rulesets resolve

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

git2gus Bot commented Nov 2, 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.

@nikhil-mittal-165

Copy link
Copy Markdown
Contributor Author

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

Finding

[P1] Do not restore the untrusted repository as a general Java classpath root

PR #503 pinned the Java cwd specifically to address CWE-427. Adding process.cwd() to the classpath restores the same trust boundary through Java resource/class lookup: a scanned repository can now provide any otherwise-unresolved class or resource, not only the intended sibling ruleset XML. Putting it last prevents replacement of classes already present, but it does not prevent loading a malicious class/resource that a dependency or custom rule resolves later. Preserve relative ruleset compatibility with a narrowly scoped resolver/staging directory or another design that exposes only intended ruleset resources, not the whole repository.

private createJavaClassPaths(): string[] {
return [
path.join(PMD_WRAPPER_LIB_FOLDER, '*'),
...this.userProvidedJavaClasspathEntries.map(toJavaClasspathEntry),
// The java process is pinned to the engine's install folder, but PMD resolves refs inside custom rulesets
// (like <rule ref="path/in/repo.xml/Rule"/>) relative to the cwd or the classpath. So we add the user's
// cwd (last, so it has the lowest precedence) so that repo-relative refs continue to resolve.
process.cwd()
];

Manual validation before merge

Required:

  • Reproduce issue #2087 with scanner/subset.xml referencing scanner/full.xml/MyRule, then run rule discovery and execution through the packed CLI.
  • In the same fixture, place class/resource names in the repository that collide with an otherwise-unresolved PMD/plugin dependency path. Verify repository content cannot be loaded as arbitrary JVM code/resources. This must pass together with the compatibility case.
  • Run from a cwd different from both config_root and the workspace. Verify the documented base is intentional and deterministic rather than whichever shell directory launched Code Analyzer.
  • Cover POSIX and Windows, paths with spaces, disk rulesets, JAR rulesets, and the AST-dump path because the helper changes all three PMD invocations.
  • Verify a ruleset-load failure still produces the established visible critical result/exit behavior; the separate exit-code issue remains explicitly out of scope.

Evidence checked: linked issue #2087, regression lineage from core PR #503, all changed invocations/callers, the new unit fixture, and green Linux/macOS/Windows CI. The functional CLI reproduction in the PR description is author-supplied evidence.

…a classpath

Adding process.cwd() to the PMD classpath re-exposed the scanned repository
to the JVM as a source of classes and resources (CWE-427, see #503). Instead,
copy only the xml ruleset files that custom rulesets reference (transitively,
and never from outside the base folder, including via symlinks) into a
staging folder in the working folder and put just that folder last on the
classpath. Repo-relative rule refs keep resolving (#2087).

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

Copy link
Copy Markdown
Contributor Author

Addressed the P1 finding in 7fd77a8: process.cwd() is no longer on the Java classpath. Instead, only the xml rulesets that custom rulesets reference (transitively, never from outside the base folder, including via symlinks) are copied into a staging folder in the working folder, and just that folder is added last to the classpath. Added unit tests for the staging helper. The existing #2087 regression test and the full PMD suite pass. Still open: refs are resolved against process.cwd() (existing behavior) rather than config_root, and there is no direct test that colliding classes/resources in a repo are not loaded.

🤖 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.

1 participant