n8n - 💡(How to fix) Fix public-api/insights: handler accepts projectId without membership check (parity with variables fix) [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The Public API handler GET /api/v1/insights/summary accepts a caller-supplied projectId query parameter and forwards it directly to InsightsService.getInsightsSummary without verifying that the caller has access to that project. Other public API handlers that accept a projectId (variables, projects/{id}/users) gate access through ProjectService.getProjectWithScope first. This is a parity gap with the variables fix shipped in 2.18.1 (CVE-2026-42227 / GHSA-756q-gq9h-fp22), which routed variables filtering through the authorization-aware VariablesService.getAllForUser.

Root Cause

The Public API handler GET /api/v1/insights/summary accepts a caller-supplied projectId query parameter and forwards it directly to InsightsService.getInsightsSummary without verifying that the caller has access to that project. Other public API handlers that accept a projectId (variables, projects/{id}/users) gate access through ProjectService.getProjectWithScope first. This is a parity gap with the variables fix shipped in 2.18.1 (CVE-2026-42227 / GHSA-756q-gq9h-fp22), which routed variables filtering through the authorization-aware VariablesService.getAllForUser.

Fix Action

Fixed

Code Example

const insightsHandlers: InsightsHandlers = {
  getInsightsSummary: [
    publicApiScope('insights:read'),
    async (req, res) => {
      // ... date validation, license check ...
      const summary = await Container.get(InsightsService).getInsightsSummary({
        startDate,
        endDate,
        projectId: query.data.projectId,   // forwarded as-is
      });
      return res.json(summary);
    },
  ],
};
RAW_BUFFERClick to expand / collapse

Summary

The Public API handler GET /api/v1/insights/summary accepts a caller-supplied projectId query parameter and forwards it directly to InsightsService.getInsightsSummary without verifying that the caller has access to that project. Other public API handlers that accept a projectId (variables, projects/{id}/users) gate access through ProjectService.getProjectWithScope first. This is a parity gap with the variables fix shipped in 2.18.1 (CVE-2026-42227 / GHSA-756q-gq9h-fp22), which routed variables filtering through the authorization-aware VariablesService.getAllForUser.

Where

packages/cli/src/public-api/v1/handlers/insights/insights.handler.ts:46-77 (HEAD 60e23e10):

const insightsHandlers: InsightsHandlers = {
  getInsightsSummary: [
    publicApiScope('insights:read'),
    async (req, res) => {
      // ... date validation, license check ...
      const summary = await Container.get(InsightsService).getInsightsSummary({
        startDate,
        endDate,
        projectId: query.data.projectId,   // forwarded as-is
      });
      return res.json(summary);
    },
  ],
};

insights:read is in MEMBER_API_KEY_SCOPES (packages/@n8n/permissions/src/public-api-permissions.ee.ts:122), so any Member-tier API key carries it by default.

Comparison with adjacent handlers

  • packages/cli/src/public-api/v1/handlers/projects/projects.handler.ts:130 — calls projectService.getProjectWithScope(req.user, projectId, ['project:list']) and returns 404 if the caller cannot read the project.
  • packages/cli/src/environments.ee/variables/variables.service.ee.ts:90-122getAllForUser re-derives the caller's accessible project list via projectService.getProjectIdsWithScope and intersects with the requested filter (the fix landed in 2.18.1).

The internal InsightsController (packages/cli/src/modules/insights/insights.controller.ts:25) is annotated @GlobalScope('insights:list'), an owner/admin-only scope, so the lack of project scoping in the service is fine for that path. The public-api handler however uses insights:read (member-default), which is what creates the parity gap.

Suggested fix

Mirror the pattern in projects.handler.ts:130. Before passing projectId to the service, check it via ProjectService.getProjectWithScope(req.user, projectId, ['project:read']) and 404 if the call returns null. getProjectWithScope already short-circuits for users carrying the global scope (packages/cli/src/services/project.service.ee.ts:581), so global admins keep their existing behaviour.

A draft PR with this change and an integration test is at https://github.com/n8n-io/n8n/pull/<TBD> (will edit once opened).

References

  • 2.18.1 variables fix: GHSA-756q-gq9h-fp22 / CVE-2026-42227
  • Existing primitive: ProjectService.getProjectWithScope at packages/cli/src/services/project.service.ee.ts:569

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

n8n - 💡(How to fix) Fix public-api/insights: handler accepts projectId without membership check (parity with variables fix) [1 pull requests]