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:
The failures are:
runtime directories and plugin Python boundary > validates explicit output directories and creates private temporary paths
canonical scan contract > accepts a scan directory beneath a symlinked parent
CLI authentication > keeps delegated credentials in the configured Codex home
CodexSecurity orchestration > preflights local inputs without initializing runtime or credentials
CodexSecurity orchestration > rejects output inside normal and linked Git worktrees before runtime initialization
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.
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 umask0002, six POSIX tests create directories without an explicit mode. Those fixtures become0775, then correctly fail the SDK's group-writable ancestor checks.Changing only the umask to
0022makes 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
7.0.0-28-genericv24.18.01.3.13mainata8fc00984b07f10f5b607a9521c2f05aa57c51070002A simple probe confirms the behavior:
Reproduction
From
sdk/typescriptafter installing the locked dependencies:npx -y bun@1.3.13 test --timeout 30000 --silent ./tests-tsWith the account's default
0002umask:The failures are:
runtime directories and plugin Python boundary > validates explicit output directories and creates private temporary pathscanonical scan contract > accepts a scan directory beneath a symlinked parentCLI authentication > keeps delegated credentials in the configured Codex homeCodexSecurity orchestration > preflights local inputs without initializing runtime or credentialsCodexSecurity orchestration > rejects output inside normal and linked Git worktrees before runtime initializationCodexSecurity orchestration > keeps a private preflight snapshot isolated from persistent credentialsRepresentative error:
The focused runtime test shows the same A/B result:
Finally, the complete suite passes when only the umask changes:
Root cause
Several fixtures that are meant to represent trusted/private parents use
mkdir(...)ormkdir(..., { recursive: true })without a mode. For example, the symlink-parent runtime test createscanonical-parentthis way before askingprepareOutputDirto accept a child beneath it.Under
0002, the parent is0775.requireSecureOutputAncestrythen 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
mode: 0o700(and usechmodwhere an existing directory may ignore the requested creation mode).0777/01777fixtures that exercise shared-parent rejection and sticky-parent acceptance.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.