Infographic Agent is a client-side React SPA that leverages Gemini API for infographic generation. This document outlines the security architecture, best practices, and vulnerability disclosure process.
By design, API keys are user-provided and client-side. This model means:
- No backend proxy required for the web application (fully self-contained static HTML)
- User owns their API key - keys are never transmitted to our servers
- Keys persist locally in browser localStorage for convenience across sessions
- Users control key scope - they can use restricted API keys in GCP
- Simplicity: Deployable as a single
index.htmlto any static host - Privacy: Your keys never leave your browser unless you explicitly share them
- Cost control: You control which API key is used and can rotate/restrict at any time
- No backend burden: No need to maintain, scale, or secure a backend service
- Risk: API keys in localStorage are accessible to XSS attacks
- Mitigation:
- A Content Security Policy restricts where scripts, styles, and connections can come from (see its limits below)
- Model output is rendered as plain text/images through React — no
dangerouslySetInnerHTML,eval, or iframes anywhere in the app - Input validation prevents file-based attacks
- Users should use restricted API keys (see below)
- Risk:
VITE_GEMINI_API_KEYset at build time is inlined into the bundleddist/index.html— anyone who can load the page can read it - Mitigation: only bake a key into private/internal deployments; for public hosting, ship without a key and let each visitor supply their own via the settings panel
For production use, create a restricted Gemini API key in Google Cloud:
- Go to Google Cloud Console
- Create a new API key
- Restrict the key to:
- API: Only "Generative Language API"
- Application Restrictions: HTTP referrer (your domain)
- Rate limiting: Set appropriate limits for your use case
- Use this restricted key in the app
This prevents a compromised key from being used to call other Google APIs or services outside your domain.
For production applications with sensitive requirements, implement a backend proxy:
Browser → Your Backend (with restricted API key) → Gemini API
Example Node.js proxy:
// backend/routes/gemini.ts
app.post('/api/generate-infographic', authenticateUser, async (req, res) => {
const { prompt, images } = req.body;
// Validate inputs, apply rate limiting, log usage
const response = await geminiClient.models.generateContent({
model: 'gemini-3.1-flash-lite-image',
contents: [...images, { text: prompt }],
});
res.json(response);
});Benefits:
- API key never exposed to browser
- Server-side rate limiting and authentication
- Audit logging and usage tracking
- Fine-grained access control
A CSP is delivered three ways — a <meta> tag in app.html, public/_headers (static hosts like Netlify/Cloudflare Pages), and nginx.conf (Docker). The policy:
default-src 'self'
script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.tailwindcss.com
font-src 'self' https://fonts.gstatic.com https://fonts.googleapis.com
connect-src 'self' https://generativelanguage.googleapis.com
img-src 'self' data: blob:
frame-ancestors 'none' (header-delivered only; ignored in <meta> per spec)
base-uri 'self'
form-action 'self'
Known limitation: script-src includes 'unsafe-inline'. This is required by the Tailwind Play CDN's inline configuration script (see docs/learnings.md — the Play CDN does not support SRI/CORS), and by the single-file production build, which inlines the app bundle into dist/index.html. It means the CSP does not block inline script injection; the real XSS defense is that the app never renders untrusted HTML (React text rendering only, no dangerouslySetInnerHTML). Compiling Tailwind at build time and moving to nonce-based scripts would allow dropping 'unsafe-inline' — contributions welcome.
What the CSP does enforce:
- Script/style origins - only same-origin and the pinned CDNs can serve script and style files
- Network egress - the page can only talk to the Gemini API endpoint (
generativelanguage.googleapis.com) - Framing attacks - page cannot be embedded in iframes (header-based
frame-ancestorsplusX-Frame-Options: DENY) - Form hijacking - forms can only submit to same origin
File uploads are validated by:
- Magic byte validation - for PDF, PNG, JPEG, and WebP, file content must match the declared MIME type (prevents disguised uploads); other whitelisted formats are validated by MIME type and size only
- MIME type whitelist - only safe formats accepted (PDF, images, text, spreadsheets)
- Size limits - individual files capped at 20MB, 50MB total per generation, up to 14 files
- Early validation - magic bytes checked before full base64 decode (prevents DoS)
Uploaded files are never executed or rendered locally — they are only base64-encoded and sent to the Gemini API.
Supported formats:
- Documents: PDF
- Spreadsheets: CSV, TSV, XLS, XLSX
- Images: PNG, JPEG, WebP, HEIC, HEIF
- Text: Plain text, Markdown
Note: The Tailwind Play CDN (cdn.tailwindcss.com) does not support CORS headers (it does not return Access-Control-Allow-Origin). Therefore, standard Subresource Integrity (SRI) hashes and strict Cross-Origin-Embedder-Policy (COEP) are incompatible with the Play CDN and have been omitted to ensure the script loads correctly in all browsers. If a production-level SRI is required, the project should be compiled using Tailwind's build-time CLI or self-hosted.
The following headers are configured in public/_headers (for static hosts) and nginx.conf (Docker); app.html additionally carries the CSP as a <meta> tag so it applies even without server headers:
| Header | Value | Purpose |
|---|---|---|
X-Frame-Options |
DENY |
Prevent clickjacking |
X-Content-Type-Options |
nosniff |
Prevent MIME sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Control referrer information |
Permissions-Policy |
Restrict camera, microphone, geolocation | Limit browser API access |
Cross-Origin-Opener-Policy |
same-origin |
Prevent cross-origin window access |
Client-side rate limiting is implemented in src/services/geminiService.ts:
- Token bucket algorithm with 10 requests per minute
- User-friendly messaging when limit exceeded
- Configurable per deployment
Note: For production, implement server-side rate limiting to prevent abuse.
At the time of release, no security vulnerabilities are known in this application. However, security is an ongoing process. Please report any issues responsibly.
If you discover a security vulnerability, please report it privately via GitHub Security Advisories, including:
- Description - clear explanation of the vulnerability
- Affected components - which files/services are involved
- Proof of concept - steps to reproduce (if applicable)
- Impact - severity and potential harm
- Suggested fix - if you have one (optional)
Please do not open public issues for security vulnerabilities. We will:
- Acknowledge receipt within 48 hours
- Investigate the issue
- Develop a fix
- Release a patch and credit you (if desired)
- Provide an estimated timeline for public disclosure
Dependencies are managed via package-lock.json for reproducible builds:
# Check for known vulnerabilities
npm audit
# Update dependencies safely
npm updateKey dependencies:
@google/genai- Official Google SDKreact- Vetted by Meta security teamvite- Widely-used build tool
- Modern browser with ES2020+ support
- JavaScript enabled
- Local storage access (for API key persistence)
- HTTPS recommended for production
-
API Keys
- Never commit keys to git (use
.envand.gitignore) - Use environment variables for CI/CD
- Rotate keys regularly
- Never commit keys to git (use
-
Dependencies
- Run
npm auditbefore releases - Keep dependencies up-to-date
- Use exact versions in production
- Run
-
Code Review
- Review for injection vulnerabilities
- Validate all user input
- Test CSP compliance
-
Deployment
- Use HTTPS in production
- Set security headers on your host
- Enable HSTS if possible
- Monitor for unusual API usage
# Check for known vulnerabilities
npm audit
# Build and check CSP compliance
npm run build
# Test in browser
# 1. Open DevTools Console
# 2. Check for CSP violations
# 3. Verify scripts/fonts load correctly- OWASP Top 10
- MDN Web Security
- Content Security Policy
- SRI (Subresource Integrity)
- Google Cloud API Key Security
For security-related questions (not vulnerabilities), please open an issue on GitHub with the security label.