From 47a0bf2fc80c82195c2a6fadb2818f352dd6cd0e Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 11 Aug 2026 19:22:52 +0300 Subject: [PATCH 1/3] Remove hardcoded permission allowlist for expert MCP tokens The user:expert-mcp entry in IMPLICIT_TOKEN_SCOPES was a safety measure from before HITL and scoped PATs existed. With both now in place, the allowlist is a duplicate gate that adds maintenance overhead without meaningful protection. Every new tool capability required updating this list or it would silently fail. The expert MCP token now inherits the user's full permissions (gated by team role). The token is short-lived (5 min TTL) and first-party only. Co-Authored-By: Claude Opus 4.6 (1M context) --- forge/routes/auth/index.js | 6 ++++ forge/routes/auth/permissions.js | 31 ------------------- .../forge/routes/auth/permissions_spec.js | 15 +++++---- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/forge/routes/auth/index.js b/forge/routes/auth/index.js index f8f1a1baab..22dd408bd1 100644 --- a/forge/routes/auth/index.js +++ b/forge/routes/auth/index.js @@ -224,6 +224,12 @@ async function init (app, opts) { reply.code(401).send({ code: 'unauthorized', error: 'unauthorized' }) return } + if (accessToken.ownerType === 'user:expert-mcp') { + // The expert MCP token is short-lived (5 min TTL) and first-party only. + // It inherits the user's full permissions rather than being restricted + // to a hardcoded allowlist. HITL and scoped PATs provide the access control. + delete request.session.scope + } resolveSourceContext(request) return } diff --git a/forge/routes/auth/permissions.js b/forge/routes/auth/permissions.js index c1740abaa9..12d5240059 100644 --- a/forge/routes/auth/permissions.js +++ b/forge/routes/auth/permissions.js @@ -52,37 +52,6 @@ const IMPLICIT_TOKEN_SCOPES = { 'broker:clients:list', 'broker:clients:link', 'assistant:call' // permit access to assistant - ], - 'user:expert-mcp': [ - // applications - 'team:projects:list', // list applications, list hosted instances, get instances status - 'project:read', // get application details - 'team:device:list', // list application remote instances - 'application:audit-log', // get application audit log - // devices - 'device:read', // get remote instance details - 'device:create', // create remote instance - 'device:edit', // assign remote instance to application - // hosted instances - 'project:create', // create application, create hosted instance, check instance name - 'project-type:read', - 'project-type:list', - 'project:log', // get hosted instance logs - // snapshots - 'project:snapshot:list', // list hosted instance snapshots - 'project:snapshot:create', // create hosted instance snapshot - 'device:snapshot:list', // list remote instance snapshots - 'device:snapshot:create', // create remote instance snapshot - // teams - 'user:team:list', // list teams - 'team:read', // get team details - // tables - 'team:database:list', // list/get databases, list/get tables, query table data - // platform - 'stack:list', - 'flow-blueprint:list', - 'project:status', - 'template:list' ] } diff --git a/test/unit/forge/routes/auth/permissions_spec.js b/test/unit/forge/routes/auth/permissions_spec.js index 30d31aa733..604a7e89e1 100644 --- a/test/unit/forge/routes/auth/permissions_spec.js +++ b/test/unit/forge/routes/auth/permissions_spec.js @@ -56,13 +56,14 @@ describe('Permissions API', async () => { } } - // Dedicated platform-automation token: ownerType 'user:expert-mcp' + ff-expert:platform scope + // Dedicated platform-automation token: ownerType 'user:expert-mcp', scope cleared + // at auth time so the token inherits the user's full permissions (gated by team role) const EXPERT_PLATFORM_TOKEN_TEAM_MEMBER = { - session: { User: { id: 'u123' }, ownerType: 'user:expert-mcp', scope: ['ff-expert:platform'] }, + session: { User: { id: 'u123' }, ownerType: 'user:expert-mcp' }, teamMembership: { role: Roles.Member } } const EXPERT_PLATFORM_TOKEN_TEAM_OWNER = { - session: { User: { id: 'u123' }, ownerType: 'user:expert-mcp', scope: ['ff-expert:platform'] }, + session: { User: { id: 'u123' }, ownerType: 'user:expert-mcp' }, teamMembership: { role: Roles.Owner } } // A plain user token must not gain broad access by carrying the platform scope @@ -239,12 +240,14 @@ describe('Permissions API', async () => { }) describe('expert platform token', () => { - it('Allows access to implicit scopes', async () => { + it('Inherits user permissions based on team role', async () => { expectPass(await sendRequest('team:read', EXPERT_PLATFORM_TOKEN_TEAM_MEMBER)) }) - it('Prevents access to scopes not in the implicit list', async () => { + it('Allows owner-level actions for an owner', async () => { + expectPass(await sendRequest('team:edit', EXPERT_PLATFORM_TOKEN_TEAM_OWNER)) + }) + it('Prevents member from accessing owner-level actions', async () => { expectFail(await sendRequest('team:edit', EXPERT_PLATFORM_TOKEN_TEAM_MEMBER)) - expectFail(await sendRequest('team:edit', EXPERT_PLATFORM_TOKEN_TEAM_OWNER)) }) it('Does not grant broad access to a plain user token carrying the scope', async () => { expectFail(await sendRequest('team:read', USER_TOKEN_EXPERT_PLATFORM_SCOPE_TEAM_MEMBER)) From d06251cb90eeb21e5e36cd36dad57a745b7e6f3f Mon Sep 17 00:00:00 2001 From: cstns Date: Tue, 11 Aug 2026 19:25:47 +0300 Subject: [PATCH 2/3] Fix comment: HITL and RBAC provide access control, not scoped PATs Co-Authored-By: Claude Opus 4.6 (1M context) --- forge/routes/auth/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge/routes/auth/index.js b/forge/routes/auth/index.js index 22dd408bd1..fb0acaa997 100644 --- a/forge/routes/auth/index.js +++ b/forge/routes/auth/index.js @@ -227,7 +227,7 @@ async function init (app, opts) { if (accessToken.ownerType === 'user:expert-mcp') { // The expert MCP token is short-lived (5 min TTL) and first-party only. // It inherits the user's full permissions rather than being restricted - // to a hardcoded allowlist. HITL and scoped PATs provide the access control. + // to a hardcoded allowlist. HITL and RBAC provide the access control. delete request.session.scope } resolveSourceContext(request) From fd76f237de2241edfd1eea5f38de074261e8c0ef Mon Sep 17 00:00:00 2001 From: cstns Date: Wed, 12 Aug 2026 15:41:53 +0300 Subject: [PATCH 3/3] Add MCP third-party agent feature flag and related team and posthog integrations --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 741955d732..17c5fa18cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14567,9 +14567,9 @@ } }, "node_modules/globals": { - "version": "17.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", - "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", "dev": true, "license": "MIT", "engines": { @@ -36292,9 +36292,9 @@ } }, "globals": { - "version": "17.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", - "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", "dev": true }, "globalthis": {