mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-13 20:07:08 +01:00
Stop the secrets check flagging code that reads an environment variable (GST-1894) (#4157)
* Stop the secrets check flagging code that reads an environment variable (GST-1894) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep the new secrets test rows out of the line-start credential shape Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Give the fixture-label exemption only to files named like tests A production file such as ab-test.ts or load-test.ts matched the test-file pattern, so a weak real password in it stopped being flagged. The pattern now matches only the documented forms: *.test.*, *.spec.*, *_test.* and test_*.py. Review-Finding: p1/red-team#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep flagging a hardcoded fallback in backticks or on the next line The env-read exemption only looked for a quoted fallback on the same line, so token = opts?.env?.API_TOKEN ?? followed by a backtick literal, or by a literal wrapped onto the next line, passed the secrets check. Backtick literals now count, and the next line is read when the expression continues there. The changelog now says plainly that an ALL-CAPS literal used as the variable name still passes. Review-Finding: voices/outside-voice#1 reader=outside-voice Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Exempt an env read only when its line has no fallback, and stop reading the next line Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Say in the changelog that quoted lowercase env names now fail the secrets check Review-Finding: p1/red-team#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
d479c5bf1e
commit
910093087d
4 files changed
+162
-3
No files matched your search
@@ -165,6 +165,17 @@ describe('pregate secrets: source context and diff integrity', () => {
|
||||
} finally { cleanup(fx); }
|
||||
});
|
||||
|
||||
test('a test file reading an env var in code and holding a self-named fixture label passes; a literal beside the read or in a .env file still fails (GST-1894)', () => {
|
||||
const token = '8Fk2pQ9vXz4wL7mN3rT6yB1cD5eG0hJ';
|
||||
const qm = 'let token: string | undefined;\nfunction start(opts?: { env?: Record<string, string> }) {\n token = opts?.env?.AGENT_API_TOKEN;\n}\nconst brain = {\n clientSecret: "personal-secret",\n};\n';
|
||||
expect(repo({ 'memory-registry-pg.test.ts': qm }).secrets().status).toBe('pass');
|
||||
const r = repo({ 'memory-registry-pg.test.ts': qm.replace('AGENT_API_TOKEN;', `AGENT_API_TOKEN ?? "${token}";`), 'agent.env': `AGENT_API_TOKEN=${token}\n` }).secrets();
|
||||
expect(r.status).toBe('fail');
|
||||
const kv = r.lines.filter((l) => l.includes('env.kv'));
|
||||
expect(kv).toHaveLength(2);
|
||||
expect(kv).toEqual(expect.arrayContaining([expect.stringContaining('agent.env, added line 1'), expect.stringContaining('memory-registry-pg.test.ts, added line 3')]));
|
||||
});
|
||||
|
||||
test('the same assignment in a .env file still fails, naming the file', () => {
|
||||
const f = repo({ 'deploy.env': 'API_TOKEN=8Fk2pQ9vXz4wL7mN3rT6yB1cD5eG0hJ\n' });
|
||||
const r = f.secrets();
|
||||
|
||||
@@ -153,6 +153,106 @@ describe("env.kv: a name-shaped literal inside a call is a file or dict key, not
|
||||
);
|
||||
});
|
||||
|
||||
describe("env.kv: reading an environment variable in code names a secret without holding one (GST-1894)", () => {
|
||||
const FAKE_KEY = "Zx9Qw8Er7Ty6Ui5Op4As3Df2";
|
||||
const GH = ["ghp", "_", "Zx9Qw8Er7Ty6Ui5Op4As3Df2Gh7Jk8Lm9Np0"].join("");
|
||||
table(
|
||||
"env.kv",
|
||||
"MEDIUM",
|
||||
[
|
||||
String.raw` token = opts?.env?.AGENT_API_TOKEN;`,
|
||||
String.raw`token = ctx.env.AGENT_API_TOKEN;`,
|
||||
String.raw`token = env?.AGENT_API_TOKEN;`,
|
||||
String.raw`token = env.AGENT_API_TOKEN,`,
|
||||
String.raw`token = import.meta.env.VITE_API_TOKEN;`,
|
||||
String.raw`token = Bun.env.AGENT_API_TOKEN;`,
|
||||
String.raw`token = this.env!.AGENT_API_TOKEN;`,
|
||||
String.raw`token = process.env.AGENT_API_TOKEN;`,
|
||||
String.raw`token = process.env["AGENT_API_TOKEN"];`,
|
||||
String.raw`token = opts?.env?.["AGENT_API_TOKEN"];`,
|
||||
String.raw`token = os.environ["OPENROUTER_API_KEY_V2"]`,
|
||||
String.raw`token = os.getenv("OPENROUTER_API_KEY_V2")`,
|
||||
String.raw`token = System.getenv("AGENT_API_TOKEN");`,
|
||||
],
|
||||
[
|
||||
String.raw`token = "${FAKE_KEY}"`,
|
||||
`AGENT_API_TOKEN=${FAKE_KEY}`,
|
||||
`export AGENT_API_TOKEN=${FAKE_KEY}`,
|
||||
String.raw`token = opts?.env?.AGENT_API_TOKEN ?? "${FAKE_KEY}"`,
|
||||
String.raw`token = env.AGENT_API_TOKEN||"${FAKE_KEY}"`,
|
||||
String.raw`token = process.env["AGENT_API_TOKEN"] || "${FAKE_KEY}"`,
|
||||
String.raw`token = os.getenv("AGENT_API_TOKEN", "${FAKE_KEY}")`,
|
||||
String.raw`token = process.env["${FAKE_KEY}"]`,
|
||||
String.raw`token = os.environ["${FAKE_KEY}"]`,
|
||||
`token = env.${FAKE_KEY}`,
|
||||
String.raw`token = myenv.AGENT_API_TOKEN;`,
|
||||
],
|
||||
);
|
||||
|
||||
test("a known key prefix beside an env name still reports HIGH", () => {
|
||||
for (const input of [`AGENT_API_TOKEN=${GH}`, String.raw`token = opts?.env?.AGENT_API_TOKEN ?? "${GH}"`]) {
|
||||
expect(findings(input).some((f) => f.severity === "HIGH")).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("env.kv: the env-read exemption needs a finished expression with no fallback; otherwise the line reads as on main (GST-1894)", () => {
|
||||
const FAKE_KEY = "Zx9Qw8Er7Ty6Ui5Op4As3Df2";
|
||||
const kv = (text: string) => findings(text).some((f) => f.id === "env.kv");
|
||||
test.each([
|
||||
["comment after ??", String.raw`token = opts?.env?.AGENT_API_TOKEN ?? // fallback` + "\n" + String.raw` "${FAKE_KEY}";`],
|
||||
["blank line after ??", String.raw`token = opts?.env?.AGENT_API_TOKEN ??` + "\n\n" + String.raw` "${FAKE_KEY}";`],
|
||||
["fallback wrapped after ??", String.raw`token = opts?.env?.AGENT_API_TOKEN ??` + "\n" + String.raw` "${FAKE_KEY}";`],
|
||||
["fallback on the next line", String.raw`token = opts?.env?.AGENT_API_TOKEN` + "\n" + String.raw` ?? "${FAKE_KEY}";`],
|
||||
["|| on the next line", String.raw`token = env.AGENT_API_TOKEN` + "\n" + String.raw` || "${FAKE_KEY}"`],
|
||||
["backtick fallback", "token = opts?.env?.AGENT_API_TOKEN ?? `" + FAKE_KEY + "`;"],
|
||||
["backtick concatenation", "token = ctx.env.AGENT_API_TOKEN + `" + FAKE_KEY + "`;"],
|
||||
["ternary", "token = opts?.env?.AGENT_API_TOKEN ? opts.env.AGENT_API_TOKEN : `" + FAKE_KEY + "`;"],
|
||||
["python or", String.raw`token = ctx.env.AGENT_API_TOKEN or FALLBACK_TOKEN_VALUE`],
|
||||
["getenv default", String.raw`token = System.getenv("AGENT_API_TOKEN", "${FAKE_KEY}");`],
|
||||
["call left open", String.raw`token = System.getenv("AGENT_API_TOKEN")`],
|
||||
])("fires as on main: %s", (_, input) => {
|
||||
expect(kv(input)).toBe(true);
|
||||
});
|
||||
test.each([
|
||||
["multi-line call", ["client = OpenAI(", String.raw` api_key=os.getenv("OPENAI_API_KEY"),`, String.raw` base_url="https://api.openai.com/v1",`, ")"].join("\n")],
|
||||
["object property", String.raw` token: opts?.env?.AGENT_API_TOKEN,`],
|
||||
["code span in a comment", "token = make_token(user) // `process.env.AGENT_API_TOKEN` fallback"],
|
||||
["getenv with an empty default", String.raw`token = os.getenv("OPENROUTER_API_KEY_V2", "")`],
|
||||
])("still exempt, as on main: %s", (_, input) => {
|
||||
expect(kv(input)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("env.kv: a lowercase fixture label that names itself a secret, in a test file only (GST-1894)", () => {
|
||||
const kv = (text: string, sourcePath?: string) => scan(text, { repoVisibility: "private", sourcePath }).findings.some((f) => f.id === "env.kv");
|
||||
test.each([
|
||||
["memory-registry-pg.test.ts", ' clientSecret: "personal-secret",'],
|
||||
["memory-registry-pg.test.ts", ' signingSecret: "memory-authority-signing-secret",'],
|
||||
["auth.spec.tsx", " apiKey: 'staging-api-key',"],
|
||||
["test_auth.py", ' password = "admin_password_fixture"'],
|
||||
["auth_test.go", ' token: "refresh-token-value",'],
|
||||
])("clean in %s: %s", (file, input) => {
|
||||
expect(kv(input, file)).toBe(false);
|
||||
});
|
||||
test.each([
|
||||
["memory-registry.ts", ' clientSecret: "personal-secret",'],
|
||||
[undefined, ' clientSecret: "personal-secret",'],
|
||||
["deploy.env", "CLIENT_SECRET=personal-secret"],
|
||||
["memory-registry-pg.test.ts", ' clientSecret: "correct-horse-battery-staple",'],
|
||||
["memory-registry-pg.test.ts", ' clientSecret: "Personal-Secret-9x",'],
|
||||
["memory-registry-pg.test.ts", ' clientSecret: "secretive-horse",'],
|
||||
["memory-registry-pg.test.ts", ' clientSecret: "Zx9Qw8Er7Ty6Ui5Op4As3Df2",'],
|
||||
["memory-registry-pg.test.ts.bak", ' clientSecret: "personal-secret",'],
|
||||
["src/ab-test.ts", ' clientSecret: "personal-secret",'],
|
||||
["scripts/load-test.ts", ' clientSecret: "personal-secret",'],
|
||||
["src/test_helpers.ts", ' clientSecret: "personal-secret",'],
|
||||
["spec/auth_spec.rb", ' clientSecret: "personal-secret",'],
|
||||
])("still MEDIUM in %s: %s", (file, input) => {
|
||||
expect(kv(input, file)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("internal.hostname: per-machine config filenames are not hosts (#2962, #2740)", () => {
|
||||
table(
|
||||
"internal.hostname",
|
||||
|
||||
Reference in new issue
Block a user