From d9445034a8ac65798952f1d199e176bd7a6cbecf Mon Sep 17 00:00:00 2001 From: decobot Date: Tue, 24 Feb 2026 00:58:19 +0800 Subject: [PATCH 1/5] fix IDOR cross organization PROJECT_GET --- apps/mesh/src/tools/projects/get.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/mesh/src/tools/projects/get.ts b/apps/mesh/src/tools/projects/get.ts index 04b124ebc6..5b6f64ae49 100644 --- a/apps/mesh/src/tools/projects/get.ts +++ b/apps/mesh/src/tools/projects/get.ts @@ -48,6 +48,10 @@ export const PROJECT_GET = defineTool({ let project = null; + if (input.organizationId !== ctx.organization?.id) { + throw new Error("Organization ID does not match authenticated organization"); + } + if (input.projectId) { project = await ctx.storage.projects.get(input.projectId); } else if (input.slug) { From 036203fd0a34123f6a54db36052d0f5a37f23008 Mon Sep 17 00:00:00 2001 From: decobot Date: Tue, 24 Feb 2026 23:09:43 +0800 Subject: [PATCH 2/5] delete organizationId input, and replace it with current session org --- apps/mesh/src/tools/projects/get.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/mesh/src/tools/projects/get.ts b/apps/mesh/src/tools/projects/get.ts index 5b6f64ae49..fc98899db0 100644 --- a/apps/mesh/src/tools/projects/get.ts +++ b/apps/mesh/src/tools/projects/get.ts @@ -21,7 +21,6 @@ export const PROJECT_GET = defineTool({ }, inputSchema: z .object({ - organizationId: z.string().describe("Organization ID"), projectId: z .string() .optional() @@ -48,15 +47,12 @@ export const PROJECT_GET = defineTool({ let project = null; - if (input.organizationId !== ctx.organization?.id) { - throw new Error("Organization ID does not match authenticated organization"); - } - + if (input.projectId) { project = await ctx.storage.projects.get(input.projectId); } else if (input.slug) { project = await ctx.storage.projects.getBySlug( - input.organizationId, + ctx.organization!.id, input.slug, ); } From 3f0d4373533498aee153dcad5cf3916a20ae3c83 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 25 Feb 2026 04:03:47 +0800 Subject: [PATCH 3/5] fix cross-organization bug --- apps/mesh/src/storage/ports.ts | 7 ++++- .../src/storage/project-plugin-configs.ts | 29 ++++++++++++++----- .../src/tools/projects/plugin-config-get.ts | 1 + .../tools/projects/plugin-config-update.ts | 1 + 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/mesh/src/storage/ports.ts b/apps/mesh/src/storage/ports.ts index c80123c112..c2b01a9f22 100644 --- a/apps/mesh/src/storage/ports.ts +++ b/apps/mesh/src/storage/ports.ts @@ -74,7 +74,11 @@ export interface ProjectStoragePort { export interface ProjectPluginConfigStoragePort { list(projectId: string): Promise; - get(projectId: string, pluginId: string): Promise; + get( + projectId: string, + pluginId: string, + organizationId: string, + ): Promise; upsert( projectId: string, pluginId: string, @@ -82,6 +86,7 @@ export interface ProjectPluginConfigStoragePort { connectionId?: string | null; settings?: Record | null; }, + organizationId: string, ): Promise; delete(projectId: string, pluginId: string): Promise; } diff --git a/apps/mesh/src/storage/project-plugin-configs.ts b/apps/mesh/src/storage/project-plugin-configs.ts index 8f023fdbea..72b8fc2c7a 100644 --- a/apps/mesh/src/storage/project-plugin-configs.ts +++ b/apps/mesh/src/storage/project-plugin-configs.ts @@ -60,13 +60,25 @@ export class ProjectPluginConfigsStorage async get( projectId: string, pluginId: string, + organizationId: string, ): Promise { - const row = await this.db + let query = this.db .selectFrom("project_plugin_configs") - .selectAll() - .where("project_id", "=", projectId) - .where("plugin_id", "=", pluginId) - .executeTakeFirst(); + .selectAll("project_plugin_configs") + .where("project_plugin_configs.project_id", "=", projectId) + .where("project_plugin_configs.plugin_id", "=", pluginId); + + if (organizationId) { + query = query + .innerJoin( + "projects", + "projects.id", + "project_plugin_configs.project_id", + ) + .where("projects.organization_id", "=", organizationId); + } + + const row = await query.executeTakeFirst(); return row ? this.parseRow(row) : null; } @@ -77,9 +89,10 @@ export class ProjectPluginConfigsStorage connectionId?: string | null; settings?: Record | null; }, + organizationId: string, ): Promise { const now = new Date().toISOString(); - const existing = await this.get(projectId, pluginId); + const existing = await this.get(projectId, pluginId, organizationId); if (existing) { const updateData: Record = { updated_at: now }; @@ -98,7 +111,7 @@ export class ProjectPluginConfigsStorage .where("plugin_id", "=", pluginId) .execute(); - const updated = await this.get(projectId, pluginId); + const updated = await this.get(projectId, pluginId, organizationId); if (!updated) { throw new Error("Failed to update project plugin config"); } @@ -119,7 +132,7 @@ export class ProjectPluginConfigsStorage }) .execute(); - const created = await this.get(projectId, pluginId); + const created = await this.get(projectId, pluginId, organizationId); if (!created) { throw new Error("Failed to create project plugin config"); } diff --git a/apps/mesh/src/tools/projects/plugin-config-get.ts b/apps/mesh/src/tools/projects/plugin-config-get.ts index fac88477ef..903e44fb55 100644 --- a/apps/mesh/src/tools/projects/plugin-config-get.ts +++ b/apps/mesh/src/tools/projects/plugin-config-get.ts @@ -40,6 +40,7 @@ export const PROJECT_PLUGIN_CONFIG_GET = defineTool({ const config = await ctx.storage.projectPluginConfigs.get( projectId, pluginId, + ctx.organization!.id, ); if (!config) { diff --git a/apps/mesh/src/tools/projects/plugin-config-update.ts b/apps/mesh/src/tools/projects/plugin-config-update.ts index bd3f85aec6..66670bf17b 100644 --- a/apps/mesh/src/tools/projects/plugin-config-update.ts +++ b/apps/mesh/src/tools/projects/plugin-config-update.ts @@ -92,6 +92,7 @@ export const PROJECT_PLUGIN_CONFIG_UPDATE = defineTool({ connectionId, settings, }, + ctx.organization!.id, ); return { From 17441d18d2bda45f9288696e7176a8de08226681 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 25 Feb 2026 04:08:21 +0800 Subject: [PATCH 4/5] fix format --- apps/mesh/src/tools/projects/get.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/mesh/src/tools/projects/get.ts b/apps/mesh/src/tools/projects/get.ts index fc98899db0..9d841c91bc 100644 --- a/apps/mesh/src/tools/projects/get.ts +++ b/apps/mesh/src/tools/projects/get.ts @@ -47,7 +47,6 @@ export const PROJECT_GET = defineTool({ let project = null; - if (input.projectId) { project = await ctx.storage.projects.get(input.projectId); } else if (input.slug) { From 460042803e2caa7d7ca2b25467117191b0b59c08 Mon Sep 17 00:00:00 2001 From: decobot Date: Thu, 26 Feb 2026 00:50:20 +0800 Subject: [PATCH 5/5] update config-update and throw error in get/update if no organization context exist --- apps/mesh/src/storage/project-plugin-configs.ts | 14 +++----------- apps/mesh/src/tools/projects/plugin-config-get.ts | 10 +++++++++- .../src/tools/projects/plugin-config-update.ts | 10 +++++++++- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/mesh/src/storage/project-plugin-configs.ts b/apps/mesh/src/storage/project-plugin-configs.ts index 72b8fc2c7a..f1f272919a 100644 --- a/apps/mesh/src/storage/project-plugin-configs.ts +++ b/apps/mesh/src/storage/project-plugin-configs.ts @@ -66,17 +66,9 @@ export class ProjectPluginConfigsStorage .selectFrom("project_plugin_configs") .selectAll("project_plugin_configs") .where("project_plugin_configs.project_id", "=", projectId) - .where("project_plugin_configs.plugin_id", "=", pluginId); - - if (organizationId) { - query = query - .innerJoin( - "projects", - "projects.id", - "project_plugin_configs.project_id", - ) - .where("projects.organization_id", "=", organizationId); - } + .where("project_plugin_configs.plugin_id", "=", pluginId) + .innerJoin("projects", "projects.id", "project_plugin_configs.project_id") + .where("projects.organization_id", "=", organizationId); const row = await query.executeTakeFirst(); return row ? this.parseRow(row) : null; diff --git a/apps/mesh/src/tools/projects/plugin-config-get.ts b/apps/mesh/src/tools/projects/plugin-config-get.ts index 903e44fb55..69f39b0ef0 100644 --- a/apps/mesh/src/tools/projects/plugin-config-get.ts +++ b/apps/mesh/src/tools/projects/plugin-config-get.ts @@ -37,10 +37,18 @@ export const PROJECT_PLUGIN_CONFIG_GET = defineTool({ const { projectId, pluginId } = input; + let organizationId = null; + + if (ctx.organization?.id) { + organizationId = ctx.organization.id; + } else { + throw new Error("Organization context is required"); + } + const config = await ctx.storage.projectPluginConfigs.get( projectId, pluginId, - ctx.organization!.id, + organizationId, ); if (!config) { diff --git a/apps/mesh/src/tools/projects/plugin-config-update.ts b/apps/mesh/src/tools/projects/plugin-config-update.ts index 66670bf17b..d39e206581 100644 --- a/apps/mesh/src/tools/projects/plugin-config-update.ts +++ b/apps/mesh/src/tools/projects/plugin-config-update.ts @@ -59,6 +59,14 @@ export const PROJECT_PLUGIN_CONFIG_UPDATE = defineTool({ throw new Error(`Project not found: ${projectId}`); } + let organizationId = null; + + if (ctx.organization?.id) { + organizationId = ctx.organization.id; + } else { + throw new Error("Organization context is required"); + } + const connectionExists = connectionId ? await ctx.storage.connections.findById(connectionId) : null; @@ -92,7 +100,7 @@ export const PROJECT_PLUGIN_CONFIG_UPDATE = defineTool({ connectionId, settings, }, - ctx.organization!.id, + organizationId, ); return {