Api Key Exposure on Vercel
How Api Key Exposure Manifests in Vercel
Api key exposure in Vercel deployments typically occurs through three primary vectors: environment variable leakage in client-side code, misconfigured API routes, and Next.js API middleware that inadvertently exposes secrets.
The most common scenario involves developers using process.env in Next.js getServerSideProps or getStaticProps without understanding that these props get serialized into the HTML response. Consider this vulnerable pattern:
// pages/api/secret.js
export default function handler(req, res) {
const apiKey = process.env.API_KEY;
res.status(200).json({ apiKey });
}
// pages/index.js
export async function getServerSideProps() {
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`
}
});
return {
props: {
data: await response.json(),
apiKey: process.env.API_KEY // EXPOSED!
}
};
}
export default function Home({ data, apiKey }) {
// apiKey is now in the browser
return {JSON.stringify(data)};
}Another Vercel-specific vector is the API_ROUTE_API_KEY header exposure. When using Vercel's built-in API routes, developers sometimes log or return headers that contain API keys:
// pages/api/external.js
export default async function handler(req, res) {
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`,
'API_ROUTE_API_KEY': process.env.API_KEY // Auto-injected by Vercel
}
});
// Logging headers exposes the key
console.log(req.headers);
res.status(200).json(await response.json());
}
Vercel's edge functions can also inadvertently expose keys through improper caching. When using cache-control headers without proper isolation, API keys in response bodies can be cached and served to unauthorized users:
// pages/edge.js
export default async function EdgeAPIRequest() {
const apiKey = process.env.API_KEY;
return new Response(JSON.stringify({ apiKey }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=60' // Caches the API key!
}
});
}
Vercel-Specific Detection
Detecting API key exposure in Vercel deployments requires both static analysis and runtime scanning. middleBrick's black-box scanner is particularly effective here because it can detect exposed keys without requiring source code access.
For manual detection, start by examining your build output. Vercel's deployment logs show the final bundled JavaScript, making it easy to spot process.env usage that might expose secrets:
# Check build logs for exposed environment variables
vercel logs --limit 50
# Search for process.env in the deployed bundle
curl -s https://your-app.vercel.app/_next/static/chunks/main.js | grep -o "process\.env\.[^)]*"middleBrick specifically scans for Vercel's auto-injected headers and environment variable patterns. The scanner tests for:
- Exposed
API_ROUTE_API_KEYheaders in API responses - Serialized
process.envvariables in HTML responses - Environment variable leakage through error messages and stack traces
- Cache poisoning that serves API keys to unauthorized users
Here's how to run middleBrick against your Vercel deployment:
# Install the CLI
npm install -g middlebrick
# Scan your deployed Vercel app
middlebrick scan https://your-app.vercel.app --output json
# Scan with specific focus on API routes
middlebrick scan https://your-app.vercel.app/api --category api-security
The scanner's LLM/AI security module also checks for AI-specific exposure patterns, such as system prompts containing API keys in AI-powered features:
// Vulnerable AI endpoint
export default async function handler(req, res) {
const systemPrompt = `Your API key is ${process.env.API_KEY}. Use it responsibly.`;
const prompt = req.body.prompt;
const response = await aiModel.generate({ systemPrompt, prompt });
res.status(200).json(response);
}
Vercel-Specific Remediation
Fixing API key exposure in Vercel requires understanding Next.js's data flow and using Vercel's built-in security features. The most effective approach is to ensure secrets never reach the client side.
For API routes, always validate that environment variables aren't returned in responses:
// pages/api/external.js
export default async function handler(req, res) {
const apiKey = process.env.API_KEY;
// Only use the key internally, never return it
const response = await fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const data = await response.json();
// Return only what the client needs
res.status(200).json({
data: data.results,
metadata: data.metadata
});
}
For pages that need API data, use client-side fetching instead of server-side props when dealing with secrets:
// pages/index.js
export default function Home() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProtectedData = async () => {
try {
const response = await fetch('/api/external');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Failed to fetch data:', error);
} finally {
setLoading(false);
}
};
fetchProtectedData();
}, []);
if (loading) return Loading...;
return {JSON.stringify(data)};
}
Vercel's middleware.ts can also help prevent exposure by blocking requests that might reveal secrets:
// middleware.ts
import { NextResponse } from 'next/server';
export function middleware(request) {
const url = request.nextUrl;
// Block requests to sensitive endpoints from unauthorized sources
if (url.pathname.startsWith('/api/secret') && !request.headers.get('x-internal-call')) {
return new NextResponse(null, {
status: 403,
statusText: 'Forbidden'
});
}
return NextResponse.next();
}
export const config = {
matcher: ['/api/:path*']
};
For edge functions, use Vercel's Response constructor with proper content security:
// pages/edge.js
export default async function EdgeAPIRequest() {
const apiKey = process.env.API_KEY;
// Process data without exposing the key
const processedData = await processDataWithApiKey(apiKey);
return new Response(JSON.stringify(processedData), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'private, no-store' // Never cache sensitive data
}
});
}
Frequently Asked Questions
How can I test if my Vercel API routes are leaking API keys?
process.env usage and test API endpoints directly to verify they don't return sensitive data.What's the difference between <code>getServerSideProps</code> and <code>getStaticProps</code> regarding API key exposure?
getStaticProps runs at build time and can embed keys in the HTML if not careful. getServerSideProps runs on each request but still serializes all props to the client. The safest approach is to use client-side fetching for any data that requires API keys, ensuring secrets never reach the browser.