Skip to content
Open
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
56 changes: 52 additions & 4 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,59 @@

set -eu

if [ "${1:-}" = bulk-scan ]; then
case "${2:-}" in
--help|-h)
bulk_scan_command=
bulk_scan_input=
bulk_scan_metadata=
expects_option_value=

for argument do
case "$argument" in
--help|-h|--llms|--llms-full|--schema|--version)
bulk_scan_metadata=yes
continue
Comment thread
mldangelo-oai marked this conversation as resolved.
;;
esac

if [ "$expects_option_value" = yes ]; then
if [ "$argument" = -- ] && [ "$bulk_scan_command" = yes ]; then
printf '%s\n' 'codex-security: bulk-scan does not support the -- option terminator.' >&2
exit 2
fi
expects_option_value=
continue
fi

case "$argument" in
--)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entrypoint accepts bulk-scan --output-dir out -- repositories.csv, but the pinned incur parser rejects -- with Unknown flag: --. A filename like --help or --schema is treated as metadata instead of input. The tests miss this because their fake CLI only echoes arguments. Handle -- in the real parser and add an end-to-end test, or remove this unsupported branch.

if [ "$bulk_scan_command" = yes ]; then
printf '%s\n' 'codex-security: bulk-scan does not support the -- option terminator.' >&2
exit 2
fi
break
;;
bulk-scan)
bulk_scan_command=yes
;;
""|-*)
--output-dir|--workers|--mode|--model|--effort|--provider|\
--knowledge-base|--max-attempts|--plugin-path|--python|\
--codex|--filter-output|--format|\
--token-limit|--token-offset)
expects_option_value=yes
;;
-*)
;;
*)
if [ "$bulk_scan_command" != yes ]; then
break
fi
bulk_scan_input=$argument
;;
esac
done

if [ "$bulk_scan_command" = yes ] && [ "$bulk_scan_metadata" != yes ]; then
case "$bulk_scan_input" in
"")
printf '%s\n' 'codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.' >&2
exit 2
;;
Expand Down
10 changes: 10 additions & 0 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ npx @openai/codex-security scan /path/to/repository --mode deep --workers 2 --su
npx @openai/codex-security install-hook
npx @openai/codex-security bulk-scan
npx @openai/codex-security bulk-scan --model gpt-5.6-terra --effort high
npx @openai/codex-security bulk-scan --workers 4 --mode deep --max-attempts 3
npx @openai/codex-security bulk-scan repositories.csv --output-dir /path/outside/repositories/security-scans --workers 4 --knowledge-base /path/to/threat-models --knowledge-base /path/to/architecture.pdf
npx @openai/codex-security scans list /path/to/repository
npx @openai/codex-security scans list --scan-root /path/outside/repository/results
Expand Down Expand Up @@ -466,6 +467,11 @@ Private checkouts reuse your GitHub CLI sign-in without changing your global Git
configuration. The selected repositories are saved to
`<output-dir>/repositories.csv` for review or resumption.

Interactive discovery accepts the same `--workers`, `--mode`, `--max-attempts`,
`--model`, `--effort`, `--plugin-path`, `--python`, and `--codex` settings as
CSV-driven scans. It prompts for the output directory; `--output-dir` is only
valid when a repository CSV is supplied.

To use an existing repository list or run in CI, pass a CSV with required `id`,
`repository`, and `revision` columns. Revisions must be full commit hashes;
optional `scope` and `mode` columns narrow individual scans:
Expand Down Expand Up @@ -615,6 +621,10 @@ device login remains in `state/`. For unattended scans, set `OPENAI_API_KEY`
or `CODEX_API_KEY` instead. Set `GH_TOKEN` or `GITHUB_TOKEN` for private
GitHub repositories.

The container accepts the repository CSV before or after bulk-scan options.
Interactive repository discovery remains disabled, including when global CLI
options appear before `bulk-scan`.

On Ubuntu hosts that restrict unprivileged user namespaces, an administrator
can install the optional, narrowly scoped AppArmor profile once:

Expand Down
27 changes: 2 additions & 25 deletions sdk/typescript/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1381,32 +1381,9 @@ export async function main(
let outputDir: string;
let githubHost: string | undefined;
if (args.input === undefined) {
let optionIndex = 1;
while (optionIndex < argv.length) {
const argument = argv[optionIndex]!;
if (
argument === "--model" ||
argument === "--effort" ||
argument === "--provider" ||
argument === "--codex" ||
argument === "--knowledge-base"
) {
optionIndex += 2;
} else if (
argument.startsWith("--model=") ||
argument.startsWith("--effort=") ||
argument.startsWith("--provider=") ||
argument.startsWith("--codex=") ||
argument.startsWith("--knowledge-base=")
) {
optionIndex += 1;
} else {
break;
}
}
if (argv[0] !== "bulk-scan" || optionIndex !== argv.length) {
if (options.outputDir !== undefined) {
throw new Error(
"Run 'codex-security bulk-scan [--provider PROVIDER] [--model MODEL] [--effort EFFORT] [--codex KEY=VALUE] [--knowledge-base PATH]' to discover repositories, or provide a CSV and --output-dir.",
"--output-dir can only be used with a repository CSV; omit it to choose an output directory interactively.",
);
}
const wizard = await runBulkScanWizard(
Expand Down
19 changes: 19 additions & 0 deletions sdk/typescript/tests-ts/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,10 @@ describe("CLI", () => {
["bulk-scan", "--codex", 'model_reasoning_effort="high"'],
["bulk-scan", '--codex=model_reasoning_effort="high"'],
["bulk-scan", "--model", "gpt-5.6-terra", "--effort", "high"],
["bulk-scan", "--workers", "8", "--mode", "deep"],
["bulk-scan", "--max-attempts=3", "--plugin-path", "./plugin"],
["bulk-scan", "--python=python3"],
["--format", "toon", "bulk-scan", "--workers", "8"],
["bulk-scan", "--knowledge-base", "/shared/threat-models"],
[
"bulk-scan",
Expand Down Expand Up @@ -941,6 +945,21 @@ describe("CLI", () => {
expect(stdout.text()).toBe("");
});

test("rejects an output directory without a repository CSV", async () => {
const stderr = capture();
expect(
await main(
["bulk-scan", "--output-dir", "results"],
capture().stream,
stderr.stream,
dependencies(),
),
).toBe(2);
expect(stderr.text()).toContain(
"--output-dir can only be used with a repository CSV",
);
});

test("exposes only typed, read-only SDK metadata over MCP", () => {
const child = spawnSync(
process.execPath,
Expand Down
57 changes: 51 additions & 6 deletions sdk/typescript/tests-ts/container-entrypoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,33 @@ describe("customer container entrypoint", () => {
},
);

testPosix("accepts CSVs after global and bulk-scan options", async () => {
for (const arguments_ of [
["bulk-scan", "--workers", "2", "/input/repositories.csv"],
[
"--format",
"toon",
"bulk-scan",
"--output-dir=/output",
"/input/repositories.csv",
],
] as const) {
const result = await runEntrypoint(arguments_);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toBe(
[
...arguments_,
...(appArmorRestrictsUserNamespaces &&
!usesCodexSecurityAppArmorProfile
? ["--codex", "features.use_legacy_landlock=true"]
: []),
"",
].join("\n"),
);
}
});

testPosix(
"preserves bulk-scan help without injecting scan configuration",
async () => {
Expand Down Expand Up @@ -229,15 +256,33 @@ describe("customer container entrypoint", () => {
testPosix(
"rejects interactive discovery before starting the CLI",
async () => {
const result = await runEntrypoint(["bulk-scan"]);
for (const arguments_ of [
["bulk-scan"],
["bulk-scan", "--workers", "8"],
["--format", "json", "bulk-scan", "--mode", "deep"],
] as const) {
const result = await runEntrypoint(arguments_);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"interactive discovery is not supported",
);
}
},
);

testPosix("rejects unsupported bulk-scan option terminators", async () => {
for (const arguments_ of [
["bulk-scan", "--output-dir", "/output", "--", "--help"],
["bulk-scan", "--output-dir", "--", "--help"],
] as const) {
const result = await runEntrypoint(arguments_);
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr).toBe(
"codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.\n",
expect(result.stderr).toContain(
"does not support the -- option terminator",
);
},
);
}
});

testPosix("does not change non-scan commands", async () => {
const result = await runEntrypoint(["--version"]);
Expand Down
Loading