Skip to content

Scan ancestry check makes 0.1.5 completely unusable on hosts where / is mode 0777 #212

Description

@sfdzhmr

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:

  1. 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;
}

  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions