Skip to content

πŸ›‘οΈ Sentinel: [CRITICAL] Fix SSRF in bulk lookup API#138

Open
aicoder2009 wants to merge 1 commit into
mainfrom
sentinel-fix-bulk-lookup-ssrf-4832378979654159316
Open

πŸ›‘οΈ Sentinel: [CRITICAL] Fix SSRF in bulk lookup API#138
aicoder2009 wants to merge 1 commit into
mainfrom
sentinel-fix-bulk-lookup-ssrf-4832378979654159316

Conversation

@aicoder2009

@aicoder2009 aicoder2009 commented May 31, 2026

Copy link
Copy Markdown
Owner

🚨 Severity: CRITICAL
πŸ’‘ Vulnerability: The application was using fetch(request.nextUrl.origin + endpoint) to multiplex concurrent metadata lookups for citations. Because the Next.js request.nextUrl.origin inherits the Host header from the client request, an attacker could spoof the Host header, forcing the application to execute a Server-Side Request Forgery (SSRF) against an arbitrary host.
🎯 Impact: Allowed unauthenticated external routing, possible internal network mapping, and authorization bypasses.
πŸ”§ Fix: Replaced the loopback fetch calls with direct function invocation of the imported local POST route handlers, passing a safe, statically generated NextRequest object (http://localhost).
βœ… Verification: Unit tests in src/app/api/lookup/bulk/route.test.ts have been rewritten to mock local functions instead of global.fetch. All 652 tests pass successfully.


PR created automatically by Jules for task 4832378979654159316 started by @aicoder2009

Summary by CodeRabbit

  • Documentation

    • Added security guidance for preventing SSRF attacks in API requests.
  • Bug Fixes & Improvements

    • Updated bulk lookup API to improve request routing reliability and security.

Refactored `/api/lookup/bulk/route.ts` to execute internal Next.js Route Handlers (`POST` methods for DOI, URL, ISBN) directly by constructing a synthetic `NextRequest` instead of performing a loopback `fetch` using the user-controllable `request.nextUrl.origin`.

This closes a critical SSRF vulnerability where an attacker could have supplied a custom `Host` header to bypass authorization or redirect traffic over the internet.

Also updated Vitest tests to directly mock the internal route handler exports (`POST`) rather than mocking `global.fetch`.

Co-authored-by: aicoder2009 <127642633+aicoder2009@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

πŸ‘‹ 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 @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings May 31, 2026 06:15
@vercel

vercel Bot commented May 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
opencitation Ready Ready Preview, Comment May 31, 2026 6:16am

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

πŸ“ Walkthrough

Walkthrough

Bulk lookup API refactored to invoke sub-route handlers directly instead of making HTTP requests, eliminating SSRF risk from internal fetch patterns. Security guidance added to sentinel documentation. Tests updated to mock per-route handlers instead of global fetch.

Changes

Bulk lookup SSRF mitigation

Layer / File(s) Summary
Security guidance documentation
.jules/sentinel.md
Sentinel documentation updated with guidance on preventing SSRF via internal fetch loops, recommending direct invocation of internal route handlers instead of fetching based on request.nextUrl.origin.
Bulk lookup handler refactoring
src/app/api/lookup/bulk/route.ts
Bulk lookup route imports per-type lookup handlers (lookupUrl, lookupDoi, lookupIsbn) and refactors per-item processing to select the appropriate handler via regex detection, construct a synthetic NextRequest, and invoke the handler directly instead of building and fetching to derived API endpoints.
Test updates for direct invocation
src/app/api/lookup/bulk/route.test.ts
Test suite updated to mock per-route handlers instead of global.fetch, setting up mock return values and asserting handler invocations across URL/DOI/ISBN routing, failure paths, and batch processing scenarios.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Hops past the fetch and into the fold,
Direct invocationβ€”bold and cold,
No SSRF shadows, no origin sleight,
The handlers embrace, and the routes shine bright!
Bulk lookups now guard what we hold. πŸ›‘οΈ

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title directly addresses the main change: fixing an SSRF vulnerability in the bulk lookup API, which is the core objective of this PR.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
πŸ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel-fix-bulk-lookup-ssrf-4832378979654159316

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an SSRF risk in the bulk lookup API by replacing Host-header-derived loopback fetch calls with direct invocation of local lookup route handlers.

Changes:

  • Imports URL, DOI, and ISBN lookup POST handlers directly in the bulk lookup route.
  • Replaces dynamic-origin internal HTTP requests with synthetic NextRequest objects.
  • Updates bulk lookup tests to mock route handlers instead of global.fetch, and adds a Sentinel learning entry.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/app/api/lookup/bulk/route.ts Removes request.nextUrl.origin usage and calls local lookup handlers directly.
src/app/api/lookup/bulk/route.test.ts Updates tests to mock imported lookup handlers.
.jules/sentinel.md Documents the SSRF prevention lesson for future development.

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .jules/sentinel.md
**Vulnerability:** The application was using the `marked` library to parse Markdown content into HTML (in `src/app/docs/changelog/page.tsx` and `src/lib/docs.ts`) and subsequently rendering it using `dangerouslySetInnerHTML` without proper sanitization.
**Learning:** `marked` does not sanitize HTML by default. While this may seem safe for trusted inputs (like internal docs or GitHub releases), if malicious input manages to enter these sources, it leads directly to an XSS vulnerability.
**Prevention:** The output of `marked` (or any markdown parser) must always be wrapped with `DOMPurify.sanitize()` (using `isomorphic-dompurify` for SSR) before being passed to `dangerouslySetInnerHTML`.
## 2025-05-31 - Prevent SSRF via Internal Fetch Loops

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/app/api/lookup/bulk/route.test.ts (1)

62-72: ⚑ Quick win

Consider verifying request body contents in handler assertions.

The tests verify that the correct handlers are called (mockLookupUrl, mockLookupDoi, mockLookupIsbn) but don't assert that they receive the expected request bodies. Adding assertions for the request body would strengthen test coverage.

πŸ§ͺ Example: Assert request body for URL handler
   it('routes URLs to /api/lookup/url', async () => {
     (mockLookupUrl as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
       ok: true,
       json: async () => ({ data: { title: 'Example Page' } }),
     });
     const response = await POST(makeRequest({ items: ['https://example.com'] }));
     const data = await response.json();
     expect(data.results[0].success).toBe(true);
     expect(data.results[0].data.title).toBe('Example Page');
     expect(mockLookupUrl).toHaveBeenCalled();
+    const callArg = (mockLookupUrl as unknown as ReturnType<typeof vi.fn>).mock.calls[0][0];
+    const body = await callArg.json();
+    expect(body).toEqual({ url: 'https://example.com' });
   });

Also applies to: 74-83, 85-94

πŸ€– 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.test.ts` around lines 62 - 72, The test
currently checks that mockLookupUrl/mockLookupDoi/mockLookupIsbn were called but
not what they were called with; update the tests in
src/app/api/lookup/bulk/route.test.ts (the cases that call
POST(makeRequest(...))) to assert the request body passed to the handler mocks
by inspecting their call args (e.g.,
expect(mockLookupUrl).toHaveBeenCalledWith(expect.objectContaining({ body: /*
expected payload or parsed JSON */ })) or asserting the parsed body arg
directly), and add equivalent assertions for mockLookupDoi and mockLookupIsbn so
each handler is verified to receive the exact items payload sent by makeRequest.
src/app/api/lookup/bulk/route.ts (1)

35-35: πŸ’€ Low value

Add type annotation to handler variable.

The handler variable lacks a type annotation, reducing type safety. Consider adding an explicit type to ensure only valid route handlers are assigned.

πŸ“ Suggested type annotation
-        let handler;
+        let handler: (req: NextRequest) => Promise<NextResponse>;
         let body: object;
πŸ€– 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` at line 35, The local variable handler in
src/app/api/lookup/bulk/route.ts is missing an explicit type; add a type
annotation such as a request-to-response async function signature (for example,
(request: Request) => Promise<Response>) or the project's route handler type so
only valid handlers can be assigned to handler, and update any assigned
functions to conform to that signature (ensure imports/types like
Request/Response or NextRequest/NextResponse are used consistently with existing
route handlers).
πŸ€– 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.

Nitpick comments:
In `@src/app/api/lookup/bulk/route.test.ts`:
- Around line 62-72: The test currently checks that
mockLookupUrl/mockLookupDoi/mockLookupIsbn were called but not what they were
called with; update the tests in src/app/api/lookup/bulk/route.test.ts (the
cases that call POST(makeRequest(...))) to assert the request body passed to the
handler mocks by inspecting their call args (e.g.,
expect(mockLookupUrl).toHaveBeenCalledWith(expect.objectContaining({ body: /*
expected payload or parsed JSON */ })) or asserting the parsed body arg
directly), and add equivalent assertions for mockLookupDoi and mockLookupIsbn so
each handler is verified to receive the exact items payload sent by makeRequest.

In `@src/app/api/lookup/bulk/route.ts`:
- Line 35: The local variable handler in src/app/api/lookup/bulk/route.ts is
missing an explicit type; add a type annotation such as a request-to-response
async function signature (for example, (request: Request) => Promise<Response>)
or the project's route handler type so only valid handlers can be assigned to
handler, and update any assigned functions to conform to that signature (ensure
imports/types like Request/Response or NextRequest/NextResponse are used
consistently with existing route handlers).

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c439ccd-c300-4cee-81ec-cb84ab495e22

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between b872925 and be3e519.

πŸ“’ Files selected for processing (3)
  • .jules/sentinel.md
  • src/app/api/lookup/bulk/route.test.ts
  • src/app/api/lookup/bulk/route.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants