Summary
The container entrypoint decides whether a repository CSV was supplied by looking only at $2. Because the CLI accepts options before positionals, any invocation that puts a flag first is rejected with a message stating the CSV is missing — even when it is present.
|
if [ "${1:-}" = bulk-scan ]; then |
|
case "${2:-}" in |
|
--help|-h) |
|
;; |
|
""|-*) |
|
printf '%s\n' 'codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.' >&2 |
|
exit 2 |
|
;; |
|
esac |
|
fi |
if [ "${1:-}" = bulk-scan ]; then
case "${2:-}" in
--help|-h) ;;
""|-*)
printf '%s\n' 'codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.' >&2
exit 2 ;;
esac
fi
--output-dir matches -*, so the guard fires regardless of what appears later in the argument list.
Affected version and environment
- Confirmed on current
main at f22d4a36f26d16287bcdfd707b369116e02a08c3 (docker/entrypoint.sh is unchanged from 0.1.1)
- macOS 26.5.2 (build 25F84),
/bin/sh
- Node.js v24.5.0
Steps to reproduce
First, that the CLI itself accepts either order — both invocations parse the positional CSV and fail on the same later validation:
$ codex-security bulk-scan --workers 8 /tmp/definitely-missing.csv
codex-security: --output-dir is required with a repository CSV.
$ codex-security bulk-scan /tmp/definitely-missing.csv --workers 8
codex-security: --output-dir is required with a repository CSV.
Then the entrypoint, run with a stub codex-security on PATH so the final exec is observable:
1) CSV first:
exit=0 out=STUB codex-security received: bulk-scan /input/repositories.csv --output-dir /output
2) flag first:
exit=2 err=codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.
3) flag only:
exit=2 err=codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.
4) no arguments:
exit=2 err=codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.
5) --help:
exit=0 out=STUB codex-security received: bulk-scan --help
Cases 3 and 4 are the intended behavior. Case 2 is the defect: a CSV was supplied and the container refuses it.
Expected behavior
The guard should reject only genuinely CSV-less invocations, which are the ones that would fall back to interactive discovery. An invocation that includes a positional CSV anywhere should be passed through.
Actual behavior
$ docker run --rm \
-v ./repositories.csv:/input/repositories.csv:ro \
-v ./results:/output \
codex-security bulk-scan --output-dir /output --workers 8 /input/repositories.csv
codex-security: bulk-scan requires a repository CSV; interactive discovery is not supported in this image.
Exit code 2, and the stated reason is factually wrong.
Impact
Low severity but high confusion: the diagnostic contradicts what the user typed, and the ordering constraint is not documented in README.md or anywhere else. compose.yaml happens to place the CSV first, and container-ci.yml only exercises the bare bulk-scan form, so nothing in the repository covers flag-before-positional.
Suggested direction
Scan all arguments for a non-option token rather than inspecting $2, for example:
if [ "${1:-}" = bulk-scan ]; then
shift
csv=
for argument in "$@"; do
case "$argument" in
--help|-h) csv=help; break ;;
-*) ;;
*) csv=${csv:-yes} ;;
esac
done
[ -n "$csv" ] || { printf '%s\n' 'codex-security: ...' >&2; exit 2; }
fi
Note this needs care with options that take a separate value (--workers 8 would otherwise let 8 count as the CSV), so matching on a .csv suffix or on the known option list would be more robust than the sketch above.
Summary
The container entrypoint decides whether a repository CSV was supplied by looking only at
$2. Because the CLI accepts options before positionals, any invocation that puts a flag first is rejected with a message stating the CSV is missing — even when it is present.codex-security/docker/entrypoint.sh
Lines 5 to 14 in f22d4a3
--output-dirmatches-*, so the guard fires regardless of what appears later in the argument list.Affected version and environment
mainatf22d4a36f26d16287bcdfd707b369116e02a08c3(docker/entrypoint.shis unchanged from0.1.1)/bin/shSteps to reproduce
First, that the CLI itself accepts either order — both invocations parse the positional CSV and fail on the same later validation:
Then the entrypoint, run with a stub
codex-securityonPATHso the finalexecis observable:Cases 3 and 4 are the intended behavior. Case 2 is the defect: a CSV was supplied and the container refuses it.
Expected behavior
The guard should reject only genuinely CSV-less invocations, which are the ones that would fall back to interactive discovery. An invocation that includes a positional CSV anywhere should be passed through.
Actual behavior
Exit code 2, and the stated reason is factually wrong.
Impact
Low severity but high confusion: the diagnostic contradicts what the user typed, and the ordering constraint is not documented in
README.mdor anywhere else.compose.yamlhappens to place the CSV first, andcontainer-ci.ymlonly exercises the barebulk-scanform, so nothing in the repository covers flag-before-positional.Suggested direction
Scan all arguments for a non-option token rather than inspecting
$2, for example:Note this needs care with options that take a separate value (
--workers 8would otherwise let8count as the CSV), so matching on a.csvsuffix or on the known option list would be more robust than the sketch above.