Skip to content

[Backport 2.19] Validate base_path in FsRepository to prevent path.repo containment bypass - #22380

Closed
opensearch-ci-bot wants to merge 1 commit into
opensearch-project:2.19from
opensearch-ci-bot:backport/backport-22328-to-2.19
Closed

[Backport 2.19] Validate base_path in FsRepository to prevent path.repo containment bypass#22380
opensearch-ci-bot wants to merge 1 commit into
opensearch-project:2.19from
opensearch-ci-bot:backport/backport-22328-to-2.19

Conversation

@opensearch-ci-bot

Copy link
Copy Markdown
Contributor

Backport 629a185 from #22328.

…ypass (opensearch-project#22328)

The fs repository base_path setting was read verbatim from REST input with
no validation. An absolute base_path causes Path.resolve to discard the
path.repo-validated location, redirecting all blob-store operations outside
the repository (CWE-22) and enabling arbitrary filesystem deletion via the
snapshot _cleanup API.

Add two layers of defense:
 - BASE_PATH_SETTING validator rejects absolute and upward-escaping (..) values
   after normalization (benign interior '..' that cancels out is allowed).
 - validateBasePathWithinRepo() resolves base_path against the location and
   verifies the result stays within a configured path.repo directory.

Signed-off-by: Aditya Khera <kheraadi@amazon.com>
Co-authored-by: Aditya Khera <kheraadi@amazon.com>
(cherry picked from commit 629a185)
Signed-off-by: opensearch-ci-bot <opensearch-infra@amazon.com>
@opensearch-ci-bot
opensearch-ci-bot requested a review from a team as a code owner July 2, 2026 17:54
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Overly Broad '..' Rejection

The validator rejects any base_path containing the substring .., not just .. path segments. Legitimate directory names that happen to contain two consecutive dots (e.g., my..dir, version..1, foo..bar) will be rejected. Consider splitting by path separators and checking segments equal to .., rather than a substring match, to avoid false positives on valid relative paths.

if (Strings.hasLength(value)
    && (value.startsWith("/") || value.startsWith("\\") || value.matches("^[A-Za-z]:.*") || value.contains(".."))) {
    throw new IllegalArgumentException(
        "[base_path] must be a relative path that does not contain '..' segments; got [" + value + "]"
    );
}
Windows Path Separator Handling

The setting validator only checks / and \\ prefixes and drive-letter absolute paths, but does not reject mixed separators inside the value (e.g., foo\\..\\bar). On Windows, Path.resolve treats \\ as a separator and the .. substring check would catch this particular case, but arbitrary backslash-separated segments are not otherwise normalized in the string validator. The isUnderRepo containment check is the backstop, but it's worth confirming the intended cross-platform semantics.

if (Strings.hasLength(value)
    && (value.startsWith("/") || value.startsWith("\\") || value.matches("^[A-Za-z]:.*") || value.contains(".."))) {
    throw new IllegalArgumentException(
        "[base_path] must be a relative path that does not contain '..' segments; got [" + value + "]"
    );
}

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid false positives on '..' substring

The value.contains("..") check will incorrectly reject legitimate base paths
containing .. as part of a filename (e.g., my..folder or file..txt). To only reject
actual parent-directory traversal segments, split the path on separators and check
whether any segment equals ...

server/src/main/java/org/opensearch/repositories/fs/FsRepository.java [109-114]

 if (Strings.hasLength(value)
-    && (value.startsWith("/") || value.startsWith("\\") || value.matches("^[A-Za-z]:.*") || value.contains(".."))) {
+    && (value.startsWith("/") || value.startsWith("\\") || value.matches("^[A-Za-z]:.*"))) {
     throw new IllegalArgumentException(
-        "[base_path] must be a relative path that does not contain '..' segments; got [" + value + "]"
+        "[base_path] must be a relative path; got [" + value + "]"
     );
 }
+if (Strings.hasLength(value)) {
+    for (String segment : value.split("[/\\\\]")) {
+        if ("..".equals(segment)) {
+            throw new IllegalArgumentException(
+                "[base_path] must not contain '..' segments; got [" + value + "]"
+            );
+        }
+    }
+}
Suggestion importance[1-10]: 6

__

Why: Valid point: value.contains("..") will reject legitimate filenames like my..folder. Splitting on separators and comparing segments is more accurate, though such filenames are uncommon in practice.

Low
Security
Normalize both sides before containment check

Path.startsWith compares path components literally and does not follow symbolic
links, so a symlink under path.repo pointing outside could still bypass containment.
Consider using toRealPath() (or comparing normalized absolute paths of both sides)
to make the check robust against symlink-based escapes.

server/src/main/java/org/opensearch/repositories/fs/FsRepository.java [175-181]

-final Path resolved = locationFile.resolve(basePath).normalize();
+final Path resolved = locationFile.resolve(basePath).toAbsolutePath().normalize();
 for (Path repoPath : environment.repoFiles()) {
-    if (resolved.startsWith(repoPath)) {
+    if (resolved.startsWith(repoPath.toAbsolutePath().normalize())) {
         return true;
     }
 }
 return false;
Suggestion importance[1-10]: 6

__

Why: Reasonable defense-in-depth improvement to normalize both sides to absolute paths before comparison, though repoFiles() typically already returns absolute normalized paths. The toRealPath() suggestion for symlinks is a valid concern but not fully addressed in the improved code.

Low

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for 83dc30e: TIMEOUT

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@cwperks

cwperks commented Jul 2, 2026

Copy link
Copy Markdown
Member

Superseded by #22381

@cwperks cwperks closed this Jul 2, 2026
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.

3 participants