Skip to content

Commit 4dd0694

Browse files
bartvenemanclaude
andauthored
Prevent path traversal in --coverage-dir argument (#78)
* Fix path traversal vulnerability in --coverage-dir option Resolve the provided path to an absolute path and verify it stays within process.cwd(), rejecting any value that would escape the current working directory via ../ sequences. https://claude.ai/code/session_01SoGLdxJCSsRPACjf6uLuyR * rebase + format --------- Co-authored-by: Claude <[email protected]>
1 parent 3941a07 commit 4dd0694

File tree

2 files changed

+26
-4
lines changed

2 files changed

+26
-4
lines changed

src/cli/arguments.test.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { test, expect } from '@playwright/test'
2+
import { resolve } from 'node:path'
23
import { parse_arguments, validate_arguments } from './arguments'
34

45
test.describe('--coverage-dir', () => {
@@ -12,9 +13,21 @@ test.describe('--coverage-dir', () => {
1213
expect(() => validate_arguments(parse_arguments([cov, '--coverage-dir']))).toThrowError()
1314
})
1415

15-
test('valid --coverage-dir=path/to/coverage', () => {
16-
let result = validate_arguments(parse_arguments([cov, '--coverage-dir=/path/to/coverage']))
17-
expect(result['coverage-dir']).toEqual('/path/to/coverage')
16+
test('valid --coverage-dir=coverage', () => {
17+
let result = validate_arguments(parse_arguments([cov, '--coverage-dir=coverage']))
18+
expect(result['coverage-dir']).toEqual(resolve('coverage'))
19+
})
20+
21+
test('path traversal --coverage-dir=../../etc', () => {
22+
expect(() =>
23+
validate_arguments(parse_arguments([cov, '--coverage-dir=../../etc'])),
24+
).toThrowError()
25+
})
26+
27+
test('path traversal --coverage-dir=../sibling', () => {
28+
expect(() =>
29+
validate_arguments(parse_arguments([cov, '--coverage-dir=../sibling'])),
30+
).toThrowError()
1831
})
1932
})
2033

src/cli/arguments.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { parseArgs } from 'node:util'
2+
import { resolve, sep } from 'node:path'
23
import * as v from 'valibot'
34

45
const show_uncovered_options = {
@@ -13,7 +14,15 @@ const reporters = {
1314
json: 'json',
1415
} as const
1516

16-
let CoverageDirSchema = v.pipe(v.string(), v.nonEmpty())
17+
let CoverageDirSchema = v.pipe(
18+
v.string(),
19+
v.nonEmpty(),
20+
v.transform((value) => resolve(value)),
21+
v.check((value) => {
22+
let cwd = process.cwd()
23+
return value === cwd || value.startsWith(cwd + sep)
24+
}, 'InvalidPath'),
25+
)
1726
// Coerce args string to number and validate that it's between 0 and 1
1827
let RatioPercentageSchema = v.pipe(
1928
v.string(),

0 commit comments

Comments
 (0)