🛡️ Sentinel: [HIGH] Fix SSRF vulnerability in bulk lookup API#158
🛡️ Sentinel: [HIGH] Fix SSRF vulnerability in bulk lookup API#158aicoder2009 wants to merge 1 commit into
Conversation
Removed dynamic loopback `fetch()` calls in `/api/lookup/bulk` that relied on the client-controlled `request.nextUrl.origin` (derived from the `Host` header). Replaced with direct, in-memory invocations of the underlying Next.js Route Handler functions using synthetic `NextRequest` objects to mitigate Host-header-based Server-Side Request Forgery (SSRF) vulnerabilities. Tested and verified in unit tests. Co-authored-by: aicoder2009 <127642633+aicoder2009@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR fixes an SSRF vulnerability in the ChangesSSRF Mitigation in Bulk Lookup
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/api/lookup/bulk/route.ts (1)
30-33:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard non-string batch items before
trim()to avoid whole-request 500s.Line 31 can throw when an item is not a string, which bypasses per-item failure handling and collapses the full batch into a 500 response. Validate/coerce item type before trimming.
Proposed fix
- const lookupPromises = items.map(async (item) => { - const trimmedItem = item.trim(); + const lookupPromises = items.map(async (item) => { + if (typeof item !== "string") { + return { input: String(item), success: false, error: "Input must be a string" }; + } + const trimmedItem = item.trim(); if (!trimmedItem) { return { input: item, success: false, error: "Empty input" }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/lookup/bulk/route.ts` around lines 30 - 33, The items.map callback (lookupPromises) calls item.trim() without ensuring item is a string which can throw for non-string entries and escalate to a 500; inside the map callback validate/coerce item to a string before trimming (e.g., check typeof item === "string" or coerce via String(item)), and for non-coercible values return the per-item error object ({ input: item, success: false, error: "Invalid input type" }) so the per-item failure handling remains intact — update the lookupPromises mapping logic (the async callback that creates trimmedItem) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/app/api/lookup/bulk/route.ts`:
- Around line 30-33: The items.map callback (lookupPromises) calls item.trim()
without ensuring item is a string which can throw for non-string entries and
escalate to a 500; inside the map callback validate/coerce item to a string
before trimming (e.g., check typeof item === "string" or coerce via
String(item)), and for non-coercible values return the per-item error object ({
input: item, success: false, error: "Invalid input type" }) so the per-item
failure handling remains intact — update the lookupPromises mapping logic (the
async callback that creates trimmedItem) accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 2cb495d8-be45-4931-95c5-34d99d9d428b
📒 Files selected for processing (3)
.jules/sentinel.mdsrc/app/api/lookup/bulk/route.test.tssrc/app/api/lookup/bulk/route.ts
🚨 Severity: HIGH
💡 Vulnerability: The
/api/lookup/bulkendpoint invoked internal API routes (url,doi,isbn) by dynamically constructing loopback URLs usingrequest.nextUrl.originand callingfetch(). Becauserequest.nextUrl.originis derived from the client-suppliedHostheader, an attacker could manipulate theHostheader (e.g.,Host: internal.server.local:8080) to trick the backend server into making POST requests to arbitrary internal network hosts, causing a Host-header Server-Side Request Forgery (SSRF) vulnerability.🎯 Impact: An attacker could bypass external firewalls and send POST requests to internal applications and services.
🔧 Fix: Replaced network-based
fetch()requests with direct imports and invocations of the underlying route handler functions. Created a syntheticNextRequestobject for each invocation to completely remove the network layer.✅ Verification: Ran
pnpm test:runwith fully refactored mocked vitest suite. Tests pass and SSRF pathway is eliminated.PR created automatically by Jules for task 4643558143494860932 started by @aicoder2009
Summary by CodeRabbit