Summary
Since 0.1.5, Codex Security refuses to run at all on shared/container Linux hosts where the root directory / is world-writable without the sticky bit (e.g. drwxrwxrwx). Any scan output path fails because the new ancestry check walks all ancestors up to /, and every absolute path necessarily contains /.
- 0.1.4: works fine.
- 0.1.5: every scan command aborts, both in the Node-side prepare step and in the Python-side workbench save step.
Environment
- Linux shared host (no root / no sudo available to the user)
- / is drwxrwxrwx root root (mode 0777, no sticky bit) — machine-administered configuration, out of the user's control
- codex-security 0.1.5 (installed globally)
- Reproduction does not depend on the chosen output directory (default, /tmp/..., $HOME/..., etc. all fail)
Reproduction
npm install -g @openai/codex-security # 0.1.5
export CODEX_SECURITY_STATE_DIR=/tmp/codex-security-state
codex-security scan . --dry-run # also fails for a real scan
Observed output:
[00:00] Preparing scan
Scan output parent must not be group- or world-writable without the sticky bit: /
And during a real scan, after the Node-side check is bypassed, the Python-side check fires:
Could not save the Codex Security scan: Scan output parent must not be group- or world-writable without the sticky bit.
Partial output was kept at /tmp/codex-security-state/scans/...
Root cause
Two checks walk the full ancestor chain up to the filesystem root:
- Node side — requireSecureOutputAncestry in dist/runtime.js:
// walks: dirname(resolve(outputDir)) → realpath → up to "/"
while (true) {
...
requireTrustedOutputAncestor(metadata, current, effectiveUid);
const parent = dirname(current);
if (parent === current) return;
current = parent;
}
- Python side — require_canonical_scan_directory in _bundled_plugin/scripts/workbench_db.py:
for parent in scan_dir.parents:
...
if (
stat.S_IMODE(parent_metadata.st_mode) & 0o022
and not parent_metadata.st_mode & stat.S_ISVTX
):
raise SystemExit(
"Scan output parent must not be group- or world-writable without the sticky bit."
)
Path.parents ends at /. When / is 0777 without the sticky bit, every possible output directory fails. There is no configuration switch to opt out, so the tool is fully unusable on such hosts.
Expected behavior
The threat model behind this check (see #109: scan output under a shared parent can be replaced before completion) is about user-controlled or shared parents where another unprivileged user could create/replace the output tree. System directories owned by root (notably / itself) do not fit that threat model and are also not fixable by an unprivileged user.
Please consider one of:
- Skip ancestors owned by uid 0 (root) — e.g. stop the walk at the first root-owned ancestor;
- Restrict the check to the direct parent chain that is not root-owned;
- Provide an explicit opt-out (env var or config key) for hosts with unusual filesystem permissions, with a documented security note.
Summary
Since 0.1.5, Codex Security refuses to run at all on shared/container Linux hosts where the root directory / is world-writable without the sticky bit (e.g. drwxrwxrwx). Any scan output path fails because the new ancestry check walks all ancestors up to /, and every absolute path necessarily contains /.
Environment
Reproduction
npm install -g @openai/codex-security # 0.1.5
export CODEX_SECURITY_STATE_DIR=/tmp/codex-security-state
codex-security scan . --dry-run # also fails for a real scan
Observed output:
[00:00] Preparing scan
Scan output parent must not be group- or world-writable without the sticky bit: /
And during a real scan, after the Node-side check is bypassed, the Python-side check fires:
Could not save the Codex Security scan: Scan output parent must not be group- or world-writable without the sticky bit.
Partial output was kept at /tmp/codex-security-state/scans/...
Root cause
Two checks walk the full ancestor chain up to the filesystem root:
// walks: dirname(resolve(outputDir)) → realpath → up to "/"
while (true) {
...
requireTrustedOutputAncestor(metadata, current, effectiveUid);
const parent = dirname(current);
if (parent === current) return;
current = parent;
}
for parent in scan_dir.parents:
...
if (
stat.S_IMODE(parent_metadata.st_mode) & 0o022
and not parent_metadata.st_mode & stat.S_ISVTX
):
raise SystemExit(
"Scan output parent must not be group- or world-writable without the sticky bit."
)
Path.parents ends at /. When / is 0777 without the sticky bit, every possible output directory fails. There is no configuration switch to opt out, so the tool is fully unusable on such hosts.
Expected behavior
The threat model behind this check (see #109: scan output under a shared parent can be replaced before completion) is about user-controlled or shared parents where another unprivileged user could create/replace the output tree. System directories owned by root (notably / itself) do not fit that threat model and are also not fixable by an unprivileged user.
Please consider one of: