Repository navigation
fix(project): credential/auto-auth file flags bypass the file-read guard (raw ENOENT, exit 1, malformed --output json) #282
Description
Summary
Four file-reading flags added in v0.4.0 on project credential and project auto-auth read their file with a raw readFileSync(path, 'utf8').trim() and no guard. When the file is missing (or is a directory), the CLI throws an unwrapped Node error instead of the structured validation error every other file flag produces:
- exit
1(generic) instead of5(validation) - a malformed
--output jsonenvelope —erroris a bare string, not the{ code, message, nextAction }object the rest of the CLI emits, so anything parsing--output jsonbreaks - it leaks raw fs internals (absolute path + errno)
- it also crashes under
--dry-run, which is documented not to touch the filesystem
Affected flags
| Command | Flag | Site |
|---|---|---|
project credential |
--credential-file |
src/commands/project.ts:470 |
project auto-auth |
--password-file |
src/commands/project.ts:579 |
project auto-auth |
--client-secret-file |
src/commands/project.ts:583 |
project auto-auth |
--refresh-token-file |
src/commands/project.ts:588 |
All six use the same unguarded pattern:
credential = readFileSync(opts.credentialFile, 'utf8').trim();Reproduction
The contrast is the bug — the same class of user error (bad input to a file flag) gets two different contracts:
# Guarded flag (correct): structured envelope, exit 5
$ testsprite test create --project x --type backend --name n --code-file /nope.py --output json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request.",
"nextAction": "Flag `--code-file` is invalid: file does not exist: /nope.py.",
"requestId": "local",
"details": { "field": "code-file", ... }
}
}
# exit 5
# Unguarded flag (bug): bare-string envelope, exit 1, leaks fs internals
$ testsprite project credential <project-id> --type "API key" --credential-file /nope --output json
{
"error": "ENOENT: no such file or directory, open '/nope'"
}
# exit 1--dry-run does not help — it reads the file (and crashes) before the dry-run banner:
$ testsprite project credential <project-id> --type "API key" --credential-file /nope --dry-run
Error: ENOENT: no such file or directory, open '/nope'
# exit 1A directory argument surfaces a raw EISDIR the same way.
Why this is a gap and not already covered
src/commands/test.tsalready guards all of its file flags (--code-file,--plan-from,--plans,--steps) — each returns a cleanVALIDATION_ERROR/ exit 5 with afielddetail. That is the reference behavior.- PR Recovered: fix: bound pagination and guard password file reads (#61 by @merlinsantiago982-cmd) #248 adds
readPasswordFileGuarded(), but it is hardcoded to--password-fileand only wired intoproject create/project update(project.ts:213,:329). The four flags above are not covered —credential/auto-authdid not exist when that fix was written, so they survive its merge. - A repo-wide
readFileSyncsweep confirms these four are the only remaining unguarded user-flag file reads.
Suggested fix
Generalize the guard into a small helper and apply it to all six project.ts file-read sites (the two --password-file sites plus the four new ones):
function readSecretFileGuarded(path: string, flagName: string): string {
// statSync -> not-found / not-a-file / too-large => VALIDATION_ERROR (exit 5),
// field: flagName, requestId: 'local' — mirrors readPasswordFileGuarded (#248)
// and the --code-file guard in test.ts.
}Happy to sequence this however you prefer — fold it into #248, or land it right after so the helper covers --password-file too.
Environment: reproduced on current main (v0.4.0) against the production API, Node 20.
discord: okey_amy
Activity
OkeyAmy commentedon Jul 24, 2026
/assign
testsprite-hob commentedon Jul 24, 2026
Assigned to @OkeyAmy. Thanks for taking this on. (2 more slots available.)
Your open assigned issues (1/3):
(none)
fix(project): guard --password-file reads on create/update (#79) (#302)
fix(project): guard credential/auto-auth secret file reads (TestSprit…
fix(project): cap --password-file size before reading it (TestSprite#58)
fix(project): cap --password-file size before reading it (TestSprite#58)
zeshi-du commentedon Sep 16, 2026
Confirmed and tracked internally as DEV-882 — the secret-file flags (
--credential-file,--password-file,--client-secret-file,--refresh-token-file) bypass the guarded-read contract, so a filesystem error surfaces raw instead of as a typedVALIDATION_ERROR, and--output jsonemits malformed output on that path.@OkeyAmy's PR #283 is the fix. Two notes there: it needs a rebase onto current
main, and the size-cap part should be dropped — that specific question was decided on #58 after your last push, and the answer was no cap on secret-file flags. The rest of the PR is exactly what we want. Sorry for the three-week gap.fix(project): guard credential/auto-auth secret file reads (TestSprit…
Merge branch 'main' into fix/project-secret-file-guard