fix(project): guard --password-file reads on create/update - #302
Conversation
…e#79) `project create` and `project update` resolved `--password-file` with a bare `readFileSync(path, 'utf8').trim()`. A path typo — the expected failure mode for a hand-typed flag — escaped as an unhandled Node exception: - exit `1` (generic) instead of `5` (validation) - an `--output json` payload whose `error` is a bare string, not the `{ code, message, nextAction }` envelope the rest of the CLI emits, so anything parsing `--output json` breaks - the absolute path and errno leaked to stderr Add `readSecretFileGuarded` in `src/lib/secret-file.ts`, mirroring `readCodeFileGuarded` in `src/commands/test.ts`, and route both call sites through it. Missing paths, permission failures, and directories now produce the standard VALIDATION_ERROR envelope naming the flag. The helper takes the flag name so the remaining unguarded file flags (`--credential-file`, `--client-secret-file`, `--refresh-token-file`, and `project auto-auth --password-file`) can adopt it under TestSprite#282 without rework — those sites are deliberately left untouched here to avoid colliding with that issue's in-progress work. The payload cap from `readCodeFileGuarded` is not carried over: secrets are small, and a size ceiling would be a behaviour change on a shipped flag rather than part of fixing the crash. Dry-run behaviour is unchanged — both paths already return before password resolution, and the existing P7 coverage still passes.
WalkthroughThe change adds guarded password-file reading, converts filesystem failures into ChangesPassword-file validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
zeshi-du
left a comment
There was a problem hiding this comment.
Approving and merging. This is the right shape: one guarded reader in src/lib/secret-file.ts rather than a try/catch at each call site, mapped onto the same VALIDATION_ERROR envelope as the existing readCodeFileGuarded, with the directory case rejected up front (so EISDIR-vs-empty-read platform divergence can't leak through) and a UTF-8 BOM stripped for files written by PowerShell's default Set-Content -Encoding utf8. The rationale comments are doing real work — thank you for writing them.
Two follow-ups, neither blocking this merge:
-
@OkeyAmy — #283 covers the
--credential-file/ auto-auth flags. Please rebase onto this and callreadSecretFileGuarded()instead of inlining the guard; this helper was written with exactly those flags as the intended next migration, and its own docstring says so. (Your Windows unit test is also failing now that CI has actually run — details on that PR.) -
The size-cap question stays open and I'll record the decision on #58 rather than settle it in two different places: this PR deliberately omits a cap, #283 proposes
PAYLOAD_TOO_LARGE. Whichever way it goes, it belongs in the shared helper so both flag families behave identically.
Sorry this sat 8 days fully green with no review — that was on us, not you.
Closes #79
Scoped per @zeshi-du's 2026-07-18 status correction: the
--sincehalf landed in #27, and this PR takes the--password-filehalf that is still live onmain.The defect
project createandproject updateresolve--password-filewith a bare read:A path typo is the expected failure mode for a hand-typed flag, but it escapes as an unhandled Node exception rather than the CLI's typed validation error.
Before (
origin/main@ fe07bc9):Three things are wrong: exit
1instead of5, anerrorthat is a bare string rather than the{ code, message, nextAction }envelope every other command emits (so--output jsonconsumers break), and the absolute path plus errno leaked to stderr.After (this branch):
A directory argument (
--password-file /tmp) previously crashed withEISDIR; it now returns the same envelope withnot a regular file.The change
New
src/lib/secret-file.tsexportingreadSecretFileGuarded(flag, path), mirroringreadCodeFileGuardedinsrc/commands/test.ts—statSyncfirst, mapENOENT/EACCES/EPERM/EISDIRand the not-a-regular-file case ontolocalValidationError, then read. Both call sites (project.ts:213,project.ts:329) route through it.Errors report the path as typed, not the resolved absolute path, so no directory layout leaks into output.
Scope: deliberately not touching
project auto-authproject.ts:579(project auto-auth --password-file) is the third bare site, but it belongs to #282 along with--credential-file,--client-secret-file, and--refresh-token-file— that issue is assigned and in progress. Fixing it here would collide.Instead the helper takes the flag name as its first argument, so #282 can adopt it directly:
That is the "split-out password-file guard" @zeshi-du pointed at in #248, available as a reusable unit rather than tangled with that PR's pagination work.
Two judgement calls
readCodeFileGuardedenforcesMAX_INLINE_CODE_BYTES. Secrets are small, and adding a size ceiling to a shipped flag is a behaviour change rather than part of fixing a crash. Happy to add one if you want the parity.U+FEFFis ECMAScript whitespace, so the existing.trim()already removes a BOM written by PowerShell 5.1's defaultSet-Content -Encoding utf8. There is a regression test pinning that, so it cannot silently regress if the trim is ever refactored.Dry-run
Unchanged — both commands already return before password resolution, and the existing
P7 — dry-run with --password-file does not read the filesystemtest still passes.Tests
15 new tests: 11 unit (
src/lib/secret-file.test.ts) covering happy path, trimming, BOM, interior whitespace, relative-path resolution, empty file, missing file (code + exit + message + caller-supplied flag name + no absolute-path leak), and the directory case; 4 command-level (src/commands/project.test.ts) assertingrunCreate/runUpdatereject withVALIDATION_ERRORexit 5 before any network call, thatnextActionnames the flag, and that a valid file is still read and sent.Summary by CodeRabbit