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
48 changes: 29 additions & 19 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ npx @openai/codex-security --version
```

The package supports macOS, Linux, and Windows and requires Node.js 22.13.0 or
later in the 22.x release line, Node.js 24.x, or Node.js 26.x. Scanning and
exporting findings also require Python 3.10 or later. If you use Python 3.10,
install the `tomli` package. Select another interpreter with `--python`,
`pythonPath`, or `PYTHON` when needed.
later in the 22.x release line, Node.js 24.x, or Node.js 26.x. Scans, bulk
scans, exports, scan history, and saved findings also require Python 3.10 or
later. Python 3.10 also requires `tomli`. Use `--python` with `scan`,
`bulk-scan`, or `export`; use `pythonPath` with the SDK. Set `PYTHON` to select
an interpreter for any Python-backed command.

When a newer version is available, the CLI shows the update command for your
installation method. Set `CODEX_SECURITY_NO_UPDATE_NOTICE=1` to hide the
Expand Down Expand Up @@ -256,6 +257,10 @@ directory and any enclosing Git worktree. When SARIF is produced, it is written
to
`<scan-dir>/exports/results.sarif`.

Working-tree snapshots include files from untracked nested Git repositories.
Initialized submodules must be clean and checked out at the commit recorded by
the parent repository.

Repeat `--knowledge-base PATH` for multiple files or directories; `bulk-scan`
shares them with every repository. Directories are searched recursively for
Markdown, text, PDF, and Word (`.docx`) files.
Expand Down Expand Up @@ -328,7 +333,7 @@ configuration. Each scan starts with a private runtime and these Codex
defaults:

```toml
cli_auth_credentials_store = "file"
cli_auth_credentials_store = "auto"
model = "gpt-5.6-sol"
model_reasoning_effort = "xhigh"
model_reasoning_summary = "detailed"
Expand Down Expand Up @@ -382,32 +387,34 @@ permissions. See [Local security model](#local-security-model).

### Deep-scan engine configuration

When the bundled plugin runs in a normal Codex host, its repeated-discovery
engine reads `$CODEX_HOME/codex-security/config.toml`, defaulting to
Deep scans read `$CODEX_HOME/codex-security/config.toml`, defaulting to
`~/.codex/codex-security/config.toml`:

```toml
[deep_scan]
workers = "auto"
subagents = 3
stop_after_no_new = 6
stop_after_consecutive_errors = 3
max_discovery_runs = 60
max_time_hours = 96
```

`workers = "auto"` uses half the available parallelism, with a minimum of one
and a maximum of six discovery workers. Set `workers` to a positive integer to
choose an explicit count. `subagents` must be a nonnegative integer;
`stop_after_no_new` and `max_discovery_runs` must be positive integers.
`max_time_hours` must be a positive finite number no greater than 96; fractional
hours are supported. Unknown `[deep_scan]` keys are rejected.
`stop_after_no_new`, `stop_after_consecutive_errors`, and `max_discovery_runs`
must be positive integers. `max_time_hours` must be a positive finite number no
greater than 96; fractional hours are supported. Unknown `[deep_scan]` keys are
rejected.

These settings are separate from Codex's
`features.multi_agent_v2.max_concurrent_threads_per_session` and
`bulk-scan --workers`. Standalone CLI and SDK scans create an isolated
`CODEX_HOME`, import the ambient `[deep_scan]` configuration, and apply explicit
CLI or SDK options on top. Use `--codex` to adjust the Codex session thread
limit, not to set `[deep_scan]` values.
CLI or SDK options on top. Set `stop_after_consecutive_errors` in the
configuration file. Use `--codex` to adjust the Codex session thread limit, not
to set `[deep_scan]` values.

### Environment variables

Expand Down Expand Up @@ -518,8 +525,10 @@ Use `--post-scan-prompt-file PATH` to run a follow-up in the same authenticated
session after each scan, including incomplete or failed scans. Canceled scans
and scans stopped at their configured cost limit do not start another turn.

`--workers` limits concurrent scans and `--max-attempts` retries failures.
Results remain under `--output-dir`; rerun the same command to resume.
`--workers` sets the number of concurrent repository scans and defaults to
`4`. `--max-attempts` sets how many times each pending repository can run per
invocation and defaults to `1`. Results remain under `--output-dir`; rerun the
same command to resume.

### Scan history and reruns

Expand All @@ -535,11 +544,12 @@ workers, which can include source code and credentials.
Every scan history command accepts a full scan ID or a unique prefix of at
least eight characters.

Scan history uses the existing Codex Security workbench database at
`$CODEX_HOME/state/plugins/codex-security/workbench.sqlite3`. Set
`CODEX_SECURITY_STATE_DIR` to place the database elsewhere. Scan credentials
are never stored in the scan configuration. Recorded failure summaries and
bulk-scan receipts omit messages that contain recognizable credentials.
Scan history uses `$CODEX_SECURITY_STATE_DIR/workbench.sqlite3` when
`CODEX_SECURITY_STATE_DIR` is set. Otherwise, it uses
`$CODEX_HOME/state/plugins/codex-security/workbench.sqlite3`; `CODEX_HOME`
defaults to `~/.codex`. Scan credentials are never stored in the scan
configuration. Recorded failure summaries and bulk-scan receipts omit messages
that contain recognizable credentials.

The scan sandbox permits writes to the selected state directory so SQLite can
maintain its database and journal files. If the host itself cannot write to the
Expand Down
52 changes: 36 additions & 16 deletions sdk/typescript/tests-ts/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Writable } from "node:stream";
import { fileURLToPath, pathToFileURL } from "node:url";
import { stripVTControlCharacters } from "node:util";
import { describe, expect, test } from "bun:test";
import { parse as parseToml } from "smol-toml";
import type {
CodexSecurityConfig,
JsonObject,
Expand Down Expand Up @@ -349,18 +350,29 @@ describe("CLI", () => {
const readme = await readFile(new URL("../README.md", import.meta.url), {
encoding: "utf8",
});
expect(readme).toContain(
`model = "${DEFAULT_SCAN_MODEL_CONFIGURATION.model}"`,
);
expect(readme).toContain(
`model_reasoning_effort = "${DEFAULT_SCAN_MODEL_CONFIGURATION.reasoningEffort}"`,
const documentedConfigs = [
...readme.matchAll(/^```toml\s*\n([\s\S]*?)\n```\s*$/gmu),
].map(([, config]) => parseToml(config!));
const documentedRuntime = documentedConfigs.find(
(config) => "cli_auth_credentials_store" in config,
);
expect(documentedRuntime).toMatchObject({
cli_auth_credentials_store:
DEFAULT_CODEX_CONFIG["cli_auth_credentials_store"],
model: DEFAULT_SCAN_MODEL_CONFIGURATION.model,
model_reasoning_effort: DEFAULT_SCAN_MODEL_CONFIGURATION.reasoningEffort,
});

const features = DEFAULT_CODEX_CONFIG["features"] as JsonObject;
const multiAgent = features["multi_agent_v2"] as JsonObject;
expect(readme).toContain(
`max_concurrent_threads_per_session = ${String(multiAgent["max_concurrent_threads_per_session"])}`,
);
expect(documentedRuntime).toMatchObject({
features: {
multi_agent_v2: {
max_concurrent_threads_per_session:
multiAgent["max_concurrent_threads_per_session"],
},
},
});

const python = Bun.which("python3") ?? Bun.which("python");
expect(python).not.toBeNull();
Expand Down Expand Up @@ -398,19 +410,27 @@ describe("CLI", () => {
workers: number;
subagents: number;
stopAfterNoNew: number;
stopAfterConsecutiveErrors: number;
maxDiscoveryRuns: number;
maxTimeHours: number;
};
expect(defaults.workers).toBe(6);
expect(readme).toContain('workers = "auto"');
expect(readme).toContain(`subagents = ${defaults.subagents}`);
expect(readme).toContain(
`stop_after_no_new = ${defaults.stopAfterNoNew}`,
);
expect(readme).toContain(
`max_discovery_runs = ${defaults.maxDiscoveryRuns}`,
const documentedDeepScan = documentedConfigs.find(
(config) =>
typeof config["deep_scan"] === "object" &&
config["deep_scan"] !== null &&
"stop_after_consecutive_errors" in config["deep_scan"],
);
expect(readme).toContain(`max_time_hours = ${defaults.maxTimeHours}`);
expect(documentedDeepScan).toMatchObject({
deep_scan: {
workers: "auto",
subagents: defaults.subagents,
stop_after_no_new: defaults.stopAfterNoNew,
stop_after_consecutive_errors: defaults.stopAfterConsecutiveErrors,
max_discovery_runs: defaults.maxDiscoveryRuns,
max_time_hours: defaults.maxTimeHours,
},
});
} finally {
await rm(root, { recursive: true, force: true });
}
Expand Down
Loading