mirror of https://github.com/garrytan/gstack.git
fix(design): create the OpenAI key file owner-only, closing the write-then-chmod race
saveApiKey wrote ~/.gstack/openai.json at the default umask and tightened to 0600 afterwards, leaving the API key briefly world-readable between write and chmod (CWE-377/367). Pass mode 0o600 at create; the trailing chmodSync stays as a backstop to tighten a pre-existing loose file. Contributed by @bunlongheng (PR #2468). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a03f571147
commit
a2ae26d44c
|
|
@ -111,7 +111,10 @@ export function describeApiKeySource(resolution: ApiKeyResolution): string {
|
|||
export function saveApiKey(key: string): void {
|
||||
const dir = path.dirname(configPath());
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
fs.writeFileSync(configPath(), JSON.stringify({ api_key: key }, null, 2));
|
||||
// Create the file owner-only up front so the API key is never briefly
|
||||
// world/group-readable in the window between write and chmod. The trailing
|
||||
// chmodSync is kept as a backstop to tighten a pre-existing loose file.
|
||||
fs.writeFileSync(configPath(), JSON.stringify({ api_key: key }, null, 2), { mode: 0o600 });
|
||||
fs.chmodSync(configPath(), 0o600);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,27 @@ describe("resolveApiKeyInfo", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("saveApiKey", () => {
|
||||
test("stores the key file owner-only, even under a permissive umask", () => {
|
||||
// The OpenAI key file must never be group/other-readable. saveApiKey now
|
||||
// creates it with mode 0600 up front (matching session.ts / #859) instead
|
||||
// of writing at the default umask and tightening afterwards, so the key is
|
||||
// not briefly world-readable in the write-then-chmod window (CWE-377/367).
|
||||
const prevUmask = process.umask(0o000);
|
||||
try {
|
||||
saveApiKey("sk-secret-value");
|
||||
} finally {
|
||||
process.umask(prevUmask);
|
||||
}
|
||||
|
||||
const keyPath = path.join(tmpHome, ".gstack", "openai.json");
|
||||
const mode = fs.statSync(keyPath).mode & 0o777;
|
||||
expect(mode).toBe(0o600);
|
||||
// No group/other read/write/exec bits.
|
||||
expect(mode & 0o077).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("requireApiKey", () => {
|
||||
test("prints source disclosure without leaking the key", () => {
|
||||
process.env.OPENAI_API_KEY = "sk-secret-value";
|
||||
|
|
|
|||
Loading…
Reference in New Issue