Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,9 @@ The CLI and SDK recognize the following user-configurable environment:
| `CI` | Disable interactive update notices in automated environments. |
| `NO_COLOR`, `TERM` | Disable colored scan-history output when `NO_COLOR` is defined or `TERM=dumb`. |

Custom Codex executables must support thread source attribution for both `exec`
and `app-server` requests (Codex 0.149.1 or later).

On Windows, `CODEX_CLI_PATH` must name a native `.exe` or `.com`. Command
shims such as `codex.cmd` automatically use the bundled Codex executable
instead.
Expand Down
Binary file modified sdk/typescript/_bundled_plugin/mcp/server.mjs.br.part-000
Binary file not shown.
Binary file modified sdk/typescript/_bundled_plugin/mcp/server.mjs.br.part-001
Binary file not shown.
4 changes: 2 additions & 2 deletions sdk/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@
"@inquirer/prompts": "8.3.0",
"@linear/sdk": "89.0.0",
"@octokit/core": "7.0.6",
"@openai/codex": "0.148.0-alpha.8",
"@openai/codex-sdk": "0.148.0-alpha.8",
"@openai/codex": "0.149.1",
"@openai/codex-sdk": "0.149.1",
"ajv": "8.20.0",
"extract-zip": "2.0.1",
"fast-uri": "3.1.5",
Expand Down
72 changes: 36 additions & 36 deletions sdk/typescript/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sdk/typescript/scripts/check-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ const distFiles = new Set(
"scan-logs",
"scan-sessions",
"targets",
"thread-source",
"trusted-executable",
"version",
"windows-path",
Expand Down
1 change: 1 addition & 0 deletions sdk/typescript/scripts/smoke-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ async function smokeNestedDeepScanWorker(installedRoot, consumer) {
});
const { events } = await codex
.startThread({
threadSource: "security_scan",
workingDirectory: workerHome,
skipGitRepoCheck: true,
sandboxMode: "read-only",
Expand Down
4 changes: 4 additions & 0 deletions sdk/typescript/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ import {
type ScanProgress,
type ScanWorkerStatus,
} from "./worker-progress.js";
import { CODEX_SECURITY_THREAD_SOURCES } from "./thread-source.js";
import { CODEX_EXECUTABLE_VERSION, CODEX_SDK_VERSION } from "./version.js";
import {
acquireCodexSecurityCredentialHomeLock,
Expand Down Expand Up @@ -506,6 +507,7 @@ export class CodexSecurity {
options.auth,
);
const thread = codex.startThread({
threadSource: CODEX_SECURITY_THREAD_SOURCES.validation,
workingDirectory: outputDir,
skipGitRepoCheck: true,
approvalPolicy,
Expand Down Expand Up @@ -1126,6 +1128,7 @@ export class CodexSecurity {
options.auth,
);
const thread = codex.startThread({
threadSource: CODEX_SECURITY_THREAD_SOURCES.scan,
workingDirectory: scanDir,
skipGitRepoCheck: true,
approvalPolicy,
Expand Down Expand Up @@ -1200,6 +1203,7 @@ export class CodexSecurity {
filesTotal: scopeFileCount,
});
const validationThread = codex.startThread({
threadSource: CODEX_SECURITY_THREAD_SOURCES.scan,
workingDirectory: join(scanDir, "artifacts"),
skipGitRepoCheck: true,
approvalPolicy,
Expand Down
23 changes: 22 additions & 1 deletion sdk/typescript/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ import {
type ScanComparisonInput,
} from "./scan-comparison.js";
import { scanActivitiesFromEvent } from "./scan-activity.js";
import {
CODEX_SECURITY_THREAD_SOURCES,
type CodexSecurityThreadSource,
} from "./thread-source.js";
import { readScanLogs } from "./scan-logs.js";
import {
renderScanHistory,
Expand Down Expand Up @@ -1002,13 +1006,20 @@ type MatchingPlan = JsonObject & {
batches: (JsonObject & MatchingBatch)[];
};

type SkillThreadSource = Extract<
CodexSecurityThreadSource,
| typeof CODEX_SECURITY_THREAD_SOURCES.remediation
| typeof CODEX_SECURITY_THREAD_SOURCES.validation
>;

interface SkillCommandOutput {
readonly command: "validate" | "patch" | "verify-fix";
readonly stdout: Writable;
readonly stderr: Writable;
readonly appServer?: {
readonly directory: string;
readonly prompt: string;
readonly threadSource: SkillThreadSource;
readonly sandbox?: "read-only" | "workspace-write";
readonly onEvent?: (event: Readonly<Record<string, unknown>>) => void;
};
Expand Down Expand Up @@ -1349,6 +1360,7 @@ export async function runCodexSkillCommand(
: {
directory: output.appServer.directory,
prompt: output.appServer.prompt,
threadSource: output.appServer.threadSource,
input: invocation.stdin!,
sandbox: output.appServer.sandbox,
onEvent: output.appServer.onEvent,
Expand Down Expand Up @@ -5100,9 +5112,14 @@ async function runSkill(
].join("\n");
const patch = skill === "fix-finding";
const appServer = patch || verify;
const threadSource = patch
? CODEX_SECURITY_THREAD_SOURCES.remediation
: CODEX_SECURITY_THREAD_SOURCES.validation;
return dependencies.runCodex(
[
...(appServer ? ["app-server"] : ["exec", "--ignore-user-config"]),
...(appServer
? ["app-server"]
: ["exec", "--ignore-user-config", "--thread-source", threadSource]),
"--disable",
"plugins",
...(appServer ? [] : ["--ephemeral", "--color", "never", "--json"]),
Expand Down Expand Up @@ -5150,6 +5167,7 @@ async function runSkill(
appServer: {
directory,
prompt,
threadSource,
...(verify ? { sandbox: "read-only" as const } : {}),
...(options.onEvent === undefined
? {}
Expand All @@ -5168,6 +5186,7 @@ export async function readSkillCommandOutput(
appServer?: {
readonly directory?: string;
readonly prompt: string;
readonly threadSource: SkillThreadSource;
readonly input: NodeJS.WritableStream;
readonly sandbox?: "read-only" | "workspace-write";
readonly onEvent?: (event: Readonly<Record<string, unknown>>) => void;
Expand All @@ -5188,12 +5207,14 @@ export async function readSkillCommandOutput(
appServer?.input.write(`${JSON.stringify(request)}\n`);
};
const startThread = (config?: JsonObject): void => {
if (appServer === undefined) return;
send({
id: 2,
method: "thread/start",
// An explicit cwd makes Codex persist trust for a new project.
// Inherit the child process cwd and preserve the user's decision.
params: {
threadSource: appServer.threadSource,
approvalPolicy:
appServer?.sandbox === "read-only" ? "on-request" : "never",
sandbox: appServer?.sandbox ?? "workspace-write",
Expand Down
6 changes: 5 additions & 1 deletion sdk/typescript/src/component-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
runReadOnlyCodex,
type ReadOnlyCodexOptions,
} from "./scan-comparison.js";
import { CODEX_SECURITY_THREAD_SOURCES } from "./thread-source.js";
import {
enclosingGitWorktreeRoot,
normalizeRepository,
Expand Down Expand Up @@ -88,7 +89,10 @@ export async function planComponents(
].join("\n"),
z.toJSONSchema(componentPlanSchema, { target: "openapi-3.0" }),
{ ...options, config: options.config ?? {}, workingDirectory: tmpdir() },
{ surface: "cli" },
{
surface: "cli",
threadSource: CODEX_SECURITY_THREAD_SOURCES.scan,
},
);
const plan = await normalizeComponentPlan(
repository,
Expand Down
20 changes: 18 additions & 2 deletions sdk/typescript/src/scan-comparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ import {
runCodexCommand,
type CodexCommand,
} from "./runtime.js";
import {
CODEX_SECURITY_THREAD_SOURCES,
type CodexSecurityThreadSource,
} from "./thread-source.js";

type Finding = { occurrenceId: string } & Record<string, unknown>;
type ReadOnlyCodexThreadSource = Extract<
CodexSecurityThreadSource,
| typeof CODEX_SECURITY_THREAD_SOURCES.scan
| typeof CODEX_SECURITY_THREAD_SOURCES.scanComparison
>;

export interface ScanComparisonInput {
before: readonly Finding[];
Expand Down Expand Up @@ -117,7 +126,10 @@ export async function matchScanFindingsInternal(
comparisonPrompt(input),
z.toJSONSchema(comparisonSchema, { target: "openapi-3.0" }),
options,
runtimeOptions,
{
...runtimeOptions,
threadSource: CODEX_SECURITY_THREAD_SOURCES.scanComparison,
},
);
let response: unknown;
try {
Expand All @@ -138,7 +150,10 @@ export async function runReadOnlyCodex(
prompt: string,
outputSchema: unknown,
options: ReadOnlyCodexOptions,
runtimeOptions: { surface: CodexSecuritySurface },
runtimeOptions: {
surface: CodexSecuritySurface;
threadSource: ReadOnlyCodexThreadSource;
},
): Promise<string> {
const config =
options.config === undefined
Expand Down Expand Up @@ -197,6 +212,7 @@ export async function runReadOnlyCodex(
} as NonNullable<CodexOptions["config"]>,
});
const thread = codex.startThread({
threadSource: runtimeOptions.threadSource,
...(model === undefined ? {} : { model }),
modelReasoningEffort: reasoningEffort,
sandboxMode: "read-only",
Expand Down
9 changes: 9 additions & 0 deletions sdk/typescript/src/thread-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const CODEX_SECURITY_THREAD_SOURCES = {
scan: "security_scan",
validation: "security_validation",
remediation: "security_remediation",
scanComparison: "security_scan_comparison",
} as const;

export type CodexSecurityThreadSource =
(typeof CODEX_SECURITY_THREAD_SOURCES)[keyof typeof CODEX_SECURITY_THREAD_SOURCES];
5 changes: 3 additions & 2 deletions sdk/typescript/tests-ts/api-attribution-concurrency.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { mkdir, readFile } from "node:fs/promises";
import { join } from "node:path";
import type { CodexOptions } from "@openai/codex-sdk";
import type { CodexOptions, ThreadOptions } from "@openai/codex-sdk";
import { afterEach, describe, expect, test } from "bun:test";
import { parse as parseToml } from "smol-toml";
import { CodexSecurity } from "../src/index.js";
Expand Down Expand Up @@ -75,7 +75,7 @@ describe("delegated scan attribution", () => {
return {};
},
createCodex: (options: CodexOptions) => ({
startThread: () => ({
startThread: (threadOptions: ThreadOptions) => ({
id: null,
async runStreamed() {
active += 1;
Expand All @@ -91,6 +91,7 @@ describe("delegated scan attribution", () => {
codex_security_surface: surface,
},
});
expect(threadOptions.threadSource).toBe("security_scan");
await concurrentScans;
const sharedConfig = parseToml(
await readFile(
Expand Down
Loading
Loading