From 32612b8c8ef6c0ce81f12a5e84a1b12e8a860440 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:34:04 +0100 Subject: [PATCH 1/3] fix(targets): reject special path targets during preflight --- sdk/typescript/src/targets.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sdk/typescript/src/targets.ts b/sdk/typescript/src/targets.ts index 5c8ab0888..ac60d5c71 100644 --- a/sdk/typescript/src/targets.ts +++ b/sdk/typescript/src/targets.ts @@ -266,6 +266,20 @@ export async function normalizeTarget( cause: error, }); } + let metadata; + try { + metadata = await abortable(() => stat(canonical), signal); + } catch (error) { + throwIfAborted(signal); + throw new InvalidTargetError(`Path target does not exist: ${value}`, { + cause: error, + }); + } + if (!metadata.isFile() && !metadata.isDirectory()) { + throw new InvalidTargetError( + `Path target must be a regular file or directory: ${value}`, + ); + } const relativePath = relative(root, canonical); if ( relativePath === ".." || From ae5b5619bd50a0624a90e0ba718206aba31d28bf Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:34:24 +0100 Subject: [PATCH 2/3] test(targets): reject special filesystem path targets --- .../path-target-special-files.test.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 sdk/typescript/tests-ts/path-target-special-files.test.ts diff --git a/sdk/typescript/tests-ts/path-target-special-files.test.ts b/sdk/typescript/tests-ts/path-target-special-files.test.ts new file mode 100644 index 000000000..f2cab1627 --- /dev/null +++ b/sdk/typescript/tests-ts/path-target-special-files.test.ts @@ -0,0 +1,46 @@ +import { createServer } from "node:net"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "bun:test"; +import { normalizeTarget } from "../src/targets.js"; + +test.skipIf(process.platform === "win32")( + "rejects special filesystem nodes during path-target preflight", + async () => { + const root = await mkdtemp(join(tmpdir(), "codex-security-special-target-")); + const repository = join(root, "repository"); + const socketPath = join(repository, "target.sock"); + await mkdir(repository); + const server = createServer(); + + try { + await new Promise((resolvePromise, reject) => { + server.once("error", reject); + server.listen(socketPath, () => resolvePromise()); + }); + + await expect(normalizeTarget(repository, [socketPath])).rejects.toThrow( + "Path target must be a regular file or directory", + ); + } finally { + await new Promise((resolvePromise) => server.close(() => resolvePromise())); + await rm(root, { recursive: true, force: true }); + } + }, +); + +test("keeps ordinary file and directory path targets valid", async () => { + const root = await mkdtemp(join(tmpdir(), "codex-security-regular-target-")); + const repository = join(root, "repository"); + try { + await mkdir(join(repository, "src"), { recursive: true }); + await writeFile(join(repository, "src", "app.ts"), "export {};\n"); + + await expect( + normalizeTarget(repository, ["src/app.ts", "src"]), + ).resolves.toEqual({ kind: "paths", paths: ["src/app.ts", "src"] }); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); From 6a18785b471a40bf2cb769e0118c5cb397cef926 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:40:28 +0100 Subject: [PATCH 3/3] fix(targets): preserve outside-path validation precedence --- sdk/typescript/src/targets.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sdk/typescript/src/targets.ts b/sdk/typescript/src/targets.ts index ac60d5c71..c13227d25 100644 --- a/sdk/typescript/src/targets.ts +++ b/sdk/typescript/src/targets.ts @@ -266,6 +266,16 @@ export async function normalizeTarget( cause: error, }); } + const relativePath = relative(root, canonical); + if ( + relativePath === ".." || + relativePath.startsWith(`..${sep}`) || + isAbsolute(relativePath) + ) { + throw new InvalidTargetError( + `Path target is outside the repository: ${value}`, + ); + } let metadata; try { metadata = await abortable(() => stat(canonical), signal); @@ -280,16 +290,6 @@ export async function normalizeTarget( `Path target must be a regular file or directory: ${value}`, ); } - const relativePath = relative(root, canonical); - if ( - relativePath === ".." || - relativePath.startsWith(`..${sep}`) || - isAbsolute(relativePath) - ) { - throw new InvalidTargetError( - `Path target is outside the repository: ${value}`, - ); - } const normalized = relativePath.split(sep).join("/") || "."; if (!paths.includes(normalized)) { paths.push(normalized);