Skip to content

fix: validate post-transformation path shapes and assert containment at resolve sinks - #2079

Closed
gnodet wants to merge 2 commits into
masterfrom
security/path-traversal-coordinate-validation
Closed

fix: validate post-transformation path shapes and assert containment at resolve sinks#2079
gnodet wants to merge 2 commits into
masterfrom
security/path-traversal-coordinate-validation

Conversation

@gnodet

@gnodet gnodet commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes 4 findings from the maven-resolver security audit (scan-maven-resolver-20260811):

Finding Severity Description
f001 HIGH Leading-dot and colon coordinates bypass validatePathComponent: absolute-path writes outside local repo and cross-host fetches
f003 MEDIUM FileTransporter traversal guard misses absolute paths and Windows separators
f010 MEDIUM Repository id ".." escapes origin-aware trusted-checksums and split-LRM directories
f017 MEDIUM Default file-lock name mapper builds lock paths from raw wire-supplied coordinates

Root cause: Strings from remote repositories (artifact coordinates, repository ids) are validated in their pre-transformation shape and then passed to Path.resolve(), which returns absolute arguments unchanged. No sink asserts containment.

Fix: Validate post-replacement path shape, reject .././: segments, and assert normalize().startsWith(base) containment at every resolve sink.

Test plan

  • Existing tests pass
  • New test cases for leading-dot groupId, colon groupId, ".." repository id
  • FileTransporter containment check tested
  • Lock path containment tested

Generated with Claude Code

@gnodet
gnodet force-pushed the security/path-traversal-coordinate-validation branch from 08b6710 to 4596344 Compare August 30, 2026 19:34
… path traversal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet
gnodet force-pushed the security/path-traversal-coordinate-validation branch from 4596344 to 702cfc9 Compare August 30, 2026 19:59
@gnodet
gnodet marked this pull request as ready for review August 31, 2026 05:20
@gnodet
gnodet requested a review from cstamas August 31, 2026 05:20
@gnodet gnodet added bug Something isn't working priority:critical Crashes, loss of data, severe memory leak labels Aug 31, 2026
@gnodet gnodet added this to the 2.0.23 milestone Aug 31, 2026
}
}
Path file = basePath.resolve(relative).normalize();
if (!file.startsWith(basePath.normalize())) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is overly aggressive (to normalize unchanging basepath for each file). IMHO it is already normalized in ctor, should be just used, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — moved normalize() into the ctor (this.basePath = requireNonNull(basePath).normalize()). The containment check now uses basePath directly. Also fixed @since 2.0.222.0.23 across the PR.

*/
private static String resolveContained(Path basedir, String name) {
Path resolved = basedir.resolve(name).toAbsolutePath();
if (!resolved.normalize().startsWith(basedir.toAbsolutePath().normalize())) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, doing this for each lock... basedir should be treated like this in ctor?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix applied here: basePath is now normalized at construction time (path.toAbsolutePath().normalize()), and the session-resolved fallback path is also normalized. resolveContained() now just does resolve().normalize() + startsWith(basedir) without redundant per-call normalization.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thorough, well-layered security fix addressing 4 audit findings (f001, f003, f010, f017) about path traversal and containment. Defense-in-depth is correctly applied at input validation, sanitization, post-composition, and resolve-sink layers.

@since tag corrections

Three new methods/constants have @since 2.0.22 but should be @since 2.0.23 (2.0.22 is already released):

  • PathUtils.validateDotSeparatedPathComponent — public API, will produce incorrect availability docs
  • GAVNameMapper.fieldToSegment — protected, part of API surface
  • BasedirNameMapper.resolveContained — private, lower impact

Minor observation (not blocking)

SummaryFileTrustedChecksumsSource.summaryFile() splices the repository key into a filename via string concatenation then passes to Path.resolve(). The PR adds validatePathComponent in SparseDirectoryTrustedChecksumsSource for the same pattern but not here. Risk is low (default key function sanitizes via stringToPathSegment()), but a custom RepositoryKeyFunction returning / could escape. Consider adding the same validation for consistency.

Positive observations

  • validateDotSeparatedPathComponent correctly identifies that groupId dot-to-slash expansion can produce absolute paths (.home.ci/home/ci) and authority URIs (..evilhost//evilhost)
  • Test coverage is comprehensive across all modified sinks

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of gnodet

Address review feedback from cstamas:
- FileTransporter: normalize basePath once in ctor instead of per-file
- BasedirNameMapper: normalize basedir in ctor and session-resolved path
- Fix @SInCE 2.0.22 → 2.0.23 (2.0.22 is already released)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gnodet

gnodet commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Closing — all path-traversal fixes from this PR have been superseded by cstamas's independent implementations:

All findings (f001, f003, f010, f017) from the security audit are now addressed in master.

@gnodet gnodet closed this Aug 31, 2026
@cstamas
cstamas deleted the security/path-traversal-coordinate-validation branch August 31, 2026 11:14
@github-actions github-actions Bot removed this from the 2.0.23 milestone Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority:critical Crashes, loss of data, severe memory leak

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants