Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Gemini API Key for local development/testing
# Note: For the GitHub Pages site, users will be prompted to enter their own API key
# which is stored in their browser's localStorage (not shared with the server)
# Get your free API key at: https://aistudio.google.com/apikey
GEMINI_API_KEY=AIzaSyB1kLWIDXGvwikSQyAbqhytf3wHf65aulQ
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains an exposed Gemini API key that is now committed to the repository. API keys should never be committed to version control as they can be used by anyone who has access to the repository or its history.

This API key should be:

  1. Immediately revoked at https://aistudio.google.com/apikey
  2. This file should be removed from the repository
  3. The .gitignore file should be updated to include .env files
  4. A template file like .env.example should be provided instead with placeholder values

The comment on lines 2-3 is misleading - it states that users will enter their own API key in localStorage for GitHub Pages, but this file contains an actual API key that will be in the git history even if removed.

Suggested change
GEMINI_API_KEY=AIzaSyB1kLWIDXGvwikSQyAbqhytf3wHf65aulQ
# IMPORTANT: Do not commit your real API key to version control.
# Replace the placeholder below with your actual API key for local use only.
GEMINI_API_KEY=YOUR_GEMINI_API_KEY_HERE

Copilot uses AI. Check for mistakes.
Empty file added docs/.nojekyll
Empty file.
120 changes: 120 additions & 0 deletions docs/cloudflare-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* TinyGPU Gemini API Proxy - Cloudflare Worker
*
* This worker proxies requests to the Gemini API, keeping your API key secure.
* Deploy this to Cloudflare Workers and set the GEMINI_API_KEY secret.
*
* Setup Instructions:
* 1. Go to https://dash.cloudflare.com/ and sign up/login
* 2. Go to Workers & Pages > Create Application > Create Worker
* 3. Name it something like "tinygpu-gemini-proxy"
* 4. Replace the default code with this file's contents
* 5. Go to Settings > Variables > Add Variable
* - Name: GEMINI_API_KEY
* - Value: Your Gemini API key
* - Click "Encrypt" to keep it secret
* 6. Save and Deploy
* 7. Your worker URL will be: https://tinygpu-gemini-proxy.<your-subdomain>.workers.dev
*/

export default {
async fetch(request, env) {
// Handle CORS preflight
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CORS policy is set to allow all origins with "Access-Control-Allow-Origin: *". This means any website can make requests to this proxy endpoint, potentially consuming your Gemini API quota.

Consider implementing at least one of the following protections:

  1. Restrict the origin to your specific GitHub Pages domain (e.g., "https://username.github.io")
  2. Add rate limiting to prevent abuse
  3. Implement request validation or authentication
  4. Add a referrer check to ensure requests come from your documentation site

Without these protections, anyone who discovers your worker URL can use your API key through the proxy.

Copilot uses AI. Check for mistakes.
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Max-Age": "86400",
},
});
}

// Only allow POST requests
if (request.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed" }), {
status: 405,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same overly permissive CORS configuration ("Access-Control-Allow-Origin: *") is used in all response headers throughout this file (lines 26, 40, 55, 93, 107, 115). This creates a security vulnerability as explained in the earlier comment.

Consider creating a helper function to generate CORS headers with a restricted origin, and use it consistently throughout the worker.

Copilot uses AI. Check for mistakes.
},
});
}

try {
// Get the request body
const body = await request.json();

// Validate required fields
if (!body.prompt) {
return new Response(JSON.stringify({ error: "Missing prompt" }), {
status: 400,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
}
Comment on lines +49 to +58
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input validation only checks if the prompt exists, but doesn't validate its length or content. This could lead to abuse where malicious users send extremely large prompts, consuming your API quota and potentially causing performance issues.

Consider adding:

  1. Maximum prompt length validation (e.g., 2000 characters)
  2. Maximum systemPrompt length validation
  3. Content validation to prevent obviously malicious or inappropriate content
  4. Rate limiting per IP address or user agent

Example:

if (!body.prompt || body.prompt.length > 2000) {
  return new Response(JSON.stringify({ error: "Invalid prompt length" }), {...});
}

Copilot uses AI. Check for mistakes.

// Build Gemini API request
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${env.GEMINI_API_KEY}`;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gemini model version "gemini-2.0-flash" is hardcoded in the API URL. If Google releases a new model version or deprecates this one, the code will need to be updated and redeployed.

Consider making the model version configurable through an environment variable or constant at the top of the file:

const GEMINI_MODEL = env.GEMINI_MODEL || 'gemini-2.0-flash';
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${env.GEMINI_API_KEY}`;

This allows for easier updates without code changes.

Suggested change
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${env.GEMINI_API_KEY}`;
const GEMINI_MODEL = env.GEMINI_MODEL || 'gemini-2.0-flash';
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${env.GEMINI_API_KEY}`;

Copilot uses AI. Check for mistakes.

const geminiPayload = {
contents: [{ parts: [{ text: body.prompt }] }],
};

// Add system instruction if provided
if (body.systemPrompt) {
geminiPayload.systemInstruction = {
parts: [{ text: body.systemPrompt }],
};
}

// Call Gemini API
const geminiResponse = await fetch(geminiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(geminiPayload),
});

if (!geminiResponse.ok) {
const errorText = await geminiResponse.text();
return new Response(
JSON.stringify({
error: "Gemini API error",
status: geminiResponse.status,
details: errorText,
}),
{
status: geminiResponse.status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
Comment on lines +82 to +96
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error response from the Gemini API is being returned verbatim to the client, which could potentially leak sensitive information about the API configuration, internal error details, or rate limits.

Consider sanitizing the error response before sending it to the client:

  1. Don't expose the full error text from the API
  2. Map API status codes to user-friendly messages
  3. Log detailed errors server-side for debugging, but return generic messages to clients

Example:

console.error('Gemini API error:', errorText); // Log for debugging
return new Response(JSON.stringify({
  error: 'Unable to process request',
  code: geminiResponse.status
}), {...});

Copilot uses AI. Check for mistakes.
}

const data = await geminiResponse.json();
const text =
data.candidates?.[0]?.content?.parts?.[0]?.text ||
"No response generated.";

return new Response(JSON.stringify({ text }), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
}
},
};
Loading