Skip to content

POSIX test suite fails under umask 0002 because secure fixtures inherit mode 0775 #207

Description

@pasmud

Summary

The TypeScript test suite depends on the process umask being at least as restrictive as 0022. On an Ubuntu desktop account configured with the common collaborative umask 0002, six POSIX tests create directories without an explicit mode. Those fixtures become 0775, then correctly fail the SDK's group-writable ancestor checks.

Changing only the umask to 0022 makes the full suite pass. I don't think the production ancestry check should be relaxed; the affected test fixtures should create the private/trusted directories they intend to test with an explicit mode.

Environment

  • Ubuntu 26.04 LTS, x86_64
  • Kernel: 7.0.0-28-generic
  • Node.js: v24.18.0
  • Bun: 1.3.13
  • Repository: current main at a8fc00984b07f10f5b607a9521c2f05aa57c5107
  • Shell/process umask: 0002

A simple probe confirms the behavior:

$ umask
0002

$ mkdir "$private_temp/default"
$ stat -c '%a %n' "$private_temp" "$private_temp/default"
700 /tmp/codex-security-mode-probe...
775 /tmp/codex-security-mode-probe.../default

Reproduction

From sdk/typescript after installing the locked dependencies:

npx -y bun@1.3.13 test --timeout 30000 --silent ./tests-ts

With the account's default 0002 umask:

712 pass
4 skip
6 fail

The failures are:

  1. runtime directories and plugin Python boundary > validates explicit output directories and creates private temporary paths
  2. canonical scan contract > accepts a scan directory beneath a symlinked parent
  3. CLI authentication > keeps delegated credentials in the configured Codex home
  4. CodexSecurity orchestration > preflights local inputs without initializing runtime or credentials
  5. CodexSecurity orchestration > rejects output inside normal and linked Git worktrees before runtime initialization
  6. CodexSecurity orchestration > keeps a private preflight snapshot isolated from persistent credentials

Representative error:

OutputDirectoryError: Scan output parent must not be group- or world-writable without the sticky bit:
.../canonical-parent

The focused runtime test shows the same A/B result:

# Fails
(umask 0002; npx -y bun@1.3.13 test --timeout 30000   ./tests-ts/runtime.test.ts   --test-name-pattern 'validates explicit output directories and creates private temporary paths')

# Passes
(umask 0022; npx -y bun@1.3.13 test --timeout 30000   ./tests-ts/runtime.test.ts   --test-name-pattern 'validates explicit output directories and creates private temporary paths')

Finally, the complete suite passes when only the umask changes:

$ (umask 0022; npx -y bun@1.3.13 test --timeout 30000 --silent ./tests-ts)

718 pass
4 skip
0 fail

Root cause

Several fixtures that are meant to represent trusted/private parents use mkdir(...) or mkdir(..., { recursive: true }) without a mode. For example, the symlink-parent runtime test creates canonical-parent this way before asking prepareOutputDir to accept a child beneath it.

Under 0002, the parent is 0775. requireSecureOutputAncestry then rejects it exactly as designed. Similar implicit-mode fixture setup appears in the contract, CLI-authentication, and API tests listed above.

This looks distinct from #109/#157 and PR #118: those establish and implement the shared-parent security invariant. This report is about tests accidentally violating that invariant based on the developer's ambient umask.

Expected behavior

The suite should pass on supported POSIX systems regardless of whether the invoking account uses 0002, 0022, or a more restrictive umask.

Suggested fix

  • Give fixtures that are intended to be private/trusted an explicit mode: 0o700 (and use chmod where an existing directory may ignore the requested creation mode).
  • Preserve the existing explicit 0777/01777 fixtures that exercise shared-parent rejection and sticky-parent acceptance.
  • Add a focused CI regression that runs the relevant POSIX tests under umask 0002, or run the suite once with that umask.

This keeps the security boundary intact while making the tests deterministic across normal Linux account configurations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions