Skip to content

fix(core): Bound git branch analysis to prevent subprocess exhaustion#2357

Open
Abhishek2005-ard wants to merge 8 commits into
nisshchayarathi:mainfrom
Abhishek2005-ard:fix/dos-unbounded-subprocess
Open

fix(core): Bound git branch analysis to prevent subprocess exhaustion#2357
Abhishek2005-ard wants to merge 8 commits into
nisshchayarathi:mainfrom
Abhishek2005-ard:fix/dos-unbounded-subprocess

Conversation

@Abhishek2005-ard

@Abhishek2005-ard Abhishek2005-ard commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a severe performance bottleneck and DoS vulnerability in the branch analysis phase.

By default, the application was executing a subprocess for every branch found in a repository to retrieve commit counts. For extraordinarily large repositories, this unbounded loop caused the server to hang or crash due to resource starvation.

This update strictly limits processing to the top 100 most recently updated branches by leveraging git for-each-ref --sort=-committerdate, ensuring system stability without sacrificing the relevance of the parsed metrics.

Related Issues

  • Resolves Security Audit Finding: Denial of Service (DoS) via Unbounded Subprocess Spawning.

Checklist

  • Process execution is strictly capped at MAX_BRANCHES_TO_PROCESS (100).
  • Branch resolution is sorted by recent activity so critical branches aren't truncated.
  • All existing test suites pass.

close #2354

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced security authorization checks for annotation access
    • Implemented safeguards to improve stability when processing repositories with numerous branches
    • Improved abort signal handling for repository operations
  • Dependencies

    • Updated bullmq to ^5.78.1
  • Tests

    • Enhanced test infrastructure with improved mocking and configuration strategies

@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

@Abhishek2005-ard is attempting to deploy a commit to the Nisshchaya's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds an IDOR authorization check to the annotations API route, caps branch processing in gitService at 100 to prevent subprocess-based DoS, makes @next/bundle-analyzer loading optional via try/catch, bumps bullmq to ^5.78.1, and fixes several test infrastructure issues including Jest module mappings, mock additions, and console spy hygiene.

Changes

Security, Stability & Test Infrastructure

Layer / File(s) Summary
IDOR authorization check on annotations GET
app/api/annotations/route.ts
GET queries prisma.repository.findFirst scoped to the authenticated userId; returns 403 if no matching repository is found before fetching annotations.
gitService: branch cap and early abort signal
lib/services/gitService.ts
Adds MAX_BRANCHES_TO_PROCESS = 100 constant, an immediate abort-signal check in cloneRepository(), a for-each-ref sorted query in getBranches() that stops collection at the cap, and a DoS-mitigation comment.
Optional bundle analyzer in next.config.js
next.config.js
@next/bundle-analyzer require is wrapped in try/catch; falls back to a no-op wrapper function so builds succeed without the package installed.
Test infrastructure fixes
jest.config.cjs, app/api/auth/sessions/__tests__/route.test.ts, app/api/upload/avatar/__tests__/route.test.ts, app/api/users/profile/__tests__/route.test.ts, lib/services/__tests__/data-residency.test.ts, lib/services/__tests__/repository-idor.test.ts, src/components/layout/__tests__/Navbar.test.tsx
Adds moduleNameMapper entries for jose, @panva/hkdf, and uuid; extends session Prisma mock with deleteMany; mocks @/lib/logger; adds console.error spy/restore in avatar and IDOR tests; adds DataResidencyRegion Prisma mock; updates Button mock to accept asChild.
Dependency bump
package.json
Updates bullmq from ^5.78.0 to ^5.78.1.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • nisshchayarathi/gitverse-nextjs#1469: Also modifies getBranches() in lib/services/gitService.ts to limit concurrent rev-list --count subprocess calls, directly related to the same DoS vector addressed here.

Suggested labels

bug, security, level:intermediate

🐇 A hop through the code, a cap on the branches,
No DoS today — the rabbit advances!
The annotations check who's asking the door,
Console spies cleaned up, tests behave once more.
try/catch for the analyzer, no crash to fear,
The warren stays safe — and the build stays clear! 🌿

🚥 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 'fix(core): Bound git branch analysis to prevent subprocess exhaustion' accurately and concisely describes the main change—implementing a processing limit to prevent DoS via subprocess exhaustion, matching the PR's primary objective.
Linked Issues check ✅ Passed All requirements from issue #2354 are met: a hard cap of 100 branches (MAX_BRANCHES_TO_PROCESS) is implemented in gitService.ts, branch processing uses git for-each-ref sorted by recent activity, and subprocess spawning is bounded to prevent resource exhaustion.
Out of Scope Changes check ✅ Passed All changes directly support the DoS vulnerability fix: core gitService bounds, test mocks to verify the fix, config updates for Jest module resolution, and authorization checks in the annotations route are all scoped to the security remediation.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added the GSSoC'26 Part of GirlScript Summer of Code 2026 label Jun 16, 2026
@github-actions

Copy link
Copy Markdown

🎉 Thanks for your contribution, @Abhishek2005-ard!

Your PR has passed our automated GSSoC quality checks. Here's a quick summary:

Check Status
PR description ✅ Provided
PR title ✅ Meaningful
Linked issue ✅ Found
Change size ✅ Looks good (97 lines across 19 file(s))

A maintainer will review your PR soon. Please be patient and available for feedback. 💪

GSSoC'26 automation · Maintainer: @nisshchayarathi

1 similar comment
@github-actions

Copy link
Copy Markdown

🎉 Thanks for your contribution, @Abhishek2005-ard!

Your PR has passed our automated GSSoC quality checks. Here's a quick summary:

Check Status
PR description ✅ Provided
PR title ✅ Meaningful
Linked issue ✅ Found
Change size ✅ Looks good (97 lines across 19 file(s))

A maintainer will review your PR soon. Please be patient and available for feedback. 💪

GSSoC'26 automation · Maintainer: @nisshchayarathi

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
app/api/auth/sessions/__tests__/route.test.ts (1)

17-17: ⚡ Quick win

Assert session.deleteMany is called in DELETE success path.

Line 17 adds the mock, but no test verifies the DELETE handler actually invokes it. Add a named mock and assert it in the "increments tokenVersion..." case so session invalidation remains covered.

Suggested patch
 const mockFindUnique = jest.fn();
 const mockFindMany = jest.fn();
 const mockUpdate = jest.fn();
+const mockDeleteMany = jest.fn();

 jest.mock("`@/lib/prisma`", () => ({
   __esModule: true,
   default: {
@@
     session: {
       findMany: (...args: any[]) => mockFindMany(...args),
-      deleteMany: jest.fn(),
+      deleteMany: (...args: any[]) => mockDeleteMany(...args),
     },
   },
 }));
@@
   it("increments tokenVersion and updates passwordChangedAt", async () => {
@@
     expect(mockUpdate).toHaveBeenCalledWith(
@@
     );
+    expect(mockDeleteMany).toHaveBeenCalledWith({ where: { userId: 1 } });
     const body = await response.json();
     expect(body.message).toContain("terminated");
   });
🤖 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 `@app/api/auth/sessions/__tests__/route.test.ts` at line 17, The deleteMany
mock is defined but not asserted anywhere, leaving session invalidation
uncovered by tests. Assign the jest.fn() call for deleteMany to a named variable
so it can be referenced, then add an assertion in the "increments
tokenVersion..." test case to verify that this deleteMany mock was called with
the expected arguments when the DELETE handler executes the session invalidation
path.
lib/services/__tests__/repository-idor.test.ts (1)

237-255: ⚡ Quick win

Guarantee spy cleanup with try/finally.

At Line 237 and Line 255, cleanup is not exception-safe. If any assertion fails before restore, console.error remains mocked for later tests.

Suggested patch
 const testInvalidRole = async (injectedRoleValue: any) => {
   const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {});
-  (prisma.repository.findUnique as jest.Mock).mockResolvedValue({
-    id: targetRepoId,
-    userId: directOwnerId,
-  });
-  (prisma.repositoryPolicyAssignment.findUnique as jest.Mock).mockResolvedValue({
-    organizationId: orgId,
-  });
-  (prisma.organizationMember.findUnique as jest.Mock).mockResolvedValue({
-    role: injectedRoleValue,
-  });
-
-  const result = await RepositoryAccess.checkAccess(targetRepoId, nonOwnerId);
-  expect(result.allowed).toBe(false);
-  expect(result.role).toBeUndefined();
-  expect(result.reason).toContain("Invalid organization role");
-  expect(result.repositoryExists).toBe(true);
-  
-  consoleErrorSpy.mockRestore();
+  try {
+    (prisma.repository.findUnique as jest.Mock).mockResolvedValue({
+      id: targetRepoId,
+      userId: directOwnerId,
+    });
+    (prisma.repositoryPolicyAssignment.findUnique as jest.Mock).mockResolvedValue({
+      organizationId: orgId,
+    });
+    (prisma.organizationMember.findUnique as jest.Mock).mockResolvedValue({
+      role: injectedRoleValue,
+    });
+
+    const result = await RepositoryAccess.checkAccess(targetRepoId, nonOwnerId);
+    expect(result.allowed).toBe(false);
+    expect(result.role).toBeUndefined();
+    expect(result.reason).toContain("Invalid organization role");
+    expect(result.repositoryExists).toBe(true);
+  } finally {
+    consoleErrorSpy.mockRestore();
+  }
 };
🤖 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 `@lib/services/__tests__/repository-idor.test.ts` around lines 237 - 255, The
consoleErrorSpy mock cleanup is not exception-safe because if any assertion
fails before reaching consoleErrorSpy.mockRestore(), the spy remains active and
affects subsequent tests. Wrap the code that performs the mocks and assertions
(from the prisma mock setup through the expect statements) in a try/finally
block, moving consoleErrorSpy.mockRestore() to the finally block to guarantee it
runs regardless of whether any test assertion throws an error.
lib/services/__tests__/data-residency.test.ts (1)

7-13: Use partial mock pattern to avoid brittle test when dependencies change.

The test currently mocks the entire @prisma/client module with only DataResidencyRegion. While the services being tested (region-router, compliance-enforcement, region-ai-router) currently import only DataResidencyRegion, this pattern becomes fragile if these services are updated to import additional exports (types, enums, etc.). Safer pattern: use jest.requireActual("@prisma/client") and override only DataResidencyRegion:

jest.mock("`@prisma/client`", () => ({
  ...jest.requireActual("`@prisma/client`"),
  DataResidencyRegion: {
    US: "US",
    EU: "EU",
    APAC: "APAC",
  },
}));
🤖 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 `@lib/services/__tests__/data-residency.test.ts` around lines 7 - 13, The
jest.mock for `@prisma/client` in the test file is incomplete and only defines
DataResidencyRegion, which makes the test brittle to future changes. If the
services being tested (region-router, compliance-enforcement, region-ai-router)
later import additional exports from `@prisma/client`, those imports will be
undefined. Fix this by using the partial mock pattern: spread all actual exports
from `@prisma/client` using jest.requireActual("`@prisma/client`") in the mock
callback, then override only the DataResidencyRegion object with the test
values. This preserves all other module exports while safely mocking only what
you need.
🤖 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.

Inline comments:
In `@app/api/annotations/route.ts`:
- Around line 20-31: Add strict numeric validation for the repositoryId
parameter at the beginning of the route handler, before any Prisma queries. Use
a validation method that rejects partial strings and returns NaN for non-numeric
inputs (such as checking if the string matches a numeric pattern or using
Number.isInteger after parsing). If repositoryId fails validation, return a 400
Bad Request response. Then store the validated numeric ID in a variable and
reuse it in both the findUnique query (around line 20) and the findMany query
(around line 31) instead of calling parseInt multiple times, which eliminates
the risk of accepting malformed input like "12abc" and ensures consistent
validation.

In `@lib/services/gitService.ts`:
- Around line 335-338: Move the abort check that validates opts?.signal?.aborted
to occur before the spawn call, rather than after. Currently the check happens
after the git clone subprocess has already been started, which wastes resources
for already-aborted operations. Relocate the conditional block containing the
opts?.signal?.aborted check with its reject call to execute before spawn is
invoked, ensuring that aborted signals are detected and handled before any
subprocess resources are allocated.
- Around line 435-451: The git for-each-ref command in the gitService.ts file
currently outputs all refs to stdout even though only MAX_BRANCHES_TO_PROCESS
entries are used. Add the --count=${MAX_BRANCHES_TO_PROCESS} flag to the
for-each-ref command array (alongside the existing flags like
--sort=-committerdate and --format) to limit the output at the Git command
level, preventing unnecessary work and memory consumption for repositories with
large ref sets.

In `@next.config.js`:
- Around line 17-23: The broad catch block in the withBundleAnalyzer
initialization swallows all exceptions, hiding legitimate configuration errors.
Narrow the catch block to specifically check if the caught error is a
MODULE_NOT_FOUND error (by examining the error code property); if it is, set
withBundleAnalyzer to the no-op fallback function, otherwise rethrow the error
to surface any genuine configuration issues.

---

Nitpick comments:
In `@app/api/auth/sessions/__tests__/route.test.ts`:
- Line 17: The deleteMany mock is defined but not asserted anywhere, leaving
session invalidation uncovered by tests. Assign the jest.fn() call for
deleteMany to a named variable so it can be referenced, then add an assertion in
the "increments tokenVersion..." test case to verify that this deleteMany mock
was called with the expected arguments when the DELETE handler executes the
session invalidation path.

In `@lib/services/__tests__/data-residency.test.ts`:
- Around line 7-13: The jest.mock for `@prisma/client` in the test file is
incomplete and only defines DataResidencyRegion, which makes the test brittle to
future changes. If the services being tested (region-router,
compliance-enforcement, region-ai-router) later import additional exports from
`@prisma/client`, those imports will be undefined. Fix this by using the partial
mock pattern: spread all actual exports from `@prisma/client` using
jest.requireActual("`@prisma/client`") in the mock callback, then override only
the DataResidencyRegion object with the test values. This preserves all other
module exports while safely mocking only what you need.

In `@lib/services/__tests__/repository-idor.test.ts`:
- Around line 237-255: The consoleErrorSpy mock cleanup is not exception-safe
because if any assertion fails before reaching consoleErrorSpy.mockRestore(),
the spy remains active and affects subsequent tests. Wrap the code that performs
the mocks and assertions (from the prisma mock setup through the expect
statements) in a try/finally block, moving consoleErrorSpy.mockRestore() to the
finally block to guarantee it runs regardless of whether any test assertion
throws an error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3bd31082-1dad-460f-a6ad-b0b6e2c223ff

📥 Commits

Reviewing files that changed from the base of the PR and between d53ee6b and 82e1ec4.

⛔ Files ignored due to path filters (5)
  • package-lock.json is excluded by !**/package-lock.json
  • public/uploads/avatars/1001/1780676986116_emhbae.jpg is excluded by !**/*.jpg
  • public/uploads/avatars/1001/1781627118139_jtnw3e.jpg is excluded by !**/*.jpg
  • public/uploads/avatars/1001/1781627224731_8t7syn.jpg is excluded by !**/*.jpg
  • public/uploads/avatars/1001/1781627523291_lqgcv7.jpg is excluded by !**/*.jpg
📒 Files selected for processing (14)
  • app/api/annotations/route.ts
  • app/api/auth/sessions/__tests__/route.test.ts
  • app/api/upload/avatar/__tests__/route.test.ts
  • app/api/users/profile/__tests__/route.test.ts
  • diff.txt
  • jest.config.cjs
  • lib/services/__tests__/data-residency.test.ts
  • lib/services/__tests__/repository-idor.test.ts
  • lib/services/gitService.ts
  • next.config.js
  • package.json
  • prs.json
  • src/components/layout/__tests__/Navbar.test.tsx
  • test-results.json

Comment on lines +20 to 31
id: parseInt(repositoryId),
userId: user.userId,
}
});

if (!repo) {
return NextResponse.json({ error: "Repository not found or access denied" }, { status: 403 });
}

const annotations = await prisma.mapAnnotation.findMany({
where: {
repositoryId: parseInt(repositoryId),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate repositoryId once with strict numeric checks before Prisma queries.

parseInt() on Line 20 and Line 31 can accept partial values (e.g. "12abc") and can also yield NaN, which can turn malformed client input into a server-side failure path instead of a clean 400.

Suggested fix
     const { searchParams } = new URL(request.url);
     const repositoryId = searchParams.get("repositoryId");

     if (!repositoryId) {
       return NextResponse.json({ error: "repositoryId is required" }, { status: 400 });
     }
+    const repositoryIdNum = Number(repositoryId);
+    if (!Number.isInteger(repositoryIdNum) || repositoryIdNum <= 0) {
+      return NextResponse.json({ error: "Invalid repositoryId" }, { status: 400 });
+    }

     // Verify user has access to the repository to prevent IDOR
     const repo = await prisma.repository.findFirst({
       where: {
-        id: parseInt(repositoryId),
+        id: repositoryIdNum,
         userId: user.userId,
       }
     });

     if (!repo) {
       return NextResponse.json({ error: "Repository not found or access denied" }, { status: 403 });
     }

     const annotations = await prisma.mapAnnotation.findMany({
       where: {
-        repositoryId: parseInt(repositoryId),
+        repositoryId: repositoryIdNum,
       },
🤖 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 `@app/api/annotations/route.ts` around lines 20 - 31, Add strict numeric
validation for the repositoryId parameter at the beginning of the route handler,
before any Prisma queries. Use a validation method that rejects partial strings
and returns NaN for non-numeric inputs (such as checking if the string matches a
numeric pattern or using Number.isInteger after parsing). If repositoryId fails
validation, return a 400 Bad Request response. Then store the validated numeric
ID in a variable and reuse it in both the findUnique query (around line 20) and
the findMany query (around line 31) instead of calling parseInt multiple times,
which eliminates the risk of accepting malformed input like "12abc" and ensures
consistent validation.

Comment on lines +335 to +338
if (opts?.signal?.aborted) {
reject(new Error("Repository clone aborted"));
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move the abort precheck before spawning git clone.

The check on Line 335 happens after Line 304 already starts the subprocess, so already-aborted jobs can still consume process/resources briefly. Put the abort guard before spawn(...).

Suggested fix
     return new Promise((resolve, reject) => {
+      if (opts?.signal?.aborted) {
+        reject(new Error("Repository clone aborted"));
+        return;
+      }
+
       const child = spawn("git", args, {
         stdio: ["ignore", "pipe", "pipe"],
         env: {
           ...process.env,
           GIT_TERMINAL_PROMPT: "0",
           GCM_INTERACTIVE: "Never",
           GIT_LFS_SKIP_SMUDGE: "1",
         },
         timeout: GIT_CLONE_TIMEOUT_MS,
         signal: opts?.signal,
       });
...
-      if (opts?.signal?.aborted) {
-        reject(new Error("Repository clone aborted"));
-        return;
-      }
🤖 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 `@lib/services/gitService.ts` around lines 335 - 338, Move the abort check that
validates opts?.signal?.aborted to occur before the spawn call, rather than
after. Currently the check happens after the git clone subprocess has already
been started, which wastes resources for already-aborted operations. Relocate
the conditional block containing the opts?.signal?.aborted check with its reject
call to execute before spawn is invoked, ensuring that aborted signals are
detected and handled before any subprocess resources are allocated.

Comment on lines +435 to +451
[
"for-each-ref",
"--sort=-committerdate",
"--format=%(refname:short)|%(committerdate:iso)|%(objectname)",
"refs/heads/",
"refs/remotes/origin/",
],
{ timeout: DEFAULT_GIT_TIMEOUT_MS, signal },
);

const lines = stdout.trim().split("\n").filter(Boolean);
const seenBranches = new Set<string>();
const refEntries: { name: string; fullName: string; date: string }[] = [];
let refEntries: { name: string; fullName: string; date: string }[] = [];

for (const line of lines) {
if (refEntries.length >= MAX_BRANCHES_TO_PROCESS) break;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does git for-each-refsupport--count=, and what is the minimum Git version that supports it?

💡 Result:

Yes, the git for-each-ref command supports the --count= option, which instructs the command to stop after displaying the specified number of references [1][2]. This option has been available since the command was first introduced. Historical records indicate that the --count option was part of the original implementation of git-for-each-ref [3], which was merged into the Git codebase in 2005 (early versions of Git) [3]. Therefore, it is supported in all versions of Git that include this command [3].

Citations:


Add --count flag to bound for-each-ref output at the Git command level.

Currently, for-each-ref emits all refs to stdout even though only the first MAX_BRANCHES_TO_PROCESS entries are used in JS. For repositories with large ref sets, this leaves git's upstream work and memory consumption unbounded. Add --count=${MAX_BRANCHES_TO_PROCESS} directly to the command to limit output at the source.

Suggested fix
       const { stdout } = await this.spawnGit(
         [
           "for-each-ref",
           "--sort=-committerdate",
+          `--count=${MAX_BRANCHES_TO_PROCESS}`,
           "--format=%(refname:short)|%(committerdate:iso)|%(objectname)",
           "refs/heads/",
           "refs/remotes/origin/",
         ],
         { timeout: DEFAULT_GIT_TIMEOUT_MS, signal },
       );
🤖 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 `@lib/services/gitService.ts` around lines 435 - 451, The git for-each-ref
command in the gitService.ts file currently outputs all refs to stdout even
though only MAX_BRANCHES_TO_PROCESS entries are used. Add the
--count=${MAX_BRANCHES_TO_PROCESS} flag to the for-each-ref command array
(alongside the existing flags like --sort=-committerdate and --format) to limit
the output at the Git command level, preventing unnecessary work and memory
consumption for repositories with large ref sets.

Comment thread next.config.js
Comment on lines +17 to +23
try {
withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
} catch {
withBundleAnalyzer = (config) => config;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether next.config.js has a broad catch and no MODULE_NOT_FOUND guard.
# Expected for current code: broad catch present, no explicit module-not-found filter.

fd -a "^next\\.config\\.js$" | xargs -I{} sh -c '
  echo "== {} ==";
  rg -n "try|catch|`@next/bundle-analyzer`|MODULE_NOT_FOUND" "{}";
'

Repository: nisshchayarathi/gitverse-nextjs

Length of output: 198


Narrow the fallback to only missing-module errors.

The broad catch block at line 21 swallows all exceptions, which can hide legitimate analyzer configuration errors and make failures nondeterministic. Catch only MODULE_NOT_FOUND for @next/bundle-analyzer, and rethrow everything else.

Suggested patch
 let withBundleAnalyzer;
 try {
   withBundleAnalyzer = require('`@next/bundle-analyzer`')({
     enabled: process.env.ANALYZE === 'true',
   });
-} catch {
-  withBundleAnalyzer = (config) => config;
+} catch (error) {
+  const isMissingAnalyzer =
+    error &&
+    error.code === 'MODULE_NOT_FOUND' &&
+    typeof error.message === 'string' &&
+    error.message.includes('`@next/bundle-analyzer`');
+
+  if (!isMissingAnalyzer) {
+    throw error;
+  }
+
+  withBundleAnalyzer = (config) => config;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
} catch {
withBundleAnalyzer = (config) => config;
}
try {
withBundleAnalyzer = require('`@next/bundle-analyzer`')({
enabled: process.env.ANALYZE === 'true',
});
} catch (error) {
const isMissingAnalyzer =
error &&
error.code === 'MODULE_NOT_FOUND' &&
typeof error.message === 'string' &&
error.message.includes('`@next/bundle-analyzer`');
if (!isMissingAnalyzer) {
throw error;
}
withBundleAnalyzer = (config) => config;
}
🤖 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 `@next.config.js` around lines 17 - 23, The broad catch block in the
withBundleAnalyzer initialization swallows all exceptions, hiding legitimate
configuration errors. Narrow the catch block to specifically check if the caught
error is a MODULE_NOT_FOUND error (by examining the error code property); if it
is, set withBundleAnalyzer to the no-op fallback function, otherwise rethrow the
error to surface any genuine configuration issues.

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

Labels

GSSoC'26 Part of GirlScript Summer of Code 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Denial of Service (DoS) via unbounded subprocess spawning in Git branch analysis

1 participant