From 29c433c7c3587a95b8a5635b1142a88085c7cd39 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 9 Mar 2026 08:58:28 -0700 Subject: [PATCH] fix(agent): scope instance list to requesting user (multi-user isolation) GET /api/v1/agent/instances was returning all instances regardless of user. Now decodes JWT: non-admin users only see their own instances; admins see all. Co-Authored-By: Claude Sonnet 4.6 --- .../controllers/agent-instance.controller.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/services/agent-service/src/interfaces/rest/controllers/agent-instance.controller.ts b/packages/services/agent-service/src/interfaces/rest/controllers/agent-instance.controller.ts index ddd447f..5615005 100644 --- a/packages/services/agent-service/src/interfaces/rest/controllers/agent-instance.controller.ts +++ b/packages/services/agent-service/src/interfaces/rest/controllers/agent-instance.controller.ts @@ -6,9 +6,11 @@ import { Delete, Body, Param, + Req, NotFoundException, BadRequestException, } from '@nestjs/common'; +import { Request } from 'express'; import * as crypto from 'crypto'; import { AgentInstanceRepository } from '../../../infrastructure/repositories/agent-instance.repository'; import { AgentInstanceDeployService } from '../../../infrastructure/services/agent-instance-deploy.service'; @@ -28,8 +30,16 @@ export class AgentInstanceController { } @Get() - async list() { - return this.instanceRepo.findAll(); + async list(@Req() req: Request) { + const jwt = this.decodeJwt(req.headers?.['authorization'] as string | undefined); + const userId = jwt?.sub; + const roles: string[] = jwt?.roles ?? []; + // Admins see all instances; regular users only see their own + if (!userId || roles.includes('admin')) { + return this.instanceRepo.findAll(); + } + const instances = await this.instanceRepo.findByUserId(userId); + return instances.map((inst) => this.sanitize(inst)); } @Get('user/:userId') @@ -210,4 +220,14 @@ export class AgentInstanceController { const { openclawToken, openclawTokenIv, ...safe } = inst; return { ...safe, hasToken: !!openclawToken }; } + + private decodeJwt(authHeader: string | undefined): Record | null { + if (!authHeader?.startsWith('Bearer ')) return null; + try { + const payload = authHeader.slice(7).split('.')[1]; + return JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')); + } catch { + return null; + } + } }