Skip to content

fix: replace mock data with Prisma DB queries in 6 API routes#52

Closed
qianghan wants to merge 7 commits into
livepeer:developfrom
qianghan:fix/mock-data-to-prisma
Closed

fix: replace mock data with Prisma DB queries in 6 API routes#52
qianghan wants to merge 7 commits into
livepeer:developfrom
qianghan:fix/mock-data-to-prisma

Conversation

@qianghan
Copy link
Copy Markdown
Contributor

@qianghan qianghan commented Feb 11, 2026

Summary

Several Next.js API routes returned hardcoded mock data instead of querying the database, even though Prisma models existed for the data. This is the same class of bug as #48 (capacity planner persistence).

Routes migrated to Prisma:

  • developer/models/route.ts → queries DevApiAIModel table with type-safe Prisma.DevApiAIModelWhereInput
  • developer/models/[id]/route.ts → queries DevApiAIModel by ID with gateway count
  • developer/models/[id]/gateways/route.ts → queries DevApiGatewayOffer table
  • developer/keys/route.ts → validates model/gateway existence against DB (was using mock getModel/getGatewayOffer)
  • integrations/route.ts → queries IntegrationConfig table (falls back to default catalogue if empty)
  • plugin-publisher/stats/[packageName]/route.ts → computes real stats from PluginVersion.downloads and PluginInstallation data instead of Math.random()

Also identified (lower priority, no DB model):

  • network-analytics/* routes (4 routes) — pure display/analytics, no Prisma model
  • orchestrator-manager/orchestrators/route.ts — no Prisma model
  • releases/route.ts — no Prisma model (future: GitHub API)

Closes #51

Test plan

  • Verify developer models list loads from database
  • Verify single model detail page works
  • Verify gateway offers load for a model
  • Verify API key creation validates model/gateway against DB
  • Verify integrations page loads (with and without IntegrationConfig rows)
  • Verify plugin publisher stats show real data

Made with Cursor

Summary by CodeRabbit

  • Refactor

    • Developer APIs migrated from in-memory data to database-backed queries so models, gateways and integrations reflect live data and counts; model serialization standardized.
  • New Features

    • Integrations return enriched catalogue metadata and configured status.
    • Plugin publisher stats show a real 30‑day installs timeline and accurate totals.
    • Model/gateway endpoints surface current gateway offers and counts.
  • Bug Fixes

    • Improved JSON parsing and stricter input validation with clearer error responses.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 11, 2026

@seanhanca is attempting to deploy a commit to the Livepeer Foundation Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Multiple Next.js API routes were migrated from in-memory/mock data to Prisma-backed database queries, added JSON body parsing/validation and DB existence checks, introduced a serialiseModel helper, and adjusted some route signatures and typings (params/return types) where applicable.

Changes

Cohort / File(s) Summary
Developer keys & models
apps/web-next/src/app/api/v1/developer/keys/route.ts, apps/web-next/src/app/api/v1/developer/models/route.ts, apps/web-next/src/app/api/v1/developer/models/[id]/route.ts, apps/web-next/src/app/api/v1/developer/models/[id]/gateways/route.ts
Replaced mock helpers with Prisma (devApiAIModel, devApiGatewayOffer) queries, added DB existence checks and selects, explicit request JSON parsing/validation and typings, preserved auth/CSRF checks, and corrected gatewayOfferId wiring.
Integrations catalogue
apps/web-next/src/app/api/v1/integrations/route.ts
Replaced static INTEGRATIONS with DB-backed IntegrationConfig via Prisma; maps rows to catalogue entries enriched by INTEGRATION_META, applies display name overrides, and falls back to a default catalogue when DB rows are absent.
Plugin publisher stats
apps/web-next/src/app/api/v1/plugin-publisher/stats/[packageName]/route.ts
Replaced external/mock fetch with Prisma lookups and aggregations: fetch package versions and installs (30-day UTC window), compute total downloads/installs/versionsCount, and construct a deterministic 30-day per-day installs timeline.
Model serialization helper
apps/web-next/src/app/src/lib/api/models.ts, apps/web-next/src/lib/api/models.ts
Added and exported serialiseModel to map Prisma DevApiAIModel rows to frontend shape (nested costPerMin, gatewayCount from _count.gatewayOffers, preserves other fields).
Minor / signatures & imports
apps/web-next/src/app/api/... (multiple routes)
Updated several route function signatures to include explicit return types and typed params (e.g., Promise<NextResponse>, RouteParams), added prisma imports, and adjusted parameter names where applicable.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as "Next.js API Route"
  participant Auth as "Auth / Session"
  participant DB as "Prisma (Database)"
  Client->>API: HTTP request (GET/POST)
  API->>Auth: validate session / token / CSRF
  Auth-->>API: session (ok / unauthorized)
  alt session ok
    API->>DB: Prisma query (models, gatewayOffers, integrations, packages)
    DB-->>API: rows/results
    API->>API: parse & validate JSON, serialise rows to response shape
    API-->>Client: 200 + JSON
  else session missing/invalid
    API-->>Client: 401 / unauthorized
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

scope/shell, size/L

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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 clearly summarizes the main change: replacing mock data with Prisma DB queries across 6 API routes, which is the core focus of this pull request.
Linked Issues check ✅ Passed The PR successfully addresses all high-priority objectives from issue #51: migrates developer/models routes to Prisma, updates developer/keys validation, replaces integrations mock data, and replaces plugin-publisher stats with real data.
Out of Scope Changes check ✅ Passed All changes are within scope of issue #51. New helper function serialiseModel and modified method signatures support the Prisma migration objectives without introducing unrelated functionality.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/web-next/src/app/api/v1/integrations/route.ts`:
- Around line 23-39: The response for the DB path is missing the category and
description fields that the fallback catalogue includes; update the
prisma.integrationConfig.findMany call to select category and description (add
them to the select payload) and include them in the mapping where rows are
transformed (the rows.map(...) block) so the returned object includes type,
displayName, configured, category, and description, ensuring parity with the
fallback; alternatively, if you prefer to always omit these fields, remove them
from the fallback or normalize clients, but implement the select + mapping
change in prisma.integrationConfig.findMany and the mapping function to fix the
immediate mismatch.

In `@apps/web-next/src/app/api/v1/plugin-publisher/stats/`[packageName]/route.ts:
- Around line 58-71: The code mixes local-time midnight
(thirtyDaysAgo.setHours(0,0,0,0)) with UTC reads
(getUTCFullYear/getUTCMonth/getUTCDate) causing off-by-one-day errors; update
the date creation to use UTC consistently (e.g., set thirtyDaysAgo to UTC
midnight with setUTCHours(0,0,0,0) or construct via Date.UTC) and keep the
timeline loop using getUTCFullYear/getUTCMonth/getUTCDate so the generated date
strings and bucket boundaries are all computed in UTC (adjust the thirtyDaysAgo
initialization and leave the timeline formatting using the existing UTC
getters).
- Around line 38-40: The installations relation in the query currently selects
all installations; restrict it to the last 30 days by adding a where clause on
installations.installAt/installedAt (use the exact field name installedAt) such
as installedAt >= thirtyDaysAgoDate and keep the select { installedAt: true }
inside installations. Also remove the redundant recalculation of thirtyDaysAgo
later and reuse the existing thirtyDaysAgoDate variable used in the route
handler (e.g., in the GET handler/function where installations is queried) so
the query only returns 30 days of records and the same thirtyDaysAgoDate is used
throughout.
🧹 Nitpick comments (5)
apps/web-next/src/app/api/v1/developer/keys/route.ts (2)

82-93: Consider adding runtime type validation for request body fields.

The type assertion on lines 89-93 assumes the parsed JSON has the expected shape. While downstream checks verify required fields exist, adding explicit type guards or a schema validation library (e.g., Zod) would catch malformed input earlier and provide more specific error messages.


18-20: Clarify the truncation rationale for hashApiKey.

The hash is truncated to 16 hex characters (64 bits), which significantly reduces the entropy of SHA-256. If this is intentional for storage/display purposes and the full key is validated elsewhere, consider adding a brief comment explaining this design choice.

apps/web-next/src/app/api/v1/developer/models/route.ts (1)

77-89: Consider adding server-side pagination for scalability.

The current implementation fetches all matching models from the database. While this works for small datasets, it may become a performance concern as the model catalog grows. The parsePagination utility is available (used in keys/route.ts) and could be applied here for consistency.

apps/web-next/src/app/api/v1/developer/models/[id]/route.ts (1)

10-10: Consider extracting serialiseModel to a shared utility.

Importing from ../route creates coupling between route files. Moving serialiseModel to a shared location (e.g., @/lib/api/serializers or a dedicated models.utils.ts) would improve modularity and make it easier to reuse across the codebase.

apps/web-next/src/app/api/v1/plugin-publisher/stats/[packageName]/route.ts (1)

70-74: Consider removing unused downloads field from timeline or adding a clarifying comment.

The downloads field in timeline entries is always 0 since only installs are bucketed. If per-day download data isn't available, consider either removing the field to avoid confusion or adding a comment explaining the limitation.

Comment thread apps/web-next/src/app/api/v1/integrations/route.ts Outdated
Comment thread apps/web-next/src/app/api/v1/plugin-publisher/stats/[packageName]/route.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web-next/src/app/api/v1/plugin-publisher/stats/`[packageName]/route.ts:
- Around line 80-88: The current loop over pkg.installations uses Math.min(29,
Math.floor(...)) which folds out-of-range dayIndex values into bucket 29; change
it to compute dayIndex = Math.floor((instDate.getTime() -
thirtyDaysAgo.getTime()) / (24*60*60*1000)) and then only increment
timeline[dayIndex].installs when dayIndex >= 0 && dayIndex <= 29, removing the
Math.min clamp; ensure you still skip installs with no installedAt and keep
using timeline and pkg.installations as before.
🧹 Nitpick comments (1)
apps/web-next/src/app/api/v1/integrations/route.ts (1)

25-45: Consider simplifying the two-step array initialization.

The current approach creates DEFAULT_INTEGRATIONS with simple capitalization, then mutates it via forEach to apply display name overrides. This can be consolidated into a single pass.

♻️ Proposed refactor to eliminate the mutation
+const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
+  openai: 'OpenAI',
+  anthropic: 'Anthropic',
+  'aws-s3': 'AWS S3',
+  sendgrid: 'SendGrid',
+  stripe: 'Stripe',
+  twilio: 'Twilio',
+};
+
 // Fallback catalogue — returned only when the IntegrationConfig table is empty
 // (e.g. fresh deployment before seed). Each entry carries `configured: false`.
 const DEFAULT_INTEGRATIONS = Object.entries(INTEGRATION_META).map(([type, meta]) => ({
   type,
-  displayName: type.charAt(0).toUpperCase() + type.slice(1),
+  displayName: DISPLAY_NAME_OVERRIDES[type] ?? type.charAt(0).toUpperCase() + type.slice(1),
   configured: false,
   ...meta,
 }));
-
-// Override display names for entries that need special casing
-const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
-  openai: 'OpenAI',
-  anthropic: 'Anthropic',
-  'aws-s3': 'AWS S3',
-  sendgrid: 'SendGrid',
-  stripe: 'Stripe',
-  twilio: 'Twilio',
-};
-DEFAULT_INTEGRATIONS.forEach((i) => {
-  if (DISPLAY_NAME_OVERRIDES[i.type]) i.displayName = DISPLAY_NAME_OVERRIDES[i.type];
-});

seanhanca and others added 2 commits February 10, 2026 20:58
Migrate developer/models, developer/keys, integrations, and
plugin-publisher/stats routes from hardcoded mock data to real
database queries using Prisma.

Routes fixed:
- developer/models/route.ts: query DevApiAIModel table
- developer/models/[id]/route.ts: query DevApiAIModel by id
- developer/models/[id]/gateways/route.ts: query DevApiGatewayOffer
- developer/keys/route.ts: validate model/gateway against DB
- integrations/route.ts: query IntegrationConfig table
- plugin-publisher/stats/[packageName]/route.ts: compute stats from DB

Closes #51

Co-authored-by: Cursor <cursoragent@cursor.com>
- integrations/route.ts: ensure consistent response shape by enriching
  DB rows with category/description from static metadata map
- plugin-publisher/stats: filter installations to last 30 days in the
  Prisma query for better performance
- plugin-publisher/stats: use UTC consistently (setUTCDate/setUTCHours)
  to prevent off-by-one day errors from timezone mismatch

Co-authored-by: Cursor <cursoragent@cursor.com>
@qianghan qianghan force-pushed the fix/mock-data-to-prisma branch from 25a1db6 to 66bed62 Compare February 11, 2026 04:58
@qianghan
Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/web-next/src/app/api/v1/developer/keys/route.ts`:
- Around line 82-97: The request body parsing accepts truthy non-string values
for projectName, modelId, and gatewayId which can surface as Prisma errors;
validate each of body.projectName, body.modelId, and body.gatewayId are strings
and non-empty before proceeding (after body = await request.json()), and return
errors.badRequest with a clear message if any are missing or not a non-empty
string; update the validation logic around the variables projectName, modelId,
gatewayId (used later before Prisma calls) to enforce typeof === 'string' &&
value.trim().length > 0.

In `@apps/web-next/src/app/api/v1/plugin-publisher/stats/`[packageName]/route.ts:
- Around line 59-62: The summary "Active Installs" currently uses the all-time
counter pkg._count.installations which mismatches the 30-day timeline; change
totalInstalls to use the filtered installs array instead (replace the usage of
pkg._count.installations with the 30-day filtered collection pkg.installations)
so totalInstalls = pkg.installations.length and the summary aligns with the
timeline shown by versions and installations.
🧹 Nitpick comments (2)
apps/web-next/src/app/api/v1/integrations/route.ts (1)

27-45: Consider constructing DEFAULT_INTEGRATIONS with overrides in one pass to avoid mutation.

The current approach mutates DEFAULT_INTEGRATIONS after creation (line 43-45), which works but violates the expectation that a const array's contents remain stable. Consolidating the logic avoids the mutation and is slightly cleaner.

♻️ Proposed refactor
-// Fallback catalogue — returned only when the IntegrationConfig table is empty
-// (e.g. fresh deployment before seed). Each entry carries `configured: false`.
-const DEFAULT_INTEGRATIONS = Object.entries(INTEGRATION_META).map(([type, meta]) => ({
-  type,
-  displayName: type.charAt(0).toUpperCase() + type.slice(1),
-  configured: false,
-  ...meta,
-}));
-
 // Override display names for entries that need special casing
 const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
   openai: 'OpenAI',
   anthropic: 'Anthropic',
   'aws-s3': 'AWS S3',
   sendgrid: 'SendGrid',
   stripe: 'Stripe',
   twilio: 'Twilio',
 };
-DEFAULT_INTEGRATIONS.forEach((i) => {
-  if (DISPLAY_NAME_OVERRIDES[i.type]) i.displayName = DISPLAY_NAME_OVERRIDES[i.type];
-});
+
+// Fallback catalogue — returned only when the IntegrationConfig table is empty
+// (e.g. fresh deployment before seed). Each entry carries `configured: false`.
+const DEFAULT_INTEGRATIONS = Object.entries(INTEGRATION_META).map(([type, meta]) => ({
+  type,
+  displayName: DISPLAY_NAME_OVERRIDES[type] ?? type.charAt(0).toUpperCase() + type.slice(1),
+  configured: false,
+  ...meta,
+}));
apps/web-next/src/app/api/v1/developer/models/route.ts (1)

77-90: Consider adding pagination for scalability.

The query fetches all matching models without pagination. If the number of models grows significantly, this could impact response times and memory usage. Consider adding skip and take parameters.

If the dataset is expected to remain small, this is acceptable—but worth noting for future scalability.

Optional: Add pagination support
   const searchParams = request.nextUrl.searchParams;
   const type = searchParams.get('type');
   const featured = searchParams.get('featured');
   const realtime = searchParams.get('realtime');
+  const page = Math.max(1, parseInt(searchParams.get('page') ?? '1', 10));
+  const limit = Math.min(100, Math.max(1, parseInt(searchParams.get('limit') ?? '50', 10)));

   // ... where clause building ...

   const rows = await prisma.devApiAIModel.findMany({
     where,
     orderBy: [{ featured: 'desc' }, { name: 'asc' }],
     include: {
       _count: { select: { gatewayOffers: true } },
     },
+    skip: (page - 1) * limit,
+    take: limit,
   });

+  const total = await prisma.devApiAIModel.count({ where });
   const models = rows.map(serialiseModel);

   return success({
     models,
-    total: models.length,
+    total,
+    page,
+    limit,
   });

Comment thread apps/web-next/src/app/api/v1/developer/keys/route.ts
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 11, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 11, 2026

✅ Actions performed

Comments resolved and changes approved.

- stats route: replace Math.min(29, dayIndex) clamp with explicit
  bounds check (dayIndex >= 0 && dayIndex <= 29) to avoid folding
  out-of-range installs into the last bucket
- integrations route: consolidate DEFAULT_INTEGRATIONS array init
  into a single pass using DISPLAY_NAME_OVERRIDES inline

Co-authored-by: Cursor <cursoragent@cursor.com>
- keys route: enforce typeof string + non-empty validation for
  projectName, modelId, gatewayId before Prisma calls
- stats route: use 30-day filtered installations.length for
  totalInstalls instead of all-time _count to align with timeline
- stats route: remove unused _count.installations from query

Co-authored-by: Cursor <cursoragent@cursor.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 11, 2026
@qianghan
Copy link
Copy Markdown
Contributor Author

@github-copilot review

@seanhanca seanhanca requested a review from Copilot February 11, 2026 05:13
Copy link
Copy Markdown

Copilot AI left a comment

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 replaces hardcoded mock data with real Prisma database queries across 6 API routes, ensuring all developer-facing endpoints return live data instead of static fixtures.

Changes:

  • Migrated developer models, gateways, and API key routes to query DevApiAIModel and DevApiGatewayOffer tables
  • Replaced mock integration list with IntegrationConfig table queries, falling back to a default catalogue when empty
  • Implemented real plugin statistics by aggregating PluginVersion.downloads and PluginInstallation data instead of using Math.random()

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/web-next/src/app/api/v1/plugin-publisher/stats/[packageName]/route.ts Replaced random mock statistics with real 30-day installation timeline and download counts from database
apps/web-next/src/app/api/v1/integrations/route.ts Query IntegrationConfig table with static metadata enrichment, fallback to default catalogue
apps/web-next/src/app/api/v1/developer/models/route.ts Replaced in-memory models array with Prisma queries using type-safe WhereInput filters
apps/web-next/src/app/api/v1/developer/models/[id]/route.ts Fetch single model by ID from database with gateway count
apps/web-next/src/app/api/v1/developer/models/[id]/gateways/route.ts Query DevApiGatewayOffer table for model's gateway offers
apps/web-next/src/app/api/v1/developer/keys/route.ts Validate model and gateway existence against database instead of mock helpers

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const instDate = new Date(inst.installedAt);
const diffMs = instDate.getTime() - thirtyDaysAgo.getTime();
const dayIndex = Math.floor(diffMs / (24 * 60 * 60 * 1000));
if (dayIndex >= 0 && dayIndex <= 29) {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The upper bound check should be dayIndex < 30 instead of dayIndex <= 29. While mathematically equivalent, using < 30 is clearer and matches the loop condition on line 65 that creates 30 buckets (indices 0-29). This makes the boundary check more consistent and readable.

Suggested change
if (dayIndex >= 0 && dayIndex <= 29) {
if (dayIndex >= 0 && dayIndex < 30) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d6f3997 — changed to dayIndex < 30 for consistency with the loop creating 30 buckets.

};
}

export { serialiseModel };
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The serialiseModel function is exported but defined within this route file. If this is intended for reuse (as evidenced by the import in [id]/route.ts), consider moving it to a shared utility module like @/lib/api/models to better separate business logic from route handlers and improve maintainability.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d6f3997 — moved serialiseModel to @/lib/api/models.ts shared utility. Both models/route.ts and [id]/route.ts now import from there.

@seanhanca seanhanca requested a review from Copilot February 11, 2026 05:15
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const instDate = new Date(inst.installedAt);
const diffMs = instDate.getTime() - thirtyDaysAgo.getTime();
const dayIndex = Math.floor(diffMs / (24 * 60 * 60 * 1000));
if (dayIndex >= 0 && dayIndex <= 29) {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The condition dayIndex <= 29 should be dayIndex < 30 for consistency with the loop that creates 30 buckets (indices 0-29). While both work, using < 30 matches the loop boundary and is clearer.

Suggested change
if (dayIndex >= 0 && dayIndex <= 29) {
if (dayIndex >= 0 && dayIndex < 30) {

Copilot uses AI. Check for mistakes.
projectName,
modelId,
gatewayOfferId: gatewayId || undefined,
gatewayOfferId: gateway.id,
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The gatewayOfferId field is being set to gateway.id, but the query selects only { id: true } from DevApiGatewayOffer. This id represents the offer's unique identifier, not the gatewayId. The field should likely be set to the offer's ID (which appears correct) but verify the schema expects the offer ID rather than the gateway's string ID. If the schema expects the gateway's string identifier, this should be gatewayId instead.

Copilot uses AI. Check for mistakes.
- Move serialiseModel to shared @/lib/api/models utility module
- Use dayIndex < 30 instead of dayIndex <= 29 for consistency
  with the loop that creates 30 buckets (indices 0-29)

Co-authored-by: Cursor <cursoragent@cursor.com>
@qianghan
Copy link
Copy Markdown
Contributor Author

@copilot review

Co-authored-by: Cursor <cursoragent@cursor.com>
@seanhanca seanhanca requested a review from Copilot February 11, 2026 05:26
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const timeline: { date: string; downloads: number; installs: number }[] = [];
for (let i = 0; i < 30; i++) {
const d = new Date(thirtyDaysAgo);
d.setUTCDate(d.getUTCDate() + i);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Date mutation inside the loop reuses the same Date object, causing all 30 days to reference the final date. Create a new Date instance for each iteration: const d = new Date(thirtyDaysAgo.getTime() + i * 24 * 60 * 60 * 1000); or clone the date before mutation.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e0aedd6 — now uses timestamp arithmetic new Date(thirtyDaysAgo.getTime() + i * MS_PER_DAY) instead of mutating a Date clone.

Comment on lines +26 to +33
const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
openai: 'OpenAI',
anthropic: 'Anthropic',
'aws-s3': 'AWS S3',
sendgrid: 'SendGrid',
stripe: 'Stripe',
twilio: 'Twilio',
};
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The DISPLAY_NAME_OVERRIDES constant duplicates display names that are already defined in DEFAULT_INTEGRATIONS (line 37). This creates a maintenance burden where display names must be updated in two places. Consider removing DISPLAY_NAME_OVERRIDES and deriving display names directly from INTEGRATION_META entries or storing them in a single source.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e0aedd6 — merged DISPLAY_NAME_OVERRIDES into INTEGRATION_META as a displayName field. Single source of truth now.

Comment on lines +89 to +93
const { projectName, modelId, gatewayId } = body as {
projectName?: string;
modelId?: string;
gatewayId?: string;
};
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Type assertion bypasses TypeScript's type checking. Use a type guard or validation library (e.g., zod) to safely parse and validate the request body shape before destructuring.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e0aedd6 — removed the type assertion. Now extracts fields from body as unknown and validates with typeof checks before use.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web-next/src/app/api/v1/plugin-publisher/stats/`[packageName]/route.ts:
- Around line 45-48: The installations subquery currently filters only with
installedAt: { gte: thirtyDaysAgo } which can include future-dated rows; update
the query in route.ts (the installations select used to compute totalInstalls
and the similar block at lines ~56-60) to add an upper bound by also requiring
installedAt: { lte: now } (or Date.now()/new Date()) so the where becomes a
range between thirtyDaysAgo and now, ensuring future timestamps aren’t counted.
🧹 Nitpick comments (2)
apps/web-next/src/app/api/v1/developer/models/route.ts (2)

35-40: Consider supporting explicit false filter values.

Currently, featured=false and realtime=false query params are ignored since only 'true' is checked. If clients need to filter for non-featured/non-realtime models, this pattern won't work.

If intentional (filter only when requesting featured items), the current behavior is fine. Otherwise:

♻️ Optional: Support explicit false filters
     if (featured === 'true') {
       where.featured = true;
+    } else if (featured === 'false') {
+      where.featured = false;
     }
     if (realtime === 'true') {
       where.realtime = true;
+    } else if (realtime === 'false') {
+      where.realtime = false;
     }

50-55: Consider adding pagination for scalability.

The route fetches all matching models without limit. If the model catalog grows large, this could impact response times and memory usage. Pagination (limit/offset or cursor-based) would be beneficial.

This is not blocking for the current migration but worth considering as a follow-up.

Comment on lines +45 to +48
installations: {
where: { installedAt: { gte: thirtyDaysAgo } },
select: { installedAt: true },
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an upper bound to the installations query to avoid future-dated inflation.
totalInstalls is derived from the filtered installations list, but the query only enforces gte: thirtyDaysAgo. Any future-dated rows (clock skew or bad data) will be counted, even though the timeline excludes them. Consider bounding the query to now.

🛠️ Suggested fix
         installations: {
-          where: { installedAt: { gte: thirtyDaysAgo } },
+          where: { installedAt: { gte: thirtyDaysAgo, lte: now } },
           select: { installedAt: true },
         },

Also applies to: 56-60

🤖 Prompt for AI Agents
In `@apps/web-next/src/app/api/v1/plugin-publisher/stats/`[packageName]/route.ts
around lines 45 - 48, The installations subquery currently filters only with
installedAt: { gte: thirtyDaysAgo } which can include future-dated rows; update
the query in route.ts (the installations select used to compute totalInstalls
and the similar block at lines ~56-60) to add an upper bound by also requiring
installedAt: { lte: now } (or Date.now()/new Date()) so the where becomes a
range between thirtyDaysAgo and now, ensuring future timestamps aren’t counted.

- stats route: use timestamp arithmetic instead of Date mutation
  to avoid potential issues with reused Date objects
- integrations route: merge DISPLAY_NAME_OVERRIDES into INTEGRATION_META
  to eliminate duplicate display name source
- keys route: remove type assertion, use runtime typeof checks
  instead to validate body fields safely

Co-authored-by: Cursor <cursoragent@cursor.com>
@seanhanca seanhanca requested a review from Copilot February 11, 2026 05:31
installations: {
where: { installedAt: { gte: thirtyDaysAgo } },
select: { installedAt: true },
},
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.

The installations query only filters with a lower bound (gte: thirtyDaysAgo) without an upper bound, allowing future-dated installations to inflate totalInstalls statistics

Fix on Vercel

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to 76
// Aggregate real download counts from versions
const totalDownloads = pkg.versions.reduce((sum, v) => sum + (v.downloads ?? 0), 0);
// Use the filtered (30-day) installations count to align with the timeline
const totalInstalls = pkg.installations.length;
const versionsCount = pkg.versions.length;

for (let i = 29; i >= 0; i--) {
const date = new Date(now);
date.setDate(date.getDate() - i);
// Build a 30-day installation timeline from real installation data
// Create day buckets using UTC consistently
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const timeline: { date: string; downloads: number; installs: number }[] = [];
for (let i = 0; i < 30; i++) {
const d = new Date(thirtyDaysAgo.getTime() + i * MS_PER_DAY);
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
timeline.push({
date: date.toISOString().split('T')[0],
downloads: Math.floor(Math.random() * (baseDownloads / 30) + 1),
installs: Math.floor(Math.random() * (baseDownloads / 60) + 1),
date: `${y}-${m}-${day}`,
downloads: 0,
installs: 0,
});
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The response now returns timeline.downloads: 0 for every day (no code ever increments it), which is a behavior regression if clients expect a non-zero downloads series. Either (a) compute a meaningful per-day downloads series (requires per-day download data, or an intentional allocation strategy), or (b) remove/omit downloads from the timeline and/or rename the field to avoid implying it is populated.

Copilot uses AI. Check for mistakes.
Comment on lines 89 to 94
return success({
totalDownloads: pkg.downloads || 0,
totalInstalls: Math.floor((pkg.downloads || 0) * 0.3),
versionsCount: pkg.versions?.length || 1,
totalDownloads,
totalInstalls,
versionsCount,
timeline,
});
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The response now returns timeline.downloads: 0 for every day (no code ever increments it), which is a behavior regression if clients expect a non-zero downloads series. Either (a) compute a meaningful per-day downloads series (requires per-day download data, or an intentional allocation strategy), or (b) remove/omit downloads from the timeline and/or rename the field to avoid implying it is populated.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +48
installations: {
where: { installedAt: { gte: thirtyDaysAgo } },
select: { installedAt: true },
},
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This pulls every installation row within the 30-day window into memory and then buckets in JS. For packages with high volume, this will create large payloads and slow route execution. Prefer aggregating in the database (e.g., a Prisma groupBy on date truncation via raw SQL, or maintaining a daily rollup table) so the API only transfers 30 buckets rather than N installations.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +87
// Bucket installations into days
for (const inst of pkg.installations) {
if (!inst.installedAt) continue;
const instDate = new Date(inst.installedAt);
const diffMs = instDate.getTime() - thirtyDaysAgo.getTime();
const dayIndex = Math.floor(diffMs / (24 * 60 * 60 * 1000));
if (dayIndex >= 0 && dayIndex < 30) {
timeline[dayIndex].installs += 1;
}
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

This pulls every installation row within the 30-day window into memory and then buckets in JS. For packages with high volume, this will create large payloads and slow route execution. Prefer aggregating in the database (e.g., a Prisma groupBy on date truncation via raw SQL, or maintaining a daily rollup table) so the API only transfers 30 buckets rather than N installations.

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +23
/**
* Single source of truth for known integration metadata.
* The IntegrationConfig DB model stores only type, displayName, and configured.
* displayName, category, and description are enriched from this map so the
* response shape is consistent regardless of whether DB rows exist.
*/
const INTEGRATION_META: Record<string, { displayName: string; category: string; description: string }> = {
openai: { displayName: 'OpenAI', category: 'ai', description: 'GPT models for AI-powered features' },
anthropic: { displayName: 'Anthropic', category: 'ai', description: 'Claude AI models' },
'aws-s3': { displayName: 'AWS S3', category: 'storage', description: 'Amazon S3 for file storage' },
sendgrid: { displayName: 'SendGrid', category: 'email', description: 'Email delivery service' },
stripe: { displayName: 'Stripe', category: 'payments', description: 'Payment processing' },
twilio: { displayName: 'Twilio', category: 'communications', description: 'SMS and voice services' },
};
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The comment says metadata is the “single source of truth”, but displayName comes from the DB row while category/description come from the metadata map. This can produce inconsistent results (e.g., DB displayName differs from meta displayName, or meta is updated but DB lags). Consider making one authoritative: either always use meta.displayName when present (and only fall back to r.displayName when missing), or remove displayName from INTEGRATION_META if DB should own it.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +57
type: r.type,
displayName: r.displayName,
configured: r.configured,
category: meta?.category ?? 'other',
description: meta?.description ?? '',
};
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The comment says metadata is the “single source of truth”, but displayName comes from the DB row while category/description come from the metadata map. This can produce inconsistent results (e.g., DB displayName differs from meta displayName, or meta is updated but DB lags). Consider making one authoritative: either always use meta.displayName when present (and only fall back to r.displayName when missing), or remove displayName from INTEGRATION_META if DB should own it.

Copilot uses AI. Check for mistakes.
*/

/** Map a Prisma DevApiAIModel row to the shape the frontend expects. */
export function serialiseModel(m: {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The new helper is named serialiseModel (British spelling). If the codebase predominantly uses serialize* (American spelling) elsewhere, this will be easy to miss and can lead to inconsistent naming/searchability. Consider renaming to serializeModel for consistency.

Suggested change
export function serialiseModel(m: {
export function serializeModel(m: {

Copilot uses AI. Check for mistakes.
@qianghan
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/web-next/src/app/api/v1/developer/keys/route.ts`:
- Around line 82-100: The handler currently assumes request.json() returns an
object, which throws when body is null or an array; after parsing (body = await
request.json()), add an explicit guard: if (typeof body !== 'object' || body ===
null || Array.isArray(body)) return errors.badRequest('Invalid JSON payload:
expected object'); then proceed to read projectName, modelId, gatewayId (the
existing variables) so property access on body is safe; keep body typed as
Record<string, unknown> only after this guard or cast after validating its
shape.
🧹 Nitpick comments (2)
apps/web-next/src/app/api/v1/integrations/route.ts (1)

25-33: Ordering mismatch between fallback and DB-backed responses.

DEFAULT_INTEGRATIONS preserves INTEGRATION_META insertion order, but the DB query on line 38 orders by displayName: 'asc'. This creates inconsistent ordering between fresh deployments (fallback) and seeded databases, which could cause subtle UI shifts when the DB is first populated.

🔧 Proposed fix to sort DEFAULT_INTEGRATIONS
 // Fallback catalogue — returned only when the IntegrationConfig table is empty
 // (e.g. fresh deployment before seed). Each entry carries `configured: false`.
-const DEFAULT_INTEGRATIONS = Object.entries(INTEGRATION_META).map(([type, meta]) => ({
-  type,
-  displayName: meta.displayName,
-  configured: false,
-  category: meta.category,
-  description: meta.description,
-}));
+const DEFAULT_INTEGRATIONS = Object.entries(INTEGRATION_META)
+  .map(([type, meta]) => ({
+    type,
+    displayName: meta.displayName,
+    configured: false,
+    category: meta.category,
+    description: meta.description,
+  }))
+  .sort((a, b) => a.displayName.localeCompare(b.displayName));
apps/web-next/src/app/api/v1/plugin-publisher/stats/[packageName]/route.ts (1)

73-74: Consider documenting why downloads is always zero in the timeline.

The downloads field in each timeline entry is initialized to 0 but never populated. If this is intentional due to the data model only tracking total downloads per version (not per-day), a brief comment would clarify this for future maintainers.

Comment on lines +82 to +100
let body: Record<string, unknown>;
try {
body = await request.json();
} catch {
return errors.badRequest('Invalid JSON in request body');
}

if (!projectName || !modelId || !gatewayId) {
const projectName = body.projectName;
const modelId = body.modelId;
const gatewayId = body.gatewayId;

if (
typeof projectName !== 'string' ||
typeof modelId !== 'string' ||
typeof gatewayId !== 'string' ||
projectName.trim() === '' ||
modelId.trim() === '' ||
gatewayId.trim() === ''
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against non-object JSON bodies to avoid 500s.

If the JSON body is null or an array, property access throws and the handler returns a 500. Reject non-object payloads explicitly with a 400 before reading fields.

🛠️ Proposed fix
-    let body: Record<string, unknown>;
+    let body: unknown;
     try {
       body = await request.json();
     } catch {
       return errors.badRequest('Invalid JSON in request body');
     }
 
-    const projectName = body.projectName;
-    const modelId = body.modelId;
-    const gatewayId = body.gatewayId;
+    if (body === null || typeof body !== 'object' || Array.isArray(body)) {
+      return errors.badRequest('Request body must be a JSON object');
+    }
+
+    const { projectName, modelId, gatewayId } = body as Record<string, unknown>;
🤖 Prompt for AI Agents
In `@apps/web-next/src/app/api/v1/developer/keys/route.ts` around lines 82 - 100,
The handler currently assumes request.json() returns an object, which throws
when body is null or an array; after parsing (body = await request.json()), add
an explicit guard: if (typeof body !== 'object' || body === null ||
Array.isArray(body)) return errors.badRequest('Invalid JSON payload: expected
object'); then proceed to read projectName, modelId, gatewayId (the existing
variables) so property access on body is safe; keep body typed as Record<string,
unknown> only after this guard or cast after validating its shape.

@seanhanca
Copy link
Copy Markdown
Contributor

Closing in favor of same-repo PR to fix label/merge permissions.

@seanhanca seanhanca closed this Feb 11, 2026
eliteprox added a commit that referenced this pull request Feb 11, 2026
Address 103 high-severity CodeQL alerts across 34 files:

**Insecure temporary files (js/insecure-temporary-file) — 20 alerts:**
- Replace predictable temp paths with crypto.randomUUID() and fs.mkdtempSync()

**User-controlled bypass (js/user-controlled-bypass) — 14 alerts:**
- Add typeof checks for auth tokens, codes, emails, passwords
- Remove user-controlled skipVerification flag from publish endpoints
- Validate GitHub webhook headers with typeof guards
- Add OAuth error code allowlist in callback handler

**Remote property injection (js/remote-property-injection) — 6 alerts:**
- Block __proto__, constructor, prototype keys in config merge/assignment

**File system race (js/file-system-race) — 3 alerts:**
- Replace TOCTOU check-then-read with try/catch around direct operations

**Tainted format string (js/tainted-format-string) — 5 alerts:**
- Sanitize user input (strip control chars/newlines) before logging

**Incomplete sanitization + bad HTML filter (js/bad-tag-filter) — 2 alerts:**
- Replace single-pass regex HTML stripping with iterative loop

**Path injection (js/path-injection) — 20 alerts:**
- Add safeResolvePath() for storage paths
- Add sanitizePathComponent() for plugin/version path segments
- Add validateFilePath() for uploaded file paths

**SQL injection (js/sql-injection) — 5 alerts:**
- Add validateIdentifier() strict allowlist for DDL identifiers
- Sanitize database names, usernames, and passwords

**XSS through DOM (js/xss-through-dom) — 2 alerts:**
- Validate avatar URLs with URL constructor, block javascript: URIs

**Reflected XSS (js/reflected-xss) — 1 alert:**
- Validate plugin name against allowlist before HTML interpolation

**Insecure download (js/insecure-download) — 2 alerts:**
- Enforce HTTPS protocol validation before fetch calls

**Insecure randomness (js/insecure-randomness) — 1 alert:**
- Replace Math.random() with crypto.randomInt()

**Insufficient password hash (js/insufficient-password-hash) — 2 alerts:**
- Replace SHA-256 with crypto.scryptSync for API key hashing

**Missing rate limiting (js/missing-rate-limiting) — 13 alerts:**
- Add in-memory rate limiting to auth, registry, embed, and config routes

**Insecure Helmet configuration (js/insecure-helmet-configuration) — 2 alerts:**
- Replace disabled CSP with proper CSP directives

**Incomplete URL sanitization (js/incomplete-url-substring-sanitization) — 5 alerts:**
- Replace string includes/startsWith with URL constructor parsing

Alerts fixed: #1-50, #52-53, #57-61, #76-77, #82-97, #102-128, #279-280

Co-authored-by: Cursor <cursoragent@cursor.com>
seanhanca pushed a commit that referenced this pull request Feb 12, 2026
Address 103 high-severity CodeQL alerts across 34 files:

**Insecure temporary files (js/insecure-temporary-file) — 20 alerts:**
- Replace predictable temp paths with crypto.randomUUID() and fs.mkdtempSync()

**User-controlled bypass (js/user-controlled-bypass) — 14 alerts:**
- Add typeof checks for auth tokens, codes, emails, passwords
- Remove user-controlled skipVerification flag from publish endpoints
- Validate GitHub webhook headers with typeof guards
- Add OAuth error code allowlist in callback handler

**Remote property injection (js/remote-property-injection) — 6 alerts:**
- Block __proto__, constructor, prototype keys in config merge/assignment

**File system race (js/file-system-race) — 3 alerts:**
- Replace TOCTOU check-then-read with try/catch around direct operations

**Tainted format string (js/tainted-format-string) — 5 alerts:**
- Sanitize user input (strip control chars/newlines) before logging

**Incomplete sanitization + bad HTML filter (js/bad-tag-filter) — 2 alerts:**
- Replace single-pass regex HTML stripping with iterative loop

**Path injection (js/path-injection) — 20 alerts:**
- Add safeResolvePath() for storage paths
- Add sanitizePathComponent() for plugin/version path segments
- Add validateFilePath() for uploaded file paths

**SQL injection (js/sql-injection) — 5 alerts:**
- Add validateIdentifier() strict allowlist for DDL identifiers
- Sanitize database names, usernames, and passwords

**XSS through DOM (js/xss-through-dom) — 2 alerts:**
- Validate avatar URLs with URL constructor, block javascript: URIs

**Reflected XSS (js/reflected-xss) — 1 alert:**
- Validate plugin name against allowlist before HTML interpolation

**Insecure download (js/insecure-download) — 2 alerts:**
- Enforce HTTPS protocol validation before fetch calls

**Insecure randomness (js/insecure-randomness) — 1 alert:**
- Replace Math.random() with crypto.randomInt()

**Insufficient password hash (js/insufficient-password-hash) — 2 alerts:**
- Replace SHA-256 with crypto.scryptSync for API key hashing

**Missing rate limiting (js/missing-rate-limiting) — 13 alerts:**
- Add in-memory rate limiting to auth, registry, embed, and config routes

**Insecure Helmet configuration (js/insecure-helmet-configuration) — 2 alerts:**
- Replace disabled CSP with proper CSP directives

**Incomplete URL sanitization (js/incomplete-url-substring-sanitization) — 5 alerts:**
- Replace string includes/startsWith with URL constructor parsing

Alerts fixed: #1-50, #52-53, #57-61, #76-77, #82-97, #102-128, #279-280

Co-authored-by: Cursor <cursoragent@cursor.com>
seanhanca pushed a commit that referenced this pull request Feb 12, 2026
Address 103 high-severity CodeQL alerts across 34 files:

**Insecure temporary files (js/insecure-temporary-file) — 20 alerts:**
- Replace predictable temp paths with crypto.randomUUID() and fs.mkdtempSync()

**User-controlled bypass (js/user-controlled-bypass) — 14 alerts:**
- Add typeof checks for auth tokens, codes, emails, passwords
- Remove user-controlled skipVerification flag from publish endpoints
- Validate GitHub webhook headers with typeof guards
- Add OAuth error code allowlist in callback handler

**Remote property injection (js/remote-property-injection) — 6 alerts:**
- Block __proto__, constructor, prototype keys in config merge/assignment

**File system race (js/file-system-race) — 3 alerts:**
- Replace TOCTOU check-then-read with try/catch around direct operations

**Tainted format string (js/tainted-format-string) — 5 alerts:**
- Sanitize user input (strip control chars/newlines) before logging

**Incomplete sanitization + bad HTML filter (js/bad-tag-filter) — 2 alerts:**
- Replace single-pass regex HTML stripping with iterative loop

**Path injection (js/path-injection) — 20 alerts:**
- Add safeResolvePath() for storage paths
- Add sanitizePathComponent() for plugin/version path segments
- Add validateFilePath() for uploaded file paths

**SQL injection (js/sql-injection) — 5 alerts:**
- Add validateIdentifier() strict allowlist for DDL identifiers
- Sanitize database names, usernames, and passwords

**XSS through DOM (js/xss-through-dom) — 2 alerts:**
- Validate avatar URLs with URL constructor, block javascript: URIs

**Reflected XSS (js/reflected-xss) — 1 alert:**
- Validate plugin name against allowlist before HTML interpolation

**Insecure download (js/insecure-download) — 2 alerts:**
- Enforce HTTPS protocol validation before fetch calls

**Insecure randomness (js/insecure-randomness) — 1 alert:**
- Replace Math.random() with crypto.randomInt()

**Insufficient password hash (js/insufficient-password-hash) — 2 alerts:**
- Replace SHA-256 with crypto.scryptSync for API key hashing

**Missing rate limiting (js/missing-rate-limiting) — 13 alerts:**
- Add in-memory rate limiting to auth, registry, embed, and config routes

**Insecure Helmet configuration (js/insecure-helmet-configuration) — 2 alerts:**
- Replace disabled CSP with proper CSP directives

**Incomplete URL sanitization (js/incomplete-url-substring-sanitization) — 5 alerts:**
- Replace string includes/startsWith with URL constructor parsing

Alerts fixed: #1-50, #52-53, #57-61, #76-77, #82-97, #102-128, #279-280

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

fix: Multiple API routes use hardcoded mock data instead of database queries

3 participants