Skip to content

fix(project): guard --password-file reads on create/update - #302

Open
naufalfx805-source wants to merge 1 commit into
TestSprite:mainfrom
naufalfx805-source:fix/issue-79-password-file-guard
Open

fix(project): guard --password-file reads on create/update#302
naufalfx805-source wants to merge 1 commit into
TestSprite:mainfrom
naufalfx805-source:fix/issue-79-password-file-guard

Conversation

@naufalfx805-source

@naufalfx805-source naufalfx805-source commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #79

Scoped per @zeshi-du's 2026-07-18 status correction: the --since half landed in #27, and this PR takes the --password-file half that is still live on main.

The defect

project create and project update resolve --password-file with a bare read:

password = readFileSync(opts.passwordFile, 'utf8').trim();

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):

$ testsprite project create --type backend --name Guarded \
    --password-file /nope/missing.txt --output json
{
  "error": "ENOENT: no such file or directory, open '/nope/missing.txt'"
}
$ echo $?
1

Three things are wrong: exit 1 instead of 5, an error that is a bare string rather than the { code, message, nextAction } envelope every other command emits (so --output json consumers break), and the absolute path plus errno leaked to stderr.

After (this branch):

$ testsprite project create --type backend --name Guarded \
    --password-file /nope/missing.txt --output json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request.",
    "nextAction": "Flag `--password-file` is invalid: file does not exist: /nope/missing.txt.",
    "requestId": "local",
    "details": {
      "field": "password-file",
      "reason": "file does not exist: /nope/missing.txt"
    }
  }
}
$ echo $?
5

A directory argument (--password-file /tmp) previously crashed with EISDIR; it now returns the same envelope with not a regular file.

The change

New src/lib/secret-file.ts exporting readSecretFileGuarded(flag, path), mirroring readCodeFileGuarded in src/commands/test.tsstatSync first, map ENOENT / EACCES / EPERM / EISDIR and the not-a-regular-file case onto localValidationError, 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-auth

project.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:

credential = readSecretFileGuarded('credential-file', opts.credentialFile);

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

  • No payload cap. readCodeFileGuarded enforces MAX_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.
  • No explicit BOM strip. U+FEFF is ECMAScript whitespace, so the existing .trim() already removes a BOM written by PowerShell 5.1's default Set-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 filesystem test 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) asserting runCreate / runUpdate reject with VALIDATION_ERROR exit 5 before any network call, that nextAction names the flag, and that a valid file is still read and sent.

npm test        → 59 files, 2043 passed | 2 skipped, 0 failing
npm run typecheck → clean
npx eslint      → clean
npx prettier --check → clean

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of password files during project creation and updates.
    • Missing, inaccessible, empty, or invalid password files now produce clear validation errors before any network request.
    • Password files are validated as regular files, with paths resolved consistently.
    • Existing valid password files continue to be read and submitted correctly.

…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.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds guarded password-file reading, converts filesystem failures into VALIDATION_ERROR results, and applies the reader to project creation and update. Tests cover valid files, missing files, directories, content normalization, and prevention of network requests.

Changes

Password-file validation

Layer / File(s) Summary
Guarded secret-file reader
src/lib/secret-file.ts, src/lib/secret-file.test.ts
Adds readSecretFileGuarded with path resolution, regular-file validation, UTF-8 trimming, BOM removal, and typed filesystem error handling.
Project command integration
src/commands/project.ts, src/commands/project.test.ts
Project create and update use guarded password-file reads. Tests verify validation before network access and successful password submission.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: zeshi-du

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully addresses issue #79's --password-file requirement by implementing readSecretFileGuarded to return VALIDATION_ERROR (exit code 5) for missing/invalid files.
Out of Scope Changes check ✅ Passed All changes focus on guarding --password-file reads; the PR explicitly excludes project auto-auth and other credential-file flags as out of scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the guarded --password-file handling added for project create and update.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

[Hackathon] invalid --since and missing --password-file crash instead of returning VALIDATION_ERROR

1 participant