Skip to content

Harden --prompt-file against paths outside the working directory#289

Open
lohengrin332 wants to merge 1 commit into
openai:mainfrom
lohengrin332:harden-prompt-file-containment
Open

Harden --prompt-file against paths outside the working directory#289
lohengrin332 wants to merge 1 commit into
openai:mainfrom
lohengrin332:harden-prompt-file-containment

Conversation

@lohengrin332
Copy link
Copy Markdown

@lohengrin332 lohengrin332 commented May 2, 2026

This is a defense-in-depth hardening PR, not a CVE. There is no remotely exploitable bug here in normal use of the plugin.

The behavior I'm changing: codex-companion.mjs task --prompt-file <path> currently does fs.readFileSync(path.resolve(cwd, value), "utf8") with no containment check. The file's contents are then forwarded to OpenAI as the Codex task prompt.

Why I think it's worth tightening: under prompt-injection-adjacent threat models — where Claude itself might be steered into composing slash command arguments — a path like --prompt-file ~/.ssh/id_rsa or a symlinked sibling file would exfiltrate user secrets to OpenAI. Constraining the resolved path (after symlink resolution) to live under cwd removes that capability without affecting any documented or supported usage.

Changes:

  • Extract readTaskPrompt to lib/task-prompt.mjs so it can be unit-tested directly. (Open to inlining instead if you'd prefer fewer files.)
  • Resolve symlinks via fs.realpathSync before the containment check.
  • Throw a clear error when the resolved path lies outside realpath(cwd).
  • Add tests/task-prompt.test.mjs with coverage for: happy paths (relative + absolute inside cwd), relative escape, absolute outside, symlink-out-of-cwd, and the no-prompt-file positional fallback.

No changes to documented behavior or any pre-existing test. Happy to adjust the error message format, the file-vs-inline factoring, or the exact set of cases covered.

Previously, `--prompt-file <path>` accepted any user-readable file
on the filesystem, including symlinks pointing outside cwd. The file
contents are forwarded to OpenAI as a Codex task prompt, so a
mistakenly or maliciously composed `--prompt-file ~/.ssh/id_rsa`
would exfiltrate that file. This is not exploitable in normal
single-user CLI use, but it is a defense-in-depth gap under
prompt-injection-adjacent threat models where Claude itself might
compose hostile arguments.

Resolve symlinks via realpathSync and reject any --prompt-file that
does not live under the realpath of cwd. Add coverage for relative
escapes, absolute paths outside cwd, and symlinks that point outside.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lohengrin332 lohengrin332 requested a review from a team May 2, 2026 20:40
fireblue added a commit to fireblue/codex-plugin-cc that referenced this pull request May 17, 2026
…penai#289, openai#290)

- Reject --prompt-file paths that escape the working directory
- Use --end-of-options before user-controlled refs in all git invocations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tgkim-openerd pushed a commit to opnd-io/opnd-codex-plugin that referenced this pull request May 27, 2026
…ai#190 / openai#289 / openai#290 / openai#314

본 fork 의 upstream 8 HIGH cherry-pick 후보 중 Tier 1 Group B/C/D 6 PR 의
manual port. Group A (openai#24 + openai#311, parser robustness) 는 직전 commit fbf45c8
이 cover. `git cherry-pick` 직접 불가 (fork v2.0.0/v2.1.0 hardening 과 같은
파일 같은 영역 변경으로 dry-run 시 CONFLICT 확정) — PR diff 의 intent 를
v2.1.0 base 위에 재구현.

audit 출처: docs/upstream-tracking/2026-05-18-upstream-backlog-audit.md
Tier 1 Group B/C/D. upstream issue openai#49 (per-turn watchdog) + openai#183 partial
(finalizing only) + openai#250 partial (MCP elicitation no per-tool timeout) 자동
해결.

## Group B — per-turn watchdog (upstream PR openai#312, openai#302)

- `lib/codex.mjs`: 신규 `TurnWatchdogError` (export, exit code 124 ~
  timeout(1)), 신규 helpers `armWatchdog/disarmWatchdog/kickWatchdog`,
  `createTurnCaptureState` 에 `watchdogTimer` + `watchdogMs` field 추가.
  `captureTurn` 의 notification handler 가 매 message 마다 kick 하고,
  `runAppServerTurn` 이 `options.watchdogMs` 또는 env
  `CODEX_TURN_WATCHDOG_MS` 를 forward. 미설정 시 모든 helper no-op (opt-in).
- `codex-companion.mjs`: import 에 `TurnWatchdogError`, main `catch` 가
  `instanceof` 분기로 structured JSON line emit + exit 124. 일반 error
  경로 보존.
- `tests/turn-watchdog.test.mjs` 신규 — error class contract 2 + source
  level guard 2 (총 4 case).
- PR openai#302 (captureTurn wall-clock) 효과는 openai#312 의 inactivity watchdog 가
  super-set으로 cover (per-notification kick 가 stuck turn 검출, wall-clock
  은 long valid turn 도 잘림).

## Group C — security hardening (PR openai#190 + openai#289 + openai#290)

- **openai#190 child env sanitization** — `lib/app-server.mjs` 의
  `buildPluginCodexEnv` 가 `result = { ...baseEnv }` 로 시작해 전체 env
  전달. 신규 `sanitizePluginCodexEnv(baseEnv)` 가 inject vectors
  (`BASH_FUNC_*`, `BASH_ENV`, `ENV`, `PROMPT_COMMAND`, `LD_PRELOAD`,
  `LD_AUDIT`, `LD_LIBRARY_PATH`, `DYLD_INSERT_LIBRARIES`,
  `DYLD_LIBRARY_PATH`, `DYLD_FALLBACK_LIBRARY_PATH`, `NODE_OPTIONS`) 제거 +
  `CLAUDE_*` (단 plugin 자체 의존 `CLAUDE_PLUGIN_ROOT/CLAUDE_PLUGIN_DATA`
  보존) 제거. 사용자 의도된 보존은 `CODEX_PLUGIN_PRESERVE_ENV=...` opt-in
  comma list 로.
- **openai#289 prompt-file path containment** — `codex-companion.mjs:readTaskPrompt`
  의 `path.resolve(cwd, options["prompt-file"])` 가 path traversal 가능.
  fork user-trust 모델 보존 위해 default: cwd outside 시 stderr warn +
  계속 진행. opt-in strict: `CODEX_PLUGIN_PROMPT_FILE_STRICT=1` 시 throw.
- **openai#290 git ref `--end-of-options`** — `lib/git.mjs` 의 5 사이트
  (`buildBranchComparison` + `collectBranchContext`의 4 git invocation)
  user-controlled ref / commitRange 직접 전달 → dash-prefixed value flag
  injection 위험. 모두 `--end-of-options` 추가하여 git argv parser 의
  flag 해석 차단.

## Group D — adversarial prompt cap (PR openai#314)

- `codex-companion.mjs:buildAdversarialReviewPrompt` 에 신규 helper
  `truncateToUtf8Bytes(text, maxBytes)` (UTF-8 continuation byte 검사로
  safe cut) 추가. REVIEW_INPUT 800KB 기본 cap (Codex API 1MB 한도 대비
  safe margin). 초과 시 truncation notice 명시 + stderr warn. opt-out
  `CODEX_PLUGIN_REVIEW_PROMPT_MAX_BYTES` env.

## 검증

- `node --test tests/jsonl.test.mjs tests/turn-watchdog.test.mjs
  tests/sandbox-default-omit.test.mjs tests/finalizing-timeout.test.mjs
  tests/commands.test.mjs` — 43/43 pass
- `npm run build` exit=0 — pre-existing TS error 2건 (codex.mjs:375 +
  state.mjs:142) 본 cycle 의 typedef 갱신 영향으로 자동 해결
tgkim-openerd added a commit to opnd-io/opnd-codex-plugin that referenced this pull request May 27, 2026
tgkim-openerd pushed a commit to opnd-io/opnd-codex-plugin that referenced this pull request May 27, 2026
The plugin CHANGELOG's `## Unreleased` block had entries only for the
session's first task (review-sandbox / status-watch / log-tail). PR #3-#8
landed substantial changes with no changelog record. /doc-sync adds them:

- PR #8 rename codex-plugin-cc -> opnd-codex-plugin (BREAKING invocation:
  /codex:* -> /opnd-codex:*)
- PR #3 upstream Tier 1 8-HIGH manual port (openai#312/openai#190/openai#289/openai#290/openai#314/openai#24/openai#311)
- PR #4 pair/capsule/output-profile/task-key workstream + ARCH-002/SEC-001
- PR #6 pair-readiness fixes A1-A4
- PR #7 upstream backlog openai#59/openai#113/openai#238/openai#75

Docs only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant