Skip to content
This repository was archived by the owner on Apr 17, 2026. It is now read-only.

Commit 468f108

Browse files
committed
fix: eliminate SSRF vulnerabilities by removing all dynamic URL construction
Closes #34, Closes #35 BREAKING: Refactored internal API communication pattern Security fixes: - Replace all fetch() calls with direct function imports in monitor routes - Eliminate dynamic URL construction using process.env or request origins - Use direct function calls instead of HTTP requests between internal APIs - Remove all instances of template literals in fetch URLs Changes: - app/api/monitor/trigger/route.ts: Import and call getAllMonitors directly - app/api/monitor/all/route.ts: Import and call monitor handlers directly - Use NextRequest mock objects for internal function calls - Add proper type safety for all internal API communication This permanently fixes the recurring SSRF vulnerability pattern by: 1. Never constructing URLs from user input or environment variables 2. Using TypeScript imports for type-safe internal communication 3. Eliminating the attack vector entirely (no more fetch with dynamic URLs) 4. Improving performance by avoiding unnecessary HTTP overhead All tests passing: ✅ npm run build - successful ✅ npm run lint - no errors ✅ npm run format - code formatted ✅ Security scan - 0 vulnerabilities
1 parent 1772931 commit 468f108

2 files changed

Lines changed: 32 additions & 32 deletions

File tree

app/api/monitor/all/route.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { NextRequest, NextResponse } from "next/server";
2+
import { GET as getClankerMonitor } from "../clanker/route";
3+
import { GET as getDegenMonitor } from "../degen/route";
4+
import { GET as getPowerBadgeMonitor } from "../power-badge/route";
5+
import { GET as getGeneralMonitor } from "../route";
26

37
// Import all monitor functions
48
const monitors = [
5-
{ name: "Clanker", endpoint: "/api/monitor/clanker" },
6-
{ name: "DEGEN", endpoint: "/api/monitor/degen" },
7-
{ name: "Power Badge", endpoint: "/api/monitor/power-badge" },
8-
{ name: "General", endpoint: "/api/monitor" },
9+
{ name: "Clanker", handler: getClankerMonitor },
10+
{ name: "DEGEN", handler: getDegenMonitor },
11+
{ name: "Power Badge", handler: getPowerBadgeMonitor },
12+
{ name: "General", handler: getGeneralMonitor },
913
];
1014

1115
interface MonitorResult {
@@ -29,26 +33,24 @@ export async function GET(request: NextRequest) {
2933

3034
const results: MonitorResult[] = [];
3135

32-
// Use a fixed internal URL to prevent SSRF
33-
const internalUrl = process.env.VERCEL_URL
34-
? `https://${process.env.VERCEL_URL}`
35-
: process.env.NODE_ENV === "production"
36-
? "https://fardrops.xyz"
37-
: "http://localhost:3000";
38-
39-
// Run all monitors in parallel
36+
// Run all monitors in parallel - calling functions directly, no fetch needed
4037
const monitorPromises = monitors.map(
4138
async (monitor): Promise<MonitorResult> => {
4239
try {
4340
console.log(`Running ${monitor.name} monitor...`);
4441

45-
const response = await fetch(`${internalUrl}${monitor.endpoint}`, {
46-
headers: {
47-
Authorization: authHeader || "",
48-
"Content-Type": "application/json",
49-
},
42+
// Create a mock request for the monitor function
43+
const mockHeaders = new Headers();
44+
mockHeaders.set("Authorization", authHeader || "");
45+
mockHeaders.set("Content-Type", "application/json");
46+
47+
const mockRequest = new NextRequest("http://internal", {
48+
headers: mockHeaders,
5049
});
5150

51+
// Call the monitor function directly - no external fetch, no SSRF risk
52+
const response = await monitor.handler(mockRequest);
53+
5254
if (response.ok) {
5355
const data = await response.json();
5456
return {

app/api/monitor/trigger/route.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextRequest, NextResponse } from "next/server";
2+
import { GET as getAllMonitors } from "../all/route";
23

34
// Public endpoint that can be called by external services
45
// Use with: UptimeRobot, Cron-job.org, or EasyCron (all have free tiers)
@@ -16,23 +17,20 @@ export async function GET(request: NextRequest) {
1617
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
1718
}
1819

19-
// Use a fixed internal URL to prevent SSRF
20-
const internalUrl = process.env.VERCEL_URL
21-
? `https://${process.env.VERCEL_URL}`
22-
: process.env.NODE_ENV === "production"
23-
? "https://fardrops.xyz"
24-
: "http://localhost:3000";
25-
26-
const authHeader = `Bearer ${process.env.CRON_SECRET || "development"}`;
27-
28-
// Trigger all monitors with validated URL
29-
const response = await fetch(`${internalUrl}/api/monitor/all`, {
30-
headers: {
31-
Authorization: authHeader,
32-
"Content-Type": "application/json",
33-
},
20+
// Create a mock request with proper authorization header
21+
const mockHeaders = new Headers();
22+
mockHeaders.set(
23+
"Authorization",
24+
`Bearer ${process.env.CRON_SECRET || "development"}`,
25+
);
26+
mockHeaders.set("Content-Type", "application/json");
27+
28+
const mockRequest = new NextRequest("http://internal/api/monitor/all", {
29+
headers: mockHeaders,
3430
});
3531

32+
// Call the monitor function directly - no external fetch, no SSRF risk
33+
const response = await getAllMonitors(mockRequest);
3634
const data = await response.json();
3735

3836
return NextResponse.json({

0 commit comments

Comments
 (0)