From d440c1f2d0984905304302537751478ffd332e1b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Mar 2026 09:49:06 +0000 Subject: [PATCH 1/2] 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 --- src/cli/arguments.test.ts | 15 ++++++++++++--- src/cli/arguments.ts | 11 ++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/cli/arguments.test.ts b/src/cli/arguments.test.ts index 1385269..def7ed7 100644 --- a/src/cli/arguments.test.ts +++ b/src/cli/arguments.test.ts @@ -1,4 +1,5 @@ import { test, expect } from '@playwright/test' +import { resolve } from 'node:path' import { parse_arguments, validate_arguments } from './arguments' test.describe('--coverage-dir', () => { @@ -12,9 +13,17 @@ test.describe('--coverage-dir', () => { expect(() => validate_arguments(parse_arguments([cov, '--coverage-dir']))).toThrowError() }) - test('valid --coverage-dir=path/to/coverage', () => { - let result = validate_arguments(parse_arguments([cov, '--coverage-dir=/path/to/coverage'])) - expect(result['coverage-dir']).toEqual('/path/to/coverage') + test('valid --coverage-dir=coverage', () => { + let result = validate_arguments(parse_arguments([cov, '--coverage-dir=coverage'])) + expect(result['coverage-dir']).toEqual(resolve('coverage')) + }) + + test('path traversal --coverage-dir=../../etc', () => { + expect(() => validate_arguments(parse_arguments([cov, '--coverage-dir=../../etc']))).toThrowError() + }) + + test('path traversal --coverage-dir=../sibling', () => { + expect(() => validate_arguments(parse_arguments([cov, '--coverage-dir=../sibling']))).toThrowError() }) }) diff --git a/src/cli/arguments.ts b/src/cli/arguments.ts index ed16d89..249360d 100644 --- a/src/cli/arguments.ts +++ b/src/cli/arguments.ts @@ -1,4 +1,5 @@ import { parseArgs } from 'node:util' +import { resolve, sep } from 'node:path' import * as v from 'valibot' const show_uncovered_options = { @@ -13,7 +14,15 @@ const reporters = { json: 'json', } as const -let CoverageDirSchema = v.pipe(v.string(), v.nonEmpty()) +let CoverageDirSchema = v.pipe( + v.string(), + v.nonEmpty(), + v.transform((value) => resolve(value)), + v.check((value) => { + let cwd = process.cwd() + return value === cwd || value.startsWith(cwd + sep) + }, 'InvalidPath'), +) // Coerce args string to number and validate that it's between 0 and 1 let RatioPercentageSchema = v.pipe( v.string(), From e727bb3b69715650072c0255637467df895b701e Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Thu, 12 Mar 2026 14:59:57 +0100 Subject: [PATCH 2/2] rebase + format --- src/cli/arguments.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cli/arguments.test.ts b/src/cli/arguments.test.ts index def7ed7..9e8ca96 100644 --- a/src/cli/arguments.test.ts +++ b/src/cli/arguments.test.ts @@ -19,11 +19,15 @@ test.describe('--coverage-dir', () => { }) test('path traversal --coverage-dir=../../etc', () => { - expect(() => validate_arguments(parse_arguments([cov, '--coverage-dir=../../etc']))).toThrowError() + expect(() => + validate_arguments(parse_arguments([cov, '--coverage-dir=../../etc'])), + ).toThrowError() }) test('path traversal --coverage-dir=../sibling', () => { - expect(() => validate_arguments(parse_arguments([cov, '--coverage-dir=../sibling']))).toThrowError() + expect(() => + validate_arguments(parse_arguments([cov, '--coverage-dir=../sibling'])), + ).toThrowError() }) })