Use a YAML or JSON file to reuse a scan scope, cost limit, or findings threshold.
Select the file with -c FILE (or --config FILE) on scan, bulk-scan,
scan-components, or info. The repository is a separate command argument.
Choose a file you trust. Native Codex settings in the file can start MCP servers and select where model requests go. In CI, keep the configuration in an operator-controlled location outside the checkout being scanned.
From your project root, run codex-security init to create codex-security.yaml.
To choose a different name, pass a path ending in .yaml, .yml, or .json.
The command refuses to overwrite an existing file.
For a repository with a src directory, save these settings in the file:
scan:
scope:
paths: [src]
policy:
fail_on_severity: highTo supply additional context, add knowledge_base: [SECURITY.md, docs/architecture.md]
under scan only if both files exist relative to the configuration file.
Use the paths to your own documents or leave the list out.
# Inspect the settings and where each one came from.
codex-security info -c codex-security.yaml --json
# Check the target, paths, and settings locally.
codex-security scan . -c codex-security.yaml --dry-run --jsonCheck that the preview shows your intended repository and scope. To start the scan with those settings, keep the same file selection:
codex-security scan . -c codex-security.yamlInstead of passing -c each time, you can set CODEX_SECURITY_PROJECT_CONFIG.
An explicit -c takes precedence. Without either, the CLI uses its defaults
even if the current directory contains a project file. SDK run() calls and
saved reruns do not discover files or read this environment variable.
All settings are optional; {} uses the existing defaults. No version field
is needed. Unknown wrapper keys and invalid types are errors. Values are literal:
the loader does not evaluate JavaScript, interpolate environment values, include remote files,
or merge multiple files. Wrapper null values do not reset settings. Project-file
keys use snake_case, matching native Codex configuration. Keys inside codex
keep their native spelling; names and values are not converted.
YAML anchors are supported; the parser retains its guard against excessive nested
alias expansion.
For Daybreak on Codex 0.163.0 or later, set codex.daybreak to true; the toggle
defaults off. This native setting is retained in scan recipes and passed to
Deep Scan discovery workers, including resumes. An explicit
scan.cyber_access_program takes precedence for each turn. See
Daybreak opt-in.
The YAML and JSON
examples both select this repository's sdk/typescript/src directory. From
the root of the Codex Security checkout with the CLI installed, run:
codex-security scan . -c docs/examples/codex-security.yaml --dry-run --jsonTo reuse either example in your own project, change scan.scope.paths to a
directory in the repository you will scan. Both editor schema hints point to
the schema in this checkout. For a copy at your project root with
@openai/codex-security installed locally, change the hint to
./node_modules/@openai/codex-security/schemas/project-config.schema.json.
You can also omit the hint. The CLI validates the file using its bundled schema.
Deep Scan setup accepts an explicitly selected .yaml, .yml, or .json project
file using the same format. For example:
scan:
mode: deep
max_time_minutes: 30
deep:
workers: 2
subagents_per_worker: 1
stop_after_no_new: 2
max_discovery_runs: 4Include the path to the file when asking Codex to start a Deep Scan. To check
what will run, ask for the effective worker count, subagents per worker, stop
conditions, review cap, and discovery time limit. The plugin returns each value
and its source in setup.deepScanConfig from inspect_codex_security_setup.
Both setup and start_codex_security_deep_scan accept projectConfigPath.
Explicit project settings override per-user [deep_scan] settings, which override
built-in defaults. The per-user file remains
$CODEX_HOME/codex-security/config.toml, or the existing
CODEX_SECURITY_DEEP_SCAN_CONFIG_PATH selection. The project selection does not
change that file or unrelated scans. No repository configuration is discovered or
activated automatically.
This integration supports scan.mode: deep, scan.max_time_minutes, and all
scan.deep fields listed below. Other configured fields, including codex,
auth, scope, context files, cost limits, output, and exit policy, are rejected
with their names before model work starts. Use CLI/SDK configuration for those
settings. If both minute and hour limits are present, they must agree.
Saving native setup freezes the effective settings and their sources. Headless scans freeze them when first started. Reconnecting or resuming with the returned scan ID uses that snapshot even if the selected file or global defaults change. The time limit bounds discovery; scans may stop sooner after novelty or review limits, and aggregation and reporting can continue afterward.
When the Codex host supplies the launching turn’s speed, Deep Scan uses it for
Standard scan workers, resumed workers, and finding comparisons. Selecting
Default overrides a faster tier in the worker’s Codex config. On hosts that omit the
turn’s speed, each worker uses the Codex configuration it loads. To keep a scan
at Default on those hosts, leave service_tier = "default" in the worker’s
config for the duration of the scan, including any resumes. Other scans that
load the same config also use that setting. The usual config file is
$CODEX_HOME/config.toml, or ~/.codex/config.toml when $CODEX_HOME is unset.
| Field | Meaning | Default |
|---|---|---|
auth |
Credential source: auto, chatgpt, or api-key; never a credential value |
auto |
scan.mode |
standard or deep |
standard |
scan.max_time_minutes |
Advisory Standard investigation allowance; enforced Deep discovery limit, in minutes | Unset in Standard; 5760 in Deep |
scan.cyber_access_program |
Experimental OpenAI program: standard, daybreak_blue, or daybreak_red |
Unset; preserve Codex defaults |
scan.scope |
Exactly one of paths: [src], diff: {base: HEAD}, or working_tree: {} |
Whole repository |
scan.knowledge_base |
Context files or directories | Empty list |
scan.instructions_file |
Additional scan instructions | Unset |
scan.validation_file |
Custom validation instructions; incompatible with active deep mode | Built-in validation |
scan.owner_suggestions |
Generate an owner-suggestions report after a committed Git scan | false |
scan.deep |
Deep discovery settings shown below | Existing deep defaults |
codex |
Native Codex settings and profiles | Existing isolated configuration |
limits.max_cost_usd_per_scan |
Estimated USD limit for one scan attempt | No limit |
policy.fail_on_severity |
Exit threshold: critical, high, medium, or low |
Report-only |
output.directory |
Artifact directory outside the scanned Git worktree | Existing private artifact location |
See owner suggestions for supported scan types, cost-limit behavior, and the saved report.
Patching, PR creation, publication, post-scan prompt files, and machine-specific plugin or Python selection remain explicit CLI/SDK inputs.
The SDK's ScanSettings type is shared by ScanOptions, CLI resolution, and
project-file resolution. Project files and typed ProjectConfigInput objects
use snake_case; SDK options keep camelCase, and CLI flags keep kebab-case.
The resolver maps file keys to the existing SDK options:
| Project file | SDK option | CLI flag |
|---|---|---|
auth |
auth |
--auth |
scan.mode |
mode |
--mode |
scan.cyber_access_program |
cyberAccessProgram |
--cyber-access-program (scan, scan-components) |
scan.scope.paths |
target: ["src"] |
--path |
scan.scope.diff |
target: DiffTarget.refs(...) |
--diff, --head |
scan.scope.working_tree |
target: DiffTarget.workingTree(...) |
--working-tree, --base |
scan.knowledge_base |
knowledgeBasePaths |
--knowledge-base |
scan.instructions_file |
scanPromptFile |
--scan-prompt-file |
scan.validation_file |
validationPromptFile |
--validation-prompt-file |
scan.owner_suggestions |
ownerSuggestions |
--owner-suggestions |
scan.deep.workers |
workers |
--workers |
scan.deep.subagents_per_worker |
subagents |
--subagents |
scan.deep.stop_after_no_new |
stopAfterNoNew |
--stop-after-no-new |
scan.deep.stop_after_consecutive_errors |
stopAfterConsecutiveErrors |
No flag |
scan.deep.max_discovery_runs |
maxDiscoveryRuns |
--max-discovery-runs |
scan.deep.max_time_hours |
maxTimeHours |
--max-time-hours |
scan.max_time_minutes |
maxTimeMinutes |
--max-time |
limits.max_cost_usd_per_scan |
maxCostUsd |
--max-cost |
policy.fail_on_severity |
failureSeverity |
--fail-on-severity |
output.directory |
outputDir |
--output-dir |
codex |
Constructor codexOverrides |
--codex, model/provider flags |
Use an explicit file with loadProjectConfig():
import { CodexSecurity, loadProjectConfig } from "@openai/codex-security";
const { config, options } = await loadProjectConfig("codex-security.yaml");
await using security = new CodexSecurity(config);
const result = await security.run(process.cwd(), options);
if (
options.failureSeverity !== undefined &&
result.hasFindingsAtOrAbove(options.failureSeverity)
) {
process.exitCode = 1;
}For configuration already in memory, pass the same structured object to
resolveProjectConfig():
import {
resolveProjectConfig,
type ProjectConfigInput,
} from "@openai/codex-security";
const input = {
scan: { mode: "deep", scope: { paths: ["src"] } },
limits: { max_cost_usd_per_scan: 5 },
} satisfies ProjectConfigInput;
const { config, options } = resolveProjectConfig(input, process.cwd());Both return constructor config, scan options, and an immutable sources map;
the file loader also returns
projectConfig path and source metadata. The optional directory argument defaults
to the current directory. It locates a selected file or anchors an in-memory
object's context, prompt, and output paths. Files anchor those paths to their own
directory. Neither helper reads prompt contents or prepares a runtime.
Resolved context, prompt, and output paths have the SDK's AbsolutePath type;
scope paths remain relative to the selected repository.
Use security.preflight(repository, options) for the same local checks as CLI
--dry-run, including active-mode compatibility and remaining legacy deep
defaults. To override a resolved value or attach a callback, pass
{ ...options, maxCostUsd: 10, onProgress } to run().
SDK callers can use inline scanPrompt, validationPrompt, and postScanPrompt,
or the corresponding *PromptFile options. Inline text wins without reading the
matching file. Direct SDK file paths resolve from the current directory and use
the CLI's existing file protections. Post-scan instructions remain explicit
SDK/CLI options and are not part of project files.
failureSeverity records the policy in the scan recipe. The SDK does not throw or
set process status when the threshold is met; call hasFindingsAtOrAbove() and
choose the caller's response. The method uses the same ordering as the CLI and
does not filter findings. The CLI/file contract keeps its four reportable levels;
existing SDK calls also accept informational. An unknown threshold throws,
including when the result has no findings.
Settings apply in this order: built-in defaults, applicable legacy deep settings, the project file, then explicitly supplied CLI values. Schema defaults are editor hints; parsing does not insert them. Lists are replaced, not concatenated.
| Path | Relative to |
|---|---|
| Repository positional argument | Invocation directory |
File scan.scope.paths |
Selected repository |
| File context, instruction, validation, and output paths | Configuration file's directory |
| CLI context, prompt, and output paths | Invocation directory |
Native values under codex |
Existing native Codex rules |
Scope paths follow scan --path: the same config can select src in each target
repository. Context, instruction, validation, and output paths belong to the config
file and therefore stay anchored to its directory when the invocation moves.
For example, --knowledge-base context.md replaces the file's entire context list
and resolves from the invocation directory. Existing regular-file, protected-path,
credential, and outside-worktree output checks still apply.
A CLI scope selector replaces the file's scope variant: --diff HEAD discards
configured paths, and --path src discards a configured diff. --head can refine a
file diff; --base can refine a file working-tree scope. Contradictory explicit
selectors fail. --no-working-tree disables a configured working-tree scope but
does not clear a path or committed-diff scope.
There is no general CLI reset for configured context, policy, cost limit, or scope.
Edit the file, select another file, or omit -c and unset
CODEX_SECURITY_PROJECT_CONFIG. An empty context list is valid.
Native objects merge using the existing configuration code. Duplicate native CLI
assignments remain errors; overriding a file value is valid. Selected native
profiles can still override root model/effort values, including convenience flags.
--provider openai retains its existing behavior and does not clear a native
provider selected by the file.
scan:
mode: deep
max_time_minutes: 5760
deep:
workers: 4
subagents_per_worker: 3
stop_after_no_new: 4
stop_after_consecutive_errors: 3
max_discovery_runs: 40
limits:
max_cost_usd_per_scan: 10These deep settings show the existing defaults, shared with the Python plugin.
The cost limit is illustrative. Legacy user [deep_scan] TOML remains supported,
including workers = "auto". File and CLI values override individual settings.
All six effective values are resolved before runtime preparation and saved in new
recipes.
If a runtime aliases the ambient TOML file, only explicit overrides are written
back; unrelated sections and inherited defaults remain untouched. Isolated runtime
files still receive a complete snapshot.
A valid deep block can stay inactive in standard mode. Explicit deep CLI options still require deep mode. Deep diff scans and custom validation remain unsupported. Counts retain their existing bounds; zero subagents is valid, and discovery time cannot exceed 96 hours.
scan.max_time_minutes accepts positive fractional minutes: an optional advisory
allowance in Standard, or the enforced discovery limit in Deep (default and
maximum: 5760 minutes). See investigation time
for lifecycle semantics. scan.deep.max_time_hours and legacy TOML
deep_scan.max_time_hours remain deprecated Deep-only aliases.
Explicit CLI values override project values across units; project values override legacy TOML. Only the winning source's duration is used: conflicting minute/hour aliases there are errors, while equivalent durations are accepted.
max_cost_usd_per_scan has the same meaning as --max-cost: an estimated limit for one
scan attempt. In-flight work may exceed it. It is not a total budget for a batch or
follow-up actions. fail_on_severity changes the exit status without filtering the
retained findings.
codex-security bulk-scan repositories.csv -c codex-security.yaml --output-dir ../batch-results
codex-security scan-components . --component src -c codex-security.yaml --output-dir ../component-resultsoutput.directory can supply the batch output directory; --output-dir overrides
it. In the interactive bulk wizard, it supplies the proposed output directory.
Both commands use the same config precedence, path anchoring, native settings,
context, prompts, per-attempt cost and investigation limits, and severity policy
as scan. The investigation allowance applies independently to each scan, not
planning, result matching, or total campaign time.
A bulk CSV row's mode and scope override the file's defaults for that repository.
Bulk checkouts are clean, shallow snapshots, so bulk scans accept repository or
path scopes. A configured diff or working-tree scope is rejected before work
starts unless every affected row supplies its own CSV path scope. Deep settings
apply only to deep rows. Component plans select each component's
scope, overriding scan.scope; scan.mode selects standard or deep component
scans. Batch --workers controls concurrent repositories or components, while
scan.deep.workers controls discovery workers within each deep scan.
The severity policy returns exit 1 without discarding completed results or
retrying a scan just because it found issues. Execution failures take precedence
with exit 2. Bulk resume retains the saved policy outcome and checks
that the selected scan configuration still matches the campaign.
info [-c FILE] --json reports resolved settings and their sources without a scan
target, prompt-file reads, credentials checks, or runtime initialization. With no
file, it shows defaults. Active deep mode also resolves legacy TOML. It reports
effective model details and native key sources without dumping raw native values.
This is configuration inspection,
not a filesystem, target, or model-availability check.
scan ... --dry-run --json checks local inputs without starting Codex, verifying
credentials, or establishing model availability. Removing --dry-run starts a
scan and may incur model charges. Deep preflight shows all six effective settings
and deepScanSources, including when no project file was selected; invalid
applicable legacy settings now fail before runtime startup.
With -c, the output also includes projectConfig.path, per-setting sources,
selected instruction/validation file paths, and the finding policy. Native sources
identify which layer supplied a key; profile selection still determines the
effective model and effort. Raw native configuration and credentials are not dumped.
Source paths use the project-file spelling, such as
scan.deep.stop_after_no_new; existing output properties such as deepScanSources
keep their names.
The source map includes all wrapper settings, including unset values attributed
to default; resolving preflight sources does not mutate previously returned maps.
Help, version, and command schema output do not load project files. Missing,
malformed, or invalid selected files exit 2. Scan exit codes remain 0 for
completion without a policy failure, 1 for the finding threshold, and 2 for
execution or required-output failures. Signal cancellation retains 130 for
SIGINT and 143 for SIGTERM.
The generated project schema is self-contained Draft-07 and ships in the npm package. A project-root YAML file can use:
# yaml-language-server: $schema=./node_modules/@openai/codex-security/schemas/project-config.schema.json
{}JSON files can use a root $schema string with the same relative path. The CLI
uses its bundled schema and never fetches the hint. Validation does not coerce
values, insert defaults, or discard unknown wrapper keys. Completion inside
codex covers common model/provider fields; other native JSON settings retain
existing checks. CLI scan --schema --json and result artifact schemas remain
separate contracts.
New recipes retain resolved native configuration, scope, auth choice, finding policy, cost limit, context paths, and all active deep settings. Reruns do not load the project file again. Complete saved deep settings do not use the current legacy file; older partial recipes continue using applicable defaults for missing values.
Recipes do not snapshot source or context contents. Reruns use the current checkout
and current context files. Reruns require replacement scan instructions when the
original scan used them: a new recipe records that requirement, and scans rerun
refuses to omit them silently.
Use scans rerun [SCAN_ID] --scan-prompt-file FILE to supply them again. A required
replacement must not be empty. The file resolves from the invocation directory.
Custom validation keeps its existing scans rerun --validation-prompt-file FILE
requirement.
scans resume SCAN_ID and eligible bulk-scan --recover attempts continue the
original session with its saved instructions, authentication choice, and deep
settings. They do not reload the project file or require replacement instructions.
Bulk recovery still checks the selected configuration against the campaign manifest
and applies its severity policy to recovered findings.
Workflow identity records explicitly requested deep settings, not ambient values or shipped defaults, so changing those defaults does not prevent resumption. Changing the explicit request still requires a different workflow ID.