Skip to content

Security: Missing authorization checks on ticket listing endpoints#2910

Open
tomaioo wants to merge 1 commit into
gardener:masterfrom
tomaioo:fix/security/missing-authorization-checks-on-ticket-l
Open

Security: Missing authorization checks on ticket listing endpoints#2910
tomaioo wants to merge 1 commit into
gardener:masterfrom
tomaioo:fix/security/missing-authorization-checks-on-ticket-l

Conversation

@tomaioo
Copy link
Copy Markdown

@tomaioo tomaioo commented Apr 21, 2026

Summary

Security: Missing authorization checks on ticket listing endpoints

Problem

Severity: High | File: backend/lib/routes/tickets.js:L14

The tickets routes return issue/comment data based only on the namespace URL parameter and cache lookups, but do not perform any per-request authorization check (e.g., project membership or explicit RBAC verification). An authenticated user may query /namespaces/_all/tickets (or other namespaces) and potentially retrieve tickets for projects they should not access.

Solution

Before returning tickets/comments, enforce authorization for the requesting user and namespace/project. For example, resolve namespace -> project, then call an authorization service (e.g., canGetProject/canListIssues) and return 403 on failure. Also explicitly forbid _all unless the user has a global admin/list permission.

Changes

  • backend/lib/routes/tickets.js (modified)

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced API security by implementing access control on ticket endpoints. Authorization validation ensures only authorized users can access ticket data and operations.

The tickets routes return issue/comment data based only on the `namespace` URL parameter and cache lookups, but do not perform any per-request authorization check (e.g., project membership or explicit RBAC verification). An authenticated user may query `/namespaces/_all/tickets` (or other namespaces) and potentially retrieve tickets for projects they should not access.

Signed-off-by: tomaioo <203048277+tomaioo@users.noreply.github.com>
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@gardener-prow
Copy link
Copy Markdown

gardener-prow Bot commented Apr 21, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign grolu for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@gardener-prow gardener-prow Bot added the do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. label Apr 21, 2026
@gardener-prow
Copy link
Copy Markdown

gardener-prow Bot commented Apr 21, 2026

Welcome @tomaioo!

It looks like this is your first PR to gardener/dashboard 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if gardener/dashboard has its own contribution guidelines.

Thank you, and welcome to Gardener. 😃

@gardener-prow gardener-prow Bot added the size/S Denotes a PR that changes 10-29 lines, ignoring generated files. label Apr 21, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

Added authorization enforcement to ticket routes using a new authorize() helper function that validates access permissions based on namespace parameters, throwing a 403 error on failed authorization checks.

Changes

Cohort / File(s) Summary
Authorization Helper & Route Guards
backend/lib/routes/tickets.js
Introduced new async authorize(req, namespace) helper with http-errors dependency. Updated GET / and GET /:name routes to enforce authorization checks before data retrieval, propagating 403 errors through existing error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A guardian bunny guards the gates,
With auth checks that seal all fates,
No sneaky peeks shall pass this day,
Authorization bars the way,
Security hops without debates! 🔐

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the problem, severity, solution approach, and affected files, but is missing key required sections from the template such as 'Which issue(s) this PR fixes' and 'Release note'. Add the missing required template sections: link the related issue with 'Fixes #XXXX' and include a properly formatted release note block with category and target group.
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 (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main security fix: adding missing authorization checks to ticket listing endpoints.

✏️ 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.

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

🧹 Nitpick comments (1)
backend/lib/routes/tickets.js (1)

57-80: Optional: avoid resolving the project twice per request.

Both handlers call authorize(req, namespace) (which resolves the project) and then getIssues/getIssuesAndComments, each of which calls getProjectName(namespace) again. Consider returning the resolved projectName from authorize() (or a small resolveAndAuthorize helper) and passing it into getIssues/getIssuesAndComments to remove the duplicated cache lookup and keep a single source of truth for the namespace → project mapping.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/lib/routes/tickets.js` around lines 57 - 80, Return the resolved
project name from authorize(req, namespace) (or implement a small helper like
resolveAndAuthorize) and pass that projectName into getIssues and
getIssuesAndComments instead of letting those functions call
getProjectName(namespace) again; update authorize to return the resolved project
identifier (or add resolveAndAuthorize that calls authorize then returns
projectName), then change the handlers to capture that return value and supply
it to getIssues(projectName) and getIssuesAndComments(projectName, name) so the
project lookup/cache hit happens once per request.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/lib/routes/tickets.js`:
- Around line 42-53: authorize currently calls getProjectName(namespace) which
dereferences cache.findProjectByNamespace(namespace).metadata.name and can throw
if the project is missing; change authorize to first resolve the project (via
cache.findProjectByNamespace or the existing getProjectName helper but without
blind dereference), check for null/undefined and if missing throw
createError(403) (or 404 per policy) to avoid a TypeError, otherwise extract
project.metadata.name and call req.user.canGetProject({ name: projectName }) as
before; update any use of getProjectName inside authorize to handle the
missing-project case instead of letting it throw.

---

Nitpick comments:
In `@backend/lib/routes/tickets.js`:
- Around line 57-80: Return the resolved project name from authorize(req,
namespace) (or implement a small helper like resolveAndAuthorize) and pass that
projectName into getIssues and getIssuesAndComments instead of letting those
functions call getProjectName(namespace) again; update authorize to return the
resolved project identifier (or add resolveAndAuthorize that calls authorize
then returns projectName), then change the handlers to capture that return value
and supply it to getIssues(projectName) and getIssuesAndComments(projectName,
name) so the project lookup/cache hit happens once per request.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30f1dfc1-d6a8-4dd2-9bfc-3a2b21443b17

📥 Commits

Reviewing files that changed from the base of the PR and between 373c911 and 6d5132b.

📒 Files selected for processing (1)
  • backend/lib/routes/tickets.js

Comment on lines +42 to +53
async function authorize (req, namespace) {
if (namespace === '_all') {
if (!await req.user.canListIssues()) {
throw createError(403, 'Forbidden')
}
return
}
const projectName = getProjectName(namespace)
if (!await req.user.canGetProject({ name: projectName })) {
throw createError(403, 'Forbidden')
}
}
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 | 🟠 Major

Handle missing project to avoid 500 and inconsistent auth errors.

When namespace is not _all, authorize() calls getProjectName(namespace) (line 21-25), which dereferences cache.findProjectByNamespace(namespace).metadata.name without a null-check. If the namespace does not map to a known project (unknown namespace, cache miss, or a namespace the user cannot see), this throws a TypeError and Express returns a 500. That both defeats the intent of this security fix (callers get a different error shape depending on whether the namespace exists) and leaks a probing signal.

Resolve the project first and translate a missing project into the same 403 (or a 404) consistently.

🛡️ Suggested fix
 async function authorize (req, namespace) {
   if (namespace === '_all') {
     if (!await req.user.canListIssues()) {
       throw createError(403, 'Forbidden')
     }
     return
   }
-  const projectName = getProjectName(namespace)
+  const project = cache.findProjectByNamespace(namespace)
+  if (!project) {
+    throw createError(403, 'Forbidden')
+  }
+  const projectName = project.metadata.name
   if (!await req.user.canGetProject({ name: projectName })) {
     throw createError(403, 'Forbidden')
   }
 }
📝 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
async function authorize (req, namespace) {
if (namespace === '_all') {
if (!await req.user.canListIssues()) {
throw createError(403, 'Forbidden')
}
return
}
const projectName = getProjectName(namespace)
if (!await req.user.canGetProject({ name: projectName })) {
throw createError(403, 'Forbidden')
}
}
async function authorize (req, namespace) {
if (namespace === '_all') {
if (!await req.user.canListIssues()) {
throw createError(403, 'Forbidden')
}
return
}
const project = cache.findProjectByNamespace(namespace)
if (!project) {
throw createError(403, 'Forbidden')
}
const projectName = project.metadata.name
if (!await req.user.canGetProject({ name: projectName })) {
throw createError(403, 'Forbidden')
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/lib/routes/tickets.js` around lines 42 - 53, authorize currently
calls getProjectName(namespace) which dereferences
cache.findProjectByNamespace(namespace).metadata.name and can throw if the
project is missing; change authorize to first resolve the project (via
cache.findProjectByNamespace or the existing getProjectName helper but without
blind dereference), check for null/undefined and if missing throw
createError(403) (or 404 per policy) to avoid a TypeError, otherwise extract
project.metadata.name and call req.user.canGetProject({ name: projectName }) as
before; update any use of getProjectName inside authorize to handle the
missing-project case instead of letting it throw.

@gardener-prow gardener-prow Bot added the cla: no Indicates the PR's author has not signed the cla-assistant.io CLA. label Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: no Indicates the PR's author has not signed the cla-assistant.io CLA. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/S Denotes a PR that changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants