Overview
Affected Versions
@clerk/hono: 0.1.0 to before 0.1.5; @clerk/express: 2.0.0 to before 2.0.7; @clerk/backend: 3.0.0 to before 3.2.3; @clerk/fastify: 3.1.0 to before 3.1.5
Code Fix Example
/* Vulnerable pattern (do not copy into prod) */
const express = require('express');
const app = express();
// Vulnerable: forwards arbitrary client-provided URL and includes Clerk secret key in headers
app.get('/vuln-proxy', async (req, res) => {
const target = req.query.url;
if (!target) return res.status(400).send('Missing url');
try {
const r = await fetch(target, {
headers: {
...req.headers,
'Clerk-Secret-Key': process.env.CLERK_SECRET_KEY
}
});
const text = await r.text();
res.status(r.status).send(text);
} catch (e) {
res.status(500).send('Proxy error');
}
});
/* Fixed pattern */
app.get('/fix-proxy', async (req, res) => {
const target = req.query.url;
if (!target) return res.status(400).send('Missing url');
// Validate destination against a whitelist and ensure URL is well-formed
try {
const u = new URL(target);
const allowedOrigins = [
'https://clerk.example.com',
'https://api.clerk.dev'
];
if (!allowedOrigins.includes(u.origin)) {
return res.status(400).send('Destination not allowed');
}
} catch {
return res.status(400).send('Invalid URL');
}
// Do not forward sensitive headers
const forwardHeaders = { ...req.headers };
delete forwardHeaders['clerk-secret-key'];
delete forwardHeaders['authorization'];
try {
const r = await fetch(target, { headers: forwardHeaders });
const text = await r.text();
res.status(r.status).send(text);
} catch (e) {
res.status(500).send('Proxy error');
}
});
app.listen(process.env.PORT || 3000);