Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dark-bars-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/backend': patch
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.

Minor maybe? I wasn't sure

---

Add path traversal protections in `joinPaths`
24 changes: 24 additions & 0 deletions packages/backend/src/util/__tests__/path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,28 @@ describe('utils.joinPaths(...args)', () => {
it('handles no input', () => {
expect(joinPaths()).toBe('');
});

it('rejects literal ".." segments', () => {
expect(() => joinPaths('/sessions', 'sess_abc', 'tokens', '../../../users')).toThrow();
expect(() => joinPaths('/sessions', '..')).toThrow();
});

it('rejects "." segments', () => {
expect(() => joinPaths('foo/./bar')).toThrow();
expect(() => joinPaths('foo', '.', 'bar')).toThrow();
expect(() => joinPaths('foo', './', 'bar')).toThrow();
});

it('rejects percent-encoded dot segments', () => {
expect(() => joinPaths('/sessions', 'sess_abc', 'tokens', '%2e%2e/users')).toThrow();
expect(() => joinPaths('/sessions', 'sess_abc', 'tokens', '%2E%2E/users')).toThrow();
expect(() => joinPaths('/sessions', 'sess_abc', 'tokens', '.%2E/users')).toThrow();
expect(() => joinPaths('foo', '%2e', 'bar')).toThrow();
});

it('allows legitimate URLs and ID-like segments', () => {
expect(joinPaths('https://api.clerk.com', 'v1', '/sessions/sess_abc/tokens/supabase')).toBe(
'https://api.clerk.com/v1/sessions/sess_abc/tokens/supabase',
);
});
});
20 changes: 19 additions & 1 deletion packages/backend/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,27 @@ const MULTIPLE_SEPARATOR_REGEX = new RegExp('(?<!:)' + SEPARATOR + '{1,}', 'g');

type PathString = string | null | undefined;

function isDotSegment(segment: string): boolean {
let decoded: string;
try {
decoded = decodeURIComponent(segment);
} catch {
decoded = segment;
}
return decoded === '.' || decoded === '..';
}
Comment thread
dominic-clerk marked this conversation as resolved.

export function joinPaths(...args: PathString[]): string {
return args
const result = args
.filter(p => p)
.join(SEPARATOR)
.replace(MULTIPLE_SEPARATOR_REGEX, SEPARATOR);

for (const segment of result.split(SEPARATOR)) {
if (isDotSegment(segment)) {
throw new Error(`joinPaths: "." and ".." path segments are not allowed (received "${result}")`);
}
}

return result;
}
Loading