Repository navigation
[Hackathon] fix(config): bypass unused credential files when key and endpoint are explicit #536
Description
Verified against
988c0355bb05631e9c6c1785e443937ff2a88c0e (current main), @testsprite/testsprite-cli0.14.0, Linux, Node22.23.3.
Contact
Not on Discord. Please contact @walter-w-white on this GitHub issue or PR.
What is broken
Ordinary commands still require ~/.testsprite/credentials to be readable when both the API key and endpoint are supplied explicitly. An unreadable leftover file makes project list, auth status, and test get exit1 before sending any request. A directory at the unused credentials pathname has the same effect. Neither complete environment variables nor an environment key plus --endpoint-url can bypass it.
DOCUMENTATION.md says the key/URL environment variables override the file, “so CI can run without ever touching ~/.testsprite/credentials.” The current doctor recovery also tells env-key users “commands still work.” On the same fixture, doctor reaches the API and exits0 while an immediate ordinary command fails locally.
Reproduction
This minimal, offline reproduction from a built repository uses a disposable directory instead of any real credential file. It also reproduces as root because it needs no permission denial:
node --input-type=module <<'JS'
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { loadConfig } from './dist/lib/config.js';
const temporary = mkdtempSync(join(tmpdir(), 'testsprite-env-override-'));
const credentialsPath = join(temporary, 'credentials');
const env = {
TESTSPRITE_API_KEY: 'sk-user-fixture-not-real',
TESTSPRITE_API_URL: 'http://127.0.0.1:19191',
};
try {
// Normal control: the complete pair works with no credentials file.
console.log(loadConfig({ env, credentialsPath }));
mkdirSync(credentialsPath);
// Expected: the same explicit configuration. Actual: EISDIR.
console.log(loadConfig({ env, credentialsPath }));
} finally {
rmSync(temporary, { recursive: true, force: true });
}
JSI also ran the unchanged CLI entry point against strict loopback HTTP fixtures, with a synthetic homedir and no production services. Permission cases ran with CapEff=CapBnd=0; an independent native read proved EACCES before the CLI check. The final matrix has25scenarios/30CLI processes; a separate six-call native reproducer confirms the minimal workflow:
| Same explicit key and API URL | Command | Exit | HTTP requests |
|---|---|---|---|
| Missing credentials file | project list --output json |
0 | 1 |
| Existing file, mode000 | project list --output json |
1 (EACCES) |
0 |
| Same unreadable file | doctor --output json |
0 (credentials warning) | 2 |
| Same file after restoring read permission | project list --output json |
0 | 1 |
| Directory at credentials pathname | project list --output json |
1 (EISDIR) |
0 |
| Directory removed | project list --output json |
0 | 1 |
The broader matrix includes text/JSON, auth/test reads, flag endpoint precedence, partial overrides requiring real file fallback, blank-variable normalization, malformed-profile rejection, dry-run, and a503retry after the fixture makes the file unreadable. That last request succeeds with the resolved key: the unnecessary dependency is at initial config resolution, not transport replay. An uninstrumented filesystem reproduction gives the same failure.
Expected and actual
With a valid key and endpoint completely supplied by supported overrides, the ordinary read should use those values without opening an unused credential file. Actual JSON stderr is an EACCES/EISDIR envelope with exit1 and no request; no server authentication error is involved.
This blocks environment-configured scripts and the advertised env-key workaround for an unreadable local store. I have not measured hosted incidence or tested Windows ACLs. No credential disclosure, file corruption, or server defect is claimed.
Root cause and proposed scope
loadConfig calls readProfile before normalizing/selecting TESTSPRITE_API_KEY and TESTSPRITE_API_URL. readProfile synchronously reads the existing file even when none of its values are needed. The ordinary client factory therefore fails before using its higher-priority inputs.
A focused fix can validate the profile name separately, resolve explicit key/endpoint values first, and read the credential file only when a needed field still requires fallback. Preserve errors when fallback really is needed, blank-variable behavior, malformed-profile rejection, and the existing precedence. Do not broadly catch filesystem errors or change ACLs. Add regressions for both complete and partial overrides; the local six-test regression currently has four controls passing and these two cases failing.
Related work and contribution intent
Credit to @aythin and @jangjos-128 for the ACL diagnosis and doctor recovery in #304/#301/#356. This is a remaining ordinary-command config dependency; the existing doctor fix works in the reproduction and should not be re-reported. #8/#97 handle empty variables. #399/#401 handle prototype-sensitive profile names. #225 adds optional configuration defaults but its current patch still eagerly reads credentials. I reviewed open and closed issues/PRs and the matching discussions.
I intend to implement this after maintainer acceptance and actual assignment to @walter-w-white, following the contribution guide. No production fix has been started.
Current duplicate review
Compared current open and closed reports again on 2026-10-19. #438 adds validation and diagnostics for a directory/FIFO credentials path; #465 makes telemetry authentication best-effort. Neither removes the ordinary loadConfig dependency on an unused credential file when key and endpoint are fully supplied. This reproduction disables telemetry; doctor works while the ordinary command still fails. Partial overrides must continue reading and validating required fallback fields.
Contribution contact
Please contact @walter-w-white on this issue. No Discord identity is provided. AI assistance was used for source investigation, offline reproduction and drafting. This issue reports one existing CLI behavior; no production fix or assignment is claimed.
Pre-submission revalidation
Revalidated on 2026-10-19 with six actual CLI invocations and DAC permission bypass removed from the entire reproduction process. Only unreadable-file and directory-path ordinary reads violated the override contract; missing-file, doctor, repaired-permission and removed-directory controls behaved as expected. Tested upstream remains 988c0355bb05631e9c6c1785e443937ff2a88c0e (CLI 0.14.0, Linux, Node 22.23.3). All reproduction traffic is local; no hosted testing credits were used.
I intend to implement this fix after maintainer acceptance and assignment.
Please contact @walter-w-white on this GitHub issue or PR; no Discord identity is provided.
Activity
zeshi-du commentedon Oct 20, 2026
Thanks @walter-w-white — confirmed on v0.15.0 and accepted.
loadConfigcallsreadProfilebefore looking atTESTSPRITE_API_KEY/TESTSPRITE_API_URL/--endpoint-url, so an unreadable or directory credentials path breaks commands that don't need it.Scope:
configtests. The complete case should fail before the fix.Comment
/assignto claim it.walter-w-white commentedon Oct 20, 2026
/assign
testsprite-hob commentedon Oct 20, 2026
Assigned to @walter-w-white. Thanks for taking this on. (2 more slots available.)
Your open assigned issues (1/3):
(none)
ruili-testsprite commentedon Oct 20, 2026
One addition to the scope above, since it's the one caller that depends on the eager read:
runDoctorlearns that the credentials file is unreadable by catching the errorloadConfigthrows (credentialsReadError). With a lazy read that path goes quiet whenever the key and endpoint come from flags or env, so havedoctorcheck the credentials file directly and keep reporting an unreadable file.(TESTSPRITE_API_KEY is set, so commands still work)only when both the key and the endpoint come from outside the file.