Skip to content

Commit 4da57f3

Browse files
committed
merge: resolve main conflicts and address package review
2 parents a3d1fea + dd74cdb commit 4da57f3

32 files changed

Lines changed: 3666 additions & 352 deletions

.github/workflows/container-ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ on:
2525
permissions:
2626
contents: read
2727

28+
concurrency:
29+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
30+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
31+
2832
jobs:
2933
container:
3034
name: linux-amd64
@@ -96,7 +100,8 @@ jobs:
96100
mkdir -p results state
97101
chmod 700 results state
98102
printf 'id,repository,revision\n' > repositories.csv
99-
export CODEX_SECURITY_USER="$(id -u):$(id -g)"
103+
CODEX_SECURITY_USER="$(id -u):$(id -g)"
104+
export CODEX_SECURITY_USER
100105
docker compose config --quiet
101106
docker compose run --rm codex-security --version
102107

.github/workflows/node-ci.yml

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,56 @@ on:
88
permissions:
99
contents: read
1010

11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
13+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
14+
1115
jobs:
1216
test:
1317
name: ${{ matrix.os }} / node-${{ matrix.node }}
1418
runs-on: ${{ matrix.os }}
19+
timeout-minutes: 20
1520
strategy:
1621
fail-fast: false
1722
matrix:
1823
os: [ubuntu-latest, macos-latest, windows-latest]
19-
node: ["22"]
24+
node: ["22.13.0"]
2025
include:
2126
- os: ubuntu-latest
2227
node: "24"
28+
- os: ubuntu-latest
29+
node: "26"
2330

2431
steps:
2532
- name: Checkout repository
2633
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2734
with:
2835
persist-credentials: false
2936

37+
- name: Set up pnpm
38+
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
39+
with:
40+
package_json_file: sdk/typescript/package.json
41+
cache: true
42+
cache_dependency_path: sdk/typescript/pnpm-lock.yaml
43+
3044
- name: Set up Node.js
3145
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
3246
with:
3347
node-version: ${{ matrix.node }}
48+
cache: npm
49+
cache-dependency-path: sdk/typescript/pnpm-lock.yaml
3450

3551
- name: Set up Bun
3652
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
3753
with:
3854
bun-version: "1.3.14"
3955

40-
- name: Enable pnpm
41-
shell: bash
42-
run: corepack enable && corepack prepare "$(node -p 'require("./sdk/typescript/package.json").packageManager')" --activate
43-
4456
- name: Install dependencies
4557
run: pnpm --dir sdk/typescript install --frozen-lockfile
4658

4759
- name: Audit production dependencies
48-
if: matrix.os == 'ubuntu-latest' && matrix.node == '22'
60+
if: matrix.os == 'ubuntu-latest' && matrix.node == '22.13.0'
4961
run: pnpm --dir sdk/typescript run audit:prod
5062

5163
- name: Typecheck
@@ -73,3 +85,18 @@ jobs:
7385
working-directory: sdk/typescript
7486
shell: bash
7587
run: pnpm run check:package ../../dist/*.tgz
88+
89+
- name: Smoke-test Node.js runtime
90+
working-directory: sdk/typescript
91+
shell: bash
92+
run: |
93+
set -euo pipefail
94+
node --input-type=module --eval '
95+
import { CodexSecurity } from "@openai/codex-security";
96+
97+
if (typeof CodexSecurity !== "function") {
98+
throw new Error("The SDK does not export CodexSecurity.");
99+
}
100+
'
101+
node bin/codex-security.mjs --version
102+
node bin/codex-security.mjs --help

.github/workflows/node-release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ jobs:
6767
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
6868
with:
6969
bun-version: "1.3.14"
70+
no-cache: true
7071

7172
- name: Enable pnpm
7273
shell: bash

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
`@openai/codex-security` is a CLI and TypeScript SDK for finding, validating, and fixing security vulnerabilities in your code.
44

5-
**See the [Codex Security Documentation](http://learn.chatgpt.com/docs/security/cli)** for more details.
5+
**See the [Codex Security documentation](https://learn.chatgpt.com/docs/security/cli)** for more details.
66

77
> Note: for best results, we recommend that your account is verified for [Trusted Access](https://chatgpt.com/cyber).
88
99
## Quick start
1010

11-
Requires Node.js 22.13.0 or later, Python 3.10 or later, and access to Codex Security.
11+
Requires Node.js 22.13.0 or later in the 22.x release line, Node.js 24.x, or
12+
Node.js 26.x; Python 3.10 or later; and access to Codex Security.
1213

1314
```bash
1415
npm install @openai/codex-security
@@ -17,7 +18,13 @@ npx @openai/codex-security scan .
1718
npx @openai/codex-security scan . --model gpt-5.6-terra --effort high
1819
```
1920

20-
For CI, set `OPENAI_API_KEY` instead of signing in.
21+
For CI, set `OPENAI_API_KEY` or `CODEX_API_KEY` instead of signing in. Environment API keys are
22+
passed directly to the current scan and are never stored in Codex's credential
23+
home or system keyring.
24+
25+
Local sign-in honors Codex's configured credential backend, including a system
26+
keyring required by a managed device. Codex Security keeps login and scan
27+
credentials in the same private, persistent state directory.
2128

2229
If both a ChatGPT sign-in and an API key are available, interactive scans ask
2330
which credential to use. CI and other noninteractive scans keep the existing
@@ -51,4 +58,7 @@ console.log(result.reportPath);
5158
await security.close();
5259
```
5360

54-
For installation, authentication, scan options, and CI setup, see the [official documentation](http://learn.chatgpt.com/docs/security/cli).
61+
For complete command help, runtime defaults, native multi-agent worker limits,
62+
environment variables, deep-scan configuration, and SDK options, see the
63+
[package README](sdk/typescript/README.md) and the
64+
[official CLI reference](https://learn.chatgpt.com/docs/security/cli/reference).

sdk/typescript/README.md

Lines changed: 175 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ npx @openai/codex-security --version
1616
```
1717

1818
The package supports macOS, Linux, and Windows and requires Node.js 22.13.0 or
19-
later. Scanning and exporting findings also require Python 3.10 or later. If
20-
you use Python 3.10, install the `tomli` package. Select another interpreter
21-
with `--python`, `pythonPath`, or `PYTHON` when needed.
19+
later in the 22.x release line, Node.js 24.x, or Node.js 26.x. Scanning and
20+
exporting findings also require Python 3.10 or later. If you use Python 3.10,
21+
install the `tomli` package. Select another interpreter with `--python`,
22+
`pythonPath`, or `PYTHON` when needed.
2223

2324
When a newer version is available, the CLI shows the update command for your
2425
installation method. Set `CODEX_SECURITY_NO_UPDATE_NOTICE=1` to hide the
@@ -56,6 +57,39 @@ Results can contain source excerpts, vulnerability details, and reproduction
5657
steps. Keep result directories and saved reports outside the repository and
5758
limit access to authorized reviewers.
5859

60+
### SDK configuration and scan options
61+
62+
Pass runtime configuration to the `CodexSecurity` constructor:
63+
64+
| Option | Description |
65+
| ---------------- | --------------------------------------------------------------------------- |
66+
| `pluginPath` | Use a Codex Security plugin directory or ZIP instead of the bundled plugin. |
67+
| `pythonPath` | Select the Python interpreter before consulting `PYTHON`. |
68+
| `codexOverrides` | Deep-merge supported settings into the isolated Codex configuration. |
69+
70+
Pass scan configuration to `security.run(repository, options)` or
71+
`security.preflight(repository, options)`:
72+
73+
| Option | Description |
74+
| ----------------------- | ------------------------------------------------------------------------------------- |
75+
| `auth` | Select `"auto"`, `"chatgpt"`, or `"api-key"`. |
76+
| `target` | Select a repository, repository-relative paths, committed diff, or working-tree diff. |
77+
| `mode` | Select `"standard"` or `"deep"`; deep mode supports repositories and paths. |
78+
| `knowledgeBasePaths` | Add architecture documents, security policies, threat models, or directories. |
79+
| `outputDir` | Choose an artifact directory outside the enclosing Git worktree. |
80+
| `archiveExisting` | Archive results already in `outputDir` before starting a scan. |
81+
| `maxCostUsd` | Stop after the estimated model cost exceeds a positive USD amount. |
82+
| `failureSeverity` | Record a finding-severity policy in the saved scan recipe. |
83+
| `parentScanId` | Link a rerun to an existing parent scan. |
84+
| `expectedPluginVersion` | Require the original plugin version when replaying a scan. |
85+
| `signal` | Cancel a scan with an `AbortSignal`. |
86+
87+
Progress and lifecycle callbacks are `onAuthentication`, `onCost`,
88+
`onOutputArchived`, `onOutputDirReady`, `onScanStarted`, `onReconnect`,
89+
`onWorkerStatus`, `onWarning`, and `onObserverError`. Preflight does not start
90+
the runtime, authenticate, resolve Python, inspect the plugin, or run those
91+
scan-lifecycle callbacks.
92+
5993
## Authentication
6094

6195
For local use, sign in with ChatGPT:
@@ -78,17 +112,31 @@ pass it on stdin:
78112
printenv OPENAI_API_KEY | npx @openai/codex-security login --with-api-key
79113
```
80114

115+
Environment API keys are supplied directly to the current scan and are never
116+
saved to the Codex credential home or system keyring. Only an explicit
117+
`login --with-api-key` stores an API key.
118+
119+
To pass a Codex access token explicitly, use
120+
`login --with-access-token` and provide the token on stdin. An access token
121+
environment variable is not automatically used as a scan API key.
122+
81123
On Windows, set the API key in PowerShell:
82124

83125
```powershell
84126
$env:OPENAI_API_KEY = "<your-api-key>"
85127
npx @openai/codex-security scan C:\code\repository
86128
```
87129

88-
Check or remove the stored sign-in with `npx @openai/codex-security login status` and
89-
`npx @openai/codex-security logout`. Codex Security reuses an existing file-based Codex
90-
sign-in. If Codex stores credentials in the system keyring, run
91-
`npx @openai/codex-security login` once before scanning.
130+
Check or remove the stored sign-in with `npx @openai/codex-security login status`
131+
and `npx @openai/codex-security logout`. Codex Security keeps its sign-in in a
132+
private, stable Codex home at `$CODEX_SECURITY_STATE_DIR/codex-home`, or at
133+
`$CODEX_HOME/state/plugins/codex-security/codex-home` when no state directory is
134+
configured. Login, status, logout, and scans use the same home. Codex manages
135+
credentials using its configured file or system-keyring backend and honors
136+
managed-device policies. An existing file-based Codex sign-in is imported only
137+
when the dedicated home does not already contain stored credentials. Logging
138+
out prevents later scans from automatically reimporting that ambient sign-in
139+
until you explicitly log in again.
92140

93141
An environment API key takes precedence over a stored sign-in by default.
94142
When both a stored ChatGPT sign-in and an environment API key are available, an
@@ -197,9 +245,127 @@ the implied provider. Use `--model gpt-5.6-terra` to switch models and
197245
`--codex KEY=VALUE` for other Codex settings; existing
198246
`--codex 'model_reasoning_effort="high"'` overrides remain supported.
199247

248+
### Runtime configuration and worker limits
249+
250+
The standalone CLI and SDK do not load an unrelated user or repository Codex
251+
configuration. Each scan starts with a private runtime and these Codex
252+
defaults:
253+
254+
```toml
255+
cli_auth_credentials_store = "file"
256+
model = "gpt-5.6-sol"
257+
model_reasoning_effort = "xhigh"
258+
259+
[features]
260+
plugins = true
261+
goals = true
262+
263+
[features.multi_agent_v2]
264+
enabled = true
265+
max_concurrent_threads_per_session = 9
266+
267+
[windows]
268+
sandbox = "unelevated"
269+
```
270+
271+
Use `--model` and `--effort` for model selection. Repeat
272+
`--codex KEY=VALUE` to deep-merge other TOML values into this isolated
273+
configuration:
274+
275+
```bash
276+
npx @openai/codex-security scan . \
277+
--model gpt-5.6-terra \
278+
--effort high \
279+
--codex features.multi_agent_v2.max_concurrent_threads_per_session=4
280+
```
281+
282+
The session thread limit includes the parent agent: the default of `9`
283+
provides up to eight delegated worker slots. This limit is separate from
284+
`bulk-scan --workers`, which controls how many repositories run concurrently.
285+
A configured limit is a maximum, not evidence that every worker started.
286+
287+
Quote string values as TOML, for example
288+
`--codex 'model_reasoning_effort="high"'`. Do not pass both `--model` and
289+
`--codex 'model="..."'`, or both `--effort` and
290+
`--codex 'model_reasoning_effort="..."'`: conflicting or repeated keys are
291+
rejected.
292+
293+
Plugin and marketplace loading belong to Codex Security. Overrides of
294+
`plugins`, `marketplaces`, or `features.plugins`, including profile-specific
295+
plugin overrides, are rejected; choose `--plugin-path` instead. Native
296+
multi-agent v2 must remain enabled. The legacy `agents.max_threads` setting
297+
and `features.multi_agent_v2.enabled=false` are incompatible and rejected.
298+
`validate` and `patch` accept `--effort` and only the `model` and
299+
`model_reasoning_effort` `--codex` keys; they do not accept general scan
300+
runtime overrides.
301+
200302
These overrides do not change the scan's approval policy or filesystem
201303
permissions. See [Local security model](#local-security-model).
202304

305+
### Deep-scan engine configuration
306+
307+
When the bundled plugin runs in a normal Codex host, its repeated-discovery
308+
engine reads `$CODEX_HOME/codex-security/config.toml`, defaulting to
309+
`~/.codex/codex-security/config.toml`:
310+
311+
```toml
312+
[deep_scan]
313+
workers = "auto"
314+
subagents = 3
315+
stop_after_no_new = 6
316+
max_discovery_runs = 60
317+
```
318+
319+
`workers = "auto"` uses half the available parallelism, with a minimum of one
320+
and a maximum of six discovery workers. Set `workers` to a positive integer to
321+
choose an explicit count. `subagents` must be a nonnegative integer;
322+
`stop_after_no_new` and `max_discovery_runs` must be positive integers. Unknown
323+
`[deep_scan]` keys are rejected.
324+
325+
These settings are separate from Codex's
326+
`features.multi_agent_v2.max_concurrent_threads_per_session` and
327+
`bulk-scan --workers`. Importantly, standalone CLI and SDK scans create an
328+
isolated `CODEX_HOME` and do not import the ambient deep-scan configuration
329+
file. Consequently, `scan --mode deep` currently uses the deep engine's
330+
defaults; there are no standalone CLI flags for these four settings. Use
331+
`--codex` to adjust the Codex session thread limit, not to set `[deep_scan]`
332+
values.
333+
334+
### Environment variables
335+
336+
The CLI and SDK recognize the following user-configurable environment:
337+
338+
| Variable | Effect |
339+
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
340+
| `OPENAI_API_KEY`, `CODEX_API_KEY` | Scan authentication; `OPENAI_API_KEY` wins when both are present. |
341+
| `CODEX_SECURITY_STATE_DIR` | Override the private scan-history, workbench, and default artifact directory. |
342+
| `CODEX_HOME` | Set the ambient Codex home for file-backed sign-in and default state; defaults to `~/.codex`. |
343+
| `PYTHON` | Select a Python interpreter when `--python` or SDK `pythonPath` is not set. |
344+
| `GH_HOST` | Select a GitHub Enterprise host during interactive `bulk-scan` discovery. |
345+
| `CODEX_SECURITY_NO_UPDATE_NOTICE`, `NO_UPDATE_NOTIFIER` | Disable interactive update notices when either variable is defined. |
346+
| `CODEX_SECURITY_NPM_REGISTRY`, `npm_config_registry`, `NPM_CONFIG_REGISTRY` | Select the update-check registry, in the listed precedence order. |
347+
| `CI` | Disable interactive update notices in automated environments. |
348+
| `NO_COLOR`, `TERM` | Disable colored scan-history output when `NO_COLOR` is defined or `TERM=dumb`. |
349+
350+
Interpreter discovery uses `--python` or `pythonPath` first, then `PYTHON`,
351+
the managed Codex runtime, and finally `python3` or `python` from `PATH`.
352+
`CODEX_SECURITY_STATE_DIR` takes precedence over `CODEX_HOME`; keep both
353+
state and result paths outside the scanned repository.
354+
355+
The repository's Docker Compose workflow additionally recognizes
356+
`CODEX_SECURITY_IMAGE`, `CODEX_SECURITY_USER`, `CODEX_SECURITY_SECCOMP`,
357+
`CODEX_SECURITY_CSV`, `CODEX_SECURITY_RESULTS`, and `CODEX_SECURITY_STATE` for
358+
its image, runtime user, seccomp profile, input, results, and state mounts.
359+
Provide `GH_TOKEN` or `GITHUB_TOKEN` for private GitHub checkouts and
360+
`CODEX_SECURITY_GIT_HOST` for a GitHub Enterprise host in the container.
361+
These container settings are distinct from standalone CLI flags and
362+
interactive discovery's `GH_HOST`.
363+
364+
Variables such as `CODEX_SECURITY_SCAN_ID`, `CODEX_SECURITY_SCAN_DIR`,
365+
`CODEX_SECURITY_PLUGIN_ROOT`, `CODEX_SECURITY_CONFIG_PATH`, and
366+
`CODEX_SECURITY_TARGET_PATHS_FILE` are generated by an active scan. They are
367+
internal runtime data, not supported user configuration.
368+
203369
Scan progress identifies the requested paths and reports actual ranking,
204370
file-review, validation, and attack-path phases as they become available.
205371
Completion summarizes findings, severity, coverage, elapsed time, available
@@ -244,7 +410,8 @@ Results remain under `--output-dir`; rerun the same command to resume.
244410
`npx @openai/codex-security scans list` lists scans for the current repository. Pass a
245411
repository path to inspect another checkout, `--scan-root DIR` to list scans
246412
whose artifacts are under a particular root. `scans show SCAN_ID` includes the
247-
scan configuration, results, coverage, and artifact locations.
413+
scan configuration, results, coverage, and artifact locations. Add
414+
`--show-linked-findings` to include finding links from previous scans.
248415

249416
Every scan history command accepts a full scan ID or a unique prefix of at
250417
least eight characters.

sdk/typescript/_bundled_plugin/references/scan-contract.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ A sealed manifest records the completed timestamp and hashes for the canonical d
3131
Choose the target kind based on the reviewed content, not the scan invocation:
3232
`git_worktree` for a checked-out Git workspace, `directory_snapshot` for a non-Git directory, `git_diff` for a Git-backed change set, and `git_revision` for an exact immutable Git tree.
3333

34+
For a workbench-backed scan, use the recorded target contract instead of inferring the kind from the checkout. A clean Git checkout has `allowedKinds: ["git_revision"]`: use its recorded revision and omit `snapshotDigest`. A dirty checkout has `allowedKinds: ["git_worktree"]`: copy `requiredSnapshotDigest` exactly.
35+
3436
| Kind | Required snapshot fields |
3537
| --- | --- |
3638
| `git_revision` | `revision` |

sdk/typescript/_bundled_plugin/scripts/workbench_cli.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ def parse_args(description: str) -> argparse.Namespace:
210210
update_progress.add_argument("--deep-review-pass", type=positive_int)
211211
update_progress.add_argument("--claim-token")
212212

213+
prepare_scan_completion = subparsers.add_parser("prepare-scan-completion")
214+
prepare_scan_completion.add_argument("--scan-id", required=True)
215+
prepare_scan_completion.add_argument("--claim-token")
216+
213217
complete_scan = subparsers.add_parser("complete-scan")
214218
complete_scan.add_argument("--scan-id", required=True)
215219
complete_scan.add_argument("--claim-token")

0 commit comments

Comments
 (0)