Skip to content

Conversation

@gkganesh12
Copy link

@gkganesh12 gkganesh12 commented Dec 21, 2025

Description

  • Fixed critical path traversal vulnerability in getMapBaseUrlToFolderResolvers function that allowed accessing files outside the base directory
  • Added validatePathWithinBase utility function with input validation for non-empty string parameters
  • Enhanced security by validating resolved paths stay within base directory using path.resolve() and path.normalize()
  • Added JSDoc documentation clarifying symlink behavior (does not resolve symlinks, as expected for security validation)
  • Improved error messages to be consistent and clear: "Path traversal detected" with context but without leaking excessive filesystem details
  • Simplified test case by removing conditional logic for better readability
  • All path traversal attempts are now blocked with clear error messages
  • Fully backward compatible - no breaking changes, only adds validation

Related issue(s)

Resolves #1805

Summary by CodeRabbit

  • Bug Fixes

    • Improved file-access security by blocking path traversal attempts and providing clearer error messages.
  • New Features

    • Added a reusable path-validation utility to enforce safe file reads within a base directory.
  • Tests

    • Added comprehensive integration tests covering traversal attacks, absolute-path cases, root/base-edge cases, and cross-platform behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add validatePathWithinBase utility to prevent path traversal attacks
- Update getMapBaseUrlToFolderResolvers to validate paths before file operations
- Add comprehensive tests for path traversal prevention (7 test cases)
- Improve error messages with context and security violation details
- Handle both Unix and Windows path separators
- Maintain backward compatibility for legitimate use cases

Security: Fixes critical path traversal vulnerability that allowed accessing
files outside the specified base directory through malicious URLs.

Tests: Added test suite covering:
- Normal file access within base directory
- Path traversal attempts with ../ and ..\
- Absolute paths outside base directory
- Multiple traversal sequences
- Clear error message validation
@changeset-bot
Copy link

changeset-bot bot commented Dec 21, 2025

⚠️ No Changeset found

Latest commit: bd38ce5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@asyncapi-bot
Copy link
Contributor

What reviewer looks at during PR review

The following are ideal points maintainers look for during review. Reviewing these points yourself beforehand can help streamline the review process and reduce time to merge.

  1. PR Title: Use a concise title that follows our Conventional Commits guidelines and clearly summarizes the change using imperative mood (it means spoken or written as if giving a command or instruction, like "add new helper for listing operations")

    Note - In Generator, prepend feat: or fix: in PR title only when PATCH/MINOR release must be triggered.

  2. PR Description: Clearly explain the issue being solved, summarize the changes made, and mention the related issue.

    Note - In Generator, we use Maintainers Work board to track progress. Ensure the PR Description includes Resolves #<issue-number> or Fixes #<issue-number> this will automatically close the linked issue when the PR is merged and helps automate the maintainers workflow.

  3. Documentation: Update the relevant Generator documentation to accurately reflect the changes introduced in the PR, ensuring users and contributors have up-to-date guidance.

  4. Comments and JSDoc: Write clear and consistent JSDoc comments for functions, including parameter types, return values, and error conditions, so others can easily understand and use the code.

  5. DRY Code: Ensure the code follows the Don't Repeat Yourself principle. Look out for duplicate logic that can be reused.

  6. Test Coverage: Ensure the new code is well-tested with meaningful test cases that pass consistently and cover all relevant edge cases.

  7. Commit History: Contributors should avoid force-pushing as much as possible. It makes it harder to track incremental changes and review the latest updates.

  8. Template Design Principles Alignment: While reviewing template-related changes in the packages/ directory, ensure they align with the Assumptions and Principles. If any principle feels outdated or no longer applicable, start a discussion these principles are meant to evolve with the project.

  9. Reduce Scope When Needed: If an issue or PR feels too large or complex, consider splitting it and creating follow-up issues. Smaller, focused PRs are easier to review and merge.

  10. Bot Comments: As reviewers, check that contributors have appropriately addressed comments or suggestions made by automated bots. If there are bot comments the reviewer disagrees with, react to them or mark them as resolved, so the review history remains clear and accurate.

@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

📝 Walkthrough

Walkthrough

Adds a path-validation utility and applies it to parser read operations to block directory traversal, decodes URI paths, improves error messages, and adds integration tests covering traversal attempts, absolute/outside paths, Windows cases, and base/root edge cases.

Changes

Cohort / File(s) Summary
Path validation utility
apps/generator/lib/utils.js
New exported validatePathWithinBase(filePath, baseDir, operation) that validates inputs, resolves/normalizes paths (uses fs.realpathSync when available), accounts for platform case rules, and throws on path traversal outside the base; returns normalized absolute path when valid.
Parser read flow
apps/generator/lib/parser.js
Replaces direct string-based local path with decoded URI handling then calls validatePathWithinBase to obtain the validated localpath used for fs.readFile; rejects invalid URL encodings and includes validated path and underlying errors in rejection messages.
Integration tests
apps/generator/test/parser.test.js
Adds "Path Traversal Prevention" test suite using temporary directories/files; asserts permitted reads inside base and rejects ../, ..\, absolute/outside paths, repeated traversal sequences, Windows case-insensitivity scenarios, and base/root edge cases; performs per-test setup/teardown.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'fix: path traversal vulnerability in URL-to-folder mapping' directly and clearly describes the main change: addressing a critical path traversal security vulnerability in the getMapBaseUrlToFolderResolvers function.
Linked Issues check ✅ Passed The pull request fully addresses all coding requirements from issue #1805: implements proper path validation using path.resolve() and path.normalize(), validates paths stay within base directory, adds comprehensive path traversal tests, and maintains backward compatibility with clear error messages.
Out of Scope Changes check ✅ Passed All changes in parser.js, utils.js, and parser.test.js are directly related to remediating the path traversal vulnerability described in issue #1805; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f944b3a and bd38ce5.

📒 Files selected for processing (2)
  • apps/generator/lib/parser.js
  • apps/generator/test/parser.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/generator/lib/parser.js
  • apps/generator/test/parser.test.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/generator/lib/utils.js (2)

209-233: Add input validation for parameters.

The function should validate that filePath and baseDir are non-empty strings before proceeding. Without validation, passing empty strings, null, or undefined could lead to confusing behavior or errors.

🔎 Proposed input validation
 utils.validatePathWithinBase = (filePath, baseDir, operation = 'access') => {
+  if (!filePath || typeof filePath !== 'string') {
+    throw new Error('filePath must be a non-empty string');
+  }
+  if (!baseDir || typeof baseDir !== 'string') {
+    throw new Error('baseDir must be a non-empty string');
+  }
+  
   // Resolve and normalize both paths
   const resolvedBaseDir = path.resolve(baseDir);

200-208: Consider documenting symlink behavior.

The function validates paths but doesn't resolve symlinks. If a symlink inside the base directory points outside, it won't be detected. While this is acceptable (it's a different vulnerability class requiring prior write access), consider adding a note in the JSDoc for clarity.

📝 Suggested documentation enhancement
 /**
  * Validates that a file path stays within a base directory to prevent path traversal attacks.
+ * Note: This function does not resolve symbolic links. If a symlink inside the base directory
+ * points to a location outside it, this will not be detected.
  * 
  * @param {string} filePath - The file path to validate (can be relative or absolute)
apps/generator/test/parser.test.js (1)

298-330: Consider simplifying the test logic.

The conditional logic in this test (lines 320-329) makes it harder to understand and maintain. The test would be clearer with a straightforward malicious path construction.

🔎 Suggested simplification
-    it('should reject absolute paths outside base directory', async () => {
+    it('should reject paths resolving outside base directory', async () => {
       const generator = {
         mapBaseUrlToFolder: {
           url: 'https://schema.example.com/crm/',
           folder: testBaseDir
         }
       };
       const oldOptions = {};
       const newOptions = convertOldOptionsToNew(oldOptions, generator);
       const resolver = newOptions.__unstable.resolver.resolvers[0];
 
-      // Create a file outside the base directory
-      const outsideFile = path.join(tempDir, 'outside-file.json');
-      await writeFile(outsideFile, JSON.stringify({ secret: 'data' }), 'utf8');
-
-      // Attempt to access it using absolute path manipulation
+      // Attempt to access parent directory
       const maliciousUri = {
-        toString: () => `https://schema.example.com/crm/${path.relative(testBaseDir, outsideFile)}`,
-        valueOf: () => `https://schema.example.com/crm/${path.relative(testBaseDir, outsideFile)}`
+        toString: () => 'https://schema.example.com/crm/../../sensitive-file.json',
+        valueOf: () => 'https://schema.example.com/crm/../../sensitive-file.json'
       };
 
-      // If the relative path doesn't work, try direct absolute path
-      if (!maliciousUri.toString().includes('..')) {
-        // Try with a path that would resolve outside
-        const anotherMaliciousUri = {
-          toString: () => `https://schema.example.com/crm/../../${path.basename(outsideFile)}`,
-          valueOf: () => `https://schema.example.com/crm/../../${path.basename(outsideFile)}`
-        };
-        await expect(resolver.read(anotherMaliciousUri)).rejects.toThrow('Path traversal detected');
-      } else {
-        await expect(resolver.read(maliciousUri)).rejects.toThrow('Path traversal detected');
-      }
+      await expect(resolver.read(maliciousUri)).rejects.toThrow('Path traversal detected');
     });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9eae1d and bbcc87b.

📒 Files selected for processing (3)
  • apps/generator/lib/parser.js (2 hunks)
  • apps/generator/lib/utils.js (1 hunks)
  • apps/generator/test/parser.test.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/generator/test/parser.test.js (1)
apps/generator/lib/parser.js (6)
  • require (3-3)
  • require (4-4)
  • require (5-5)
  • fs (1-1)
  • path (2-2)
  • resolver (102-127)
apps/generator/lib/parser.js (1)
apps/generator/lib/utils.js (2)
  • path (3-3)
  • fs (1-1)
🔇 Additional comments (5)
apps/generator/lib/utils.js (1)

211-222: Excellent path traversal prevention logic!

The validation correctly:

  • Resolves and normalizes both paths to handle .. sequences and various path formats
  • Uses normalizedBase + path.sep to prevent false positives (e.g., /base-evil when base is /base)
  • Allows the base directory itself via equality check

This approach effectively prevents path traversal attacks.

apps/generator/lib/parser.js (2)

2-5: Good integration of path validation!

The imports are correct, and renaming path to uriPath properly avoids naming conflicts with the path module. The string replacement to derive localpath is straightforward and appropriate for this use case.

Also applies to: 107-108


110-124: Security validation correctly implemented!

The integration properly:

  • Validates the path before any filesystem access
  • Uses the validatedPath consistently for both file reading and error messages
  • Catches and propagates validation errors appropriately
  • Cannot be bypassed due to the try-catch structure

This effectively prevents path traversal attacks in the resolver.

apps/generator/test/parser.test.js (2)

208-256: Excellent test infrastructure and positive test case!

The test setup properly:

  • Creates an isolated temporary directory for each test
  • Cleans up resources in afterEach
  • Includes a positive test case to ensure legitimate file access still works

This provides a solid foundation for testing the security fix.


332-398: Comprehensive test coverage for edge cases and error messages!

The tests effectively verify:

  • Edge case handling for the base directory itself
  • Detailed error messages include security-relevant keywords
  • Multiple traversal sequences are caught

The use of try-catch in the error message test (lines 353-378) allows for more detailed assertions on the error content, which is valuable for security-related errors.

@gkganesh12 gkganesh12 changed the title Fix: Path traversal vulnerability in URL-to-folder mapping fix: Path traversal vulnerability in URL-to-folder mapping Dec 21, 2025
@gkganesh12 gkganesh12 changed the title fix: Path traversal vulnerability in URL-to-folder mapping fix: path traversal vulnerability in URL-to-folder mapping Dec 24, 2025
- Add input validation to validatePathWithinBase for non-empty strings
- Document symlink behavior in JSDoc (does not resolve symlinks)
- Simplify test case by removing conditional logic
- Ensure consistent error messages for security violations
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbcc87b and 09c27c4.

📒 Files selected for processing (2)
  • apps/generator/lib/utils.js
  • apps/generator/test/parser.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/generator/test/parser.test.js
🧰 Additional context used
🧬 Code graph analysis (1)
apps/generator/lib/utils.js (1)
apps/generator/lib/parser.js (1)
  • path (2-2)
🔇 Additional comments (2)
apps/generator/lib/utils.js (2)

216-222: LGTM: Input validation is thorough.

The input validation correctly ensures both filePath and baseDir are non-empty strings and throws appropriate TypeError for invalid inputs.


200-213: Excellent documentation.

The JSDoc is comprehensive and clearly documents the function's behavior, parameters, return value, exceptions, and the important note about symlink handling.

- Add case-insensitive path comparison on Windows to match filesystem behavior
- Fix root directory edge case by checking for existing trailing separator
- Use fs.realpathSync() for base directory to resolve symlinks and get canonical paths
- Add tests for Windows case-insensitivity and root directory handling
- Update JSDoc to document Windows case-insensitivity and root directory handling
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
apps/generator/test/parser.test.js (6)

3-4: Inconsistent use of fs.promises API.

Line 4 destructures mkdir, writeFile, rm from require('fs').promises, but line 216 uses fs.promises.mkdtemp directly. Consider using a consistent approach.

Suggested fix
 const os = require('os');
-const { mkdir, writeFile, rm } = require('fs').promises;
+const { mkdir, writeFile, rm, mkdtemp } = require('fs').promises;

Then on line 216:

-      tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'asyncapi-test-'));
+      tempDir = await mkdtemp(path.join(os.tmpdir(), 'asyncapi-test-'));

298-320: Test name is misleading.

The test is named "should reject absolute paths outside base directory" but actually tests relative traversal (../../) rather than absolute paths. Consider either renaming the test to match its behavior or adding a separate test for true absolute paths (e.g., /etc/passwd or C:\Windows\System32).

Suggested rename
-    it('should reject absolute paths outside base directory', async () => {
+    it('should reject relative traversal to files outside base directory', async () => {

Or add a separate test for absolute paths:

it('should reject absolute paths outside base directory', async () => {
  // ... setup ...
  const absolutePathUri = {
    toString: () => 'https://schema.example.com/crm//etc/passwd',
    valueOf: () => 'https://schema.example.com/crm//etc/passwd'
  };
  await expect(resolver.read(absolutePathUri)).rejects.toThrow();
});

339-341: Vague assertion - clarify expected behavior.

The assertion .rejects.toThrow() without a specific message accepts any error. The comment on line 333 says "should be allowed or handled gracefully" but the test expects a throw. Consider clarifying the expected behavior:

  • If it should fail because directories can't be read as files, expect an EISDIR or similar error.
  • If it should be allowed, the test logic needs to change.
Suggested improvement
       // This should either succeed (if baseDir is a file) or fail with a clear error
-      await expect(resolver.read(baseDirUri)).rejects.toThrow();
+      // Reading a directory should fail with an appropriate filesystem error
+      await expect(resolver.read(baseDirUri)).rejects.toThrow(/EISDIR|ENOENT|Error opening file/);

359-368: Replace anti-pattern expect(true).toBe(false) with idiomatic Jest assertion.

Line 362 uses expect(true).toBe(false) which produces a confusing failure message. Use expect.assertions() at the start of the test to ensure the catch block runs.

Suggested fix
+      expect.assertions(3);
       try {
         await resolver.read(maliciousUri);
-        // If we reach here, the test should fail
-        expect(true).toBe(false); // Force test failure
+        // Unreachable if validation works correctly
       } catch (error) {
         expect(error.message).toContain('Path traversal detected');
         expect(error.message).toContain('security violation');
         expect(error.message).toContain('blocked');
       }

Alternatively, if only one assertion is needed:

await expect(resolver.read(maliciousUri)).rejects.toThrow(/Path traversal detected.*security violation.*blocked/s);

390-393: Silent test skip obscures test coverage.

Using return to skip tests on non-Windows platforms makes it appear as if the test passed when it was actually skipped. Consider using Jest's conditional skip pattern for clearer test reporting.

Suggested improvement
-    it('should handle case-insensitive path comparison on Windows', async () => {
-      if (process.platform !== 'win32') {
-        return; // Skip on non-Windows platforms
-      }
+    (process.platform === 'win32' ? it : it.skip)('should handle case-insensitive path comparison on Windows', async () => {

Or use describe.skip for the entire block:

(process.platform === 'win32' ? describe : describe.skip)('Windows-specific tests', () => {
  it('should handle case-insensitive path comparison', async () => {
    // ...
  });
});

417-422: Silent test skip and potentially misleading test name.

Same issue as the Windows test - the early return silently skips without indicating in test output. Additionally, the test name suggests testing the root directory (/) as a base, but it actually tests a subdirectory of tmpdir(). Consider clarifying the test's intent.

Suggested improvements
-    it('should handle root directory edge case', async () => {
-      // This test verifies that root directories (/, C:\) are handled correctly
-      // Skip on Windows as we can't easily test C:\ root
-      if (process.platform === 'win32') {
-        return;
-      }
+    (process.platform !== 'win32' ? it : it.skip)('should handle URL mapping to top-level directory', async () => {
+      // This test verifies that URL-to-folder mapping works when the URL path is at the root level
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09c27c4 and f944b3a.

📒 Files selected for processing (2)
  • apps/generator/lib/utils.js
  • apps/generator/test/parser.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/generator/lib/utils.js
🧰 Additional context used
🧬 Code graph analysis (1)
apps/generator/test/parser.test.js (1)
apps/generator/lib/parser.js (6)
  • require (3-3)
  • require (4-4)
  • require (5-5)
  • fs (1-1)
  • path (2-2)
  • resolver (102-127)
🔇 Additional comments (5)
apps/generator/test/parser.test.js (5)

214-230: LGTM!

The beforeEach/afterEach setup correctly creates an isolated temporary directory structure and cleans it up after each test, ensuring test isolation.


248-255: LGTM!

The test correctly validates that files within the base directory can be read successfully. The mock URI object pattern is clear.


258-276: LGTM!

This test correctly validates that Unix-style path traversal attempts (../) are blocked with an appropriate error message.


278-296: LGTM!

Good coverage for Windows-style backslash traversal sequences (..\\), ensuring cross-platform security.


370-388: LGTM!

Good test ensuring that multiple consecutive traversal sequences are blocked.

- Add decodeURIComponent() to decode URL-encoded characters before path validation
- Prevents bypass using encoded sequences like %2e%2e%2f (../) or %2e%2e%5c (..\)
- Add proper error handling for invalid URL encoding with descriptive error messages
- Add comprehensive tests for URL-encoded traversal attempts
- Use descriptive error variable name (decodeError) for better code clarity
@sonarqubecloud
Copy link

@derberg
Copy link
Member

derberg commented Dec 29, 2025

Please do not open PRs until the issue is reviewed by maintainers and opened for contributions.

Additionally, please:

  • follow the contributor guide
  • do not open PRs if others have already
  • read and follow bots' comments
  • don't use AI

@derberg derberg closed this Dec 29, 2025
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.

[BUG] Security - Path Traversal Vulnerability in URL-to-Folder Mapping

3 participants