Skip to content

fix(permissions): exempt owners and admins from accessedServices filter in service search (#5361) - #5441

Open
fliptrigga13 wants to merge 1 commit into
Dokploy:canaryfrom
fliptrigga13:fix/issue-5361-search-accessed-services
Open

fliptrigga13 wants to merge 1 commit into
Dokploy:canaryfrom
fliptrigga13:fix/issue-5361-search-accessed-services

Conversation

@fliptrigga13

@fliptrigga13 fliptrigga13 commented Sep 13, 2026

Copy link
Copy Markdown

What is this PR about?

Fixes #5361

Problem

compose.search (as well as application.search, postgres.search, mysql.search, mariadb.search, mongo.search, and redis.search) returned an incomplete list of services or 0 services for organization owners, administrators, and API key holders:

  • Unlike project.search, environment.search, overview.services, and checkServiceAccess (which exempt owner and admin roles), these service search procedures unconditionally filtered by the caller's accessedServices list:
    const { accessedServices } = await findMemberByUserId(ctx.user.id, ctx.session.activeOrganizationId);
    if (accessedServices.length === 0) return { items: [], total: 0 };
    baseConditions.push(sql`${table.id} IN (...)`);
  • Services are only added to a member's accessedServices at creation time via addNewService() for the specific user who created them.
  • Consequently, any service created by another user, imported, or predating the user was omitted from search for everyone else—including organization owners and admins—even though those services are fully accessible in project.one and compose.one.

Solution

Aligned all service search procedures with project.search, environment.search, and overview.services by checking whether the caller has an owner or admin role:

const member = await findMemberByUserId(ctx.user.id, ctx.session.activeOrganizationId);
const isPrivileged =
    ctx.user.role === "owner" ||
    ctx.user.role === "admin" ||
    member.role === "owner" ||
    member.role === "admin";

if (!isPrivileged) {
    const { accessedServices } = member;
    if (accessedServices.length === 0) return { items: [], total: 0 };
    baseConditions.push(
        sql`${table.columnId} IN (${sql.join(
            accessedServices.map((id) => sql`${id}`),
            sql`, `,
        )})`,
    );
}

All queries remain strictly scoped to the active organization (projects.organizationId = ctx.session.activeOrganizationId).

Updated procedures:

  • apps/dokploy/server/api/routers/compose.ts (compose.search)
  • apps/dokploy/server/api/routers/application.ts (application.search)
  • apps/dokploy/server/api/routers/postgres.ts (postgres.search)
  • apps/dokploy/server/api/routers/mysql.ts (mysql.search)
  • apps/dokploy/server/api/routers/mariadb.ts (mariadb.search)
  • apps/dokploy/server/api/routers/mongo.ts (mongo.search)
  • apps/dokploy/server/api/routers/redis.ts (redis.search)

Checklist

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file.
  • You have tested this PR in your local instance:
    • Added unit tests in apps/dokploy/__test__/permissions/service-search-permissions.test.ts (5/5 passing tests).

Issues related

Fixes #5361

RetriggerConfidence Score: 4/5

The production authorization changes appear safe to merge, with a non-blocking test-quality issue that should be addressed to provide meaningful regression protection.

Summary

  • Preserves active-organization filtering in all seven queries.
  • Uses both request-context and active-membership roles for privileged access.
  • Adds permission scenarios, but the tests currently exercise a copied helper rather than the production procedures.

Reviews (1) · Last reviewed commit: "fix(permissions): exempt owners and admi..."

Comment on lines +11 to +45
const resolveSearchFilters = async (
ctx: {
user: { id: string; role: "owner" | "admin" | "member" };
session: { activeOrganizationId: string };
},
memberRecord: {
role: "owner" | "admin" | "member";
accessedServices: string[];
},
) => {
const isPrivileged =
ctx.user.role === "owner" ||
ctx.user.role === "admin" ||
memberRecord.role === "owner" ||
memberRecord.role === "admin";

// Always scope to the active organization
const orgScopedServices = mockServices.filter(
(s) => s.organizationId === ctx.session.activeOrganizationId,
);

if (!isPrivileged) {
const { accessedServices } = memberRecord;
if (accessedServices.length === 0) {
return { items: [], total: 0 };
}
const items = orgScopedServices.filter((s) =>
accessedServices.includes(s.id),
);
return { items, total: items.length };
}

// Privileged users (owner/admin) receive all services in the active organization
return { items: orgScopedServices, total: orgScopedServices.length };
};

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.

P2 Tests bypass production code

These tests exercise a local copy of the permission logic over hardcoded data instead of importing a changed router or production helper. An incorrect or missing exemption in any of the seven search procedures would therefore leave all five tests passing, so the authorization fix lacks effective regression coverage. Please exercise the production procedures or extract and directly test a shared production helper.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

compose.search omits services missing from member accessedServices, even for owners/admins

1 participant