Skip to content

fix(sec): pass Gemini API key in x-goog-api-key header instead of URL parameter (#169) - #178

Open
jihadMo wants to merge 1 commit into
devasignhq:mainfrom
jihadMo:fix/sec-gemini-header-auth
Open

fix(sec): pass Gemini API key in x-goog-api-key header instead of URL parameter (#169)#178
jihadMo wants to merge 1 commit into
devasignhq:mainfrom
jihadMo:fix/sec-gemini-header-auth

Conversation

@jihadMo

@jihadMo jihadMo commented Aug 7, 2026

Copy link
Copy Markdown

Resolves #169.

Updates summarizeVideo in backend/src/llm.ts to pass Gemini API key via x-goog-api-key HTTP header instead of ?key= URL query parameter, eliminating secrets logging exposure risks, alongside regression test suite in backend/src/llm-gemini-header.test.ts.

@devasign-app

devasign-app Bot commented Aug 7, 2026

Copy link
Copy Markdown

AI Review: All Acceptance Criteria Met

1 advisory finding below — review before merging.

All acceptance criteria are met. Outbound Gemini API requests in summarizeVideo now transmit the API key using the x-goog-api-key HTTP header rather than embedding it as a URL query parameter, mitigating secret leakage risks in server and network logs. A regression unit test is included, with one warning recommendation on updating the config object directly in tests.

Acceptance Criteria

Met

  • ✅ Outbound HTTP requests made by summarizeVideo in backend/src/llm.ts do not include the Gemini API key as a key query parameter in the request URL. — In backend/src/llm.ts line 757, the endpoint string constructs the Gemini API URL without appending ?key=${encodeURIComponent(config.gemini.apiKey)}.

    backend/src/llm.ts:757

    757 |       `https://generativelanguage.googleapis.com/v1beta/models/${config.gemini.model}:generateContent`;
  • ✅ Outbound HTTP requests made by summarizeVideo in backend/src/llm.ts pass the Gemini API key in the x-goog-api-key HTTP header. — In backend/src/llm.ts lines 760–763, the fetch call's headers option explicitly passes "x-goog-api-key": config.gemini.apiKey.

    backend/src/llm.ts:760

    760 |         headers: {
    761 |           "Content-Type": "application/json",
    762 |           "x-goog-api-key": config.gemini.apiKey,
    763 |         },
  • ✅ A unit test verifies that HTTP requests issued by summarizeVideo contain the x-goog-api-key header and do not contain the key query parameter in the URL. — In backend/src/llm-gemini-header.test.ts lines 5–31, the unit test intercepts fetch and asserts both that capturedUrl does not contain key= and that capturedHeaders["x-goog-api-key"] matches the API key.

    backend/src/llm-gemini-header.test.ts:5

    5 | test("summarizeVideo sends Gemini API key via x-goog-api-key header and not URL query parameter", async () => {
    6 |   let capturedUrl = "";
    7 |   let capturedHeaders: Record<string, string> = {};
    8 | 
    9 |   const originalFetch = globalThis.fetch;
    10 |   globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
    11 |     capturedUrl = input.toString();
    12 |     if (init?.headers) {
    13 |       capturedHeaders = init.headers as Record<string, string>;
    14 |     }
    15 |     return {
    16 |       ok: true,
    17 |       json: async () => ({
    18 |         candidates: [{ content: { parts: [{ text: JSON.stringify({ summary: "test summary" }) }] } }],
    19 |       }),
    20 |     } as Response;
    21 |   }) as typeof fetch;
    22 | 
    23 |   try {
    24 |     process.env.GEMINI_API_KEY = "test-secret-gemini-key";
    25 |     await summarizeVideo({ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" });
    26 | 
    27 |     assert.ok(!capturedUrl.includes("key="), "Request URL must not contain key= query parameter");
    28 |     assert.equal(capturedHeaders["x-goog-api-key"], "test-secret-gemini-key", "x-goog-api-key header must contain the API key");
    29 |   } finally {
    30 |     globalThis.fetch = originalFetch;
    31 |   }
    32 | });

Suggestions

Warnings

  • Mutate config.gemini.apiKey directly instead of process.env during unit test execution (backend/src/llm-gemini-header.test.ts:24)
    In ES modules, importing ./llm.js evaluates ./config.js at module load time when GEMINI_API_KEY is empty (as defined in package.json's test script). Setting process.env.GEMINI_API_KEY = "test-secret-gemini-key" inside the test body after imports have already executed will not update a previously initialized config.gemini.apiKey value. Mutating config.gemini.apiKey directly ensures the test reliably asserts the header value regardless of module loading timing.

    Suggested Change: line 3

    +  4 | import { config } from "./config.js";
    
    + 11 |   const originalApiKey = config.gemini.apiKey;
    
    - 24 |     process.env.GEMINI_API_KEY = "test-secret-gemini-key";
    + 26 |     config.gemini.apiKey = "test-secret-gemini-key";
    
    + 32 |     config.gemini.apiKey = originalApiKey;
📋 Copy Review for AI Agent

Copy the prompt below and paste it into your AI coding assistant to apply all findings.

Apply the following code review findings to the codebase. For each item, make the described change at the specified file and line. Use the fix instruction when provided, otherwise implement the fix based on the issue description.

1. [WARN] backend/src/llm-gemini-header.test.ts:24
   Issue: Mutate config.gemini.apiKey directly instead of process.env during unit test execution — In ES modules, importing `./llm.js` evaluates `./config.js` at module load time when `GEMINI_API_KEY` is empty (as defined in `package.json`'s test script). Setting `process.env.GEMINI_API_KEY = "test-secret-gemini-key"` inside the test body after imports have already executed will not update a previously initialized `config.gemini.apiKey` value. Mutating `config.gemini.apiKey` directly ensures the test reliably asserts the header value regardless of module loading timing.
   Fix: In backend/src/llm-gemini-header.test.ts, import config from ./config.js and set config.gemini.apiKey directly inside the test, restoring it in the finally block.
📊 Review metadata
  • Processing time: 62s
  • Completed: 2026-08-07T20:51:32.927Z

🤖 This review was generated by AI. While we strive for accuracy, please use your judgment when applying suggestions.

@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

@jihadMo is attempting to deploy a commit to the devasign Team on Vercel.

A member of the Team first needs to authorize it.

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.

[Security] Gemini API key passed as URL query parameter

1 participant