mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
fix: enforce low-trust runtime issue context
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
89945fd23d
commit
a5f2d64e27
2 files changed
+76
-8
No files matched your search
@@ -198,6 +198,47 @@ describeEmbeddedPostgres("workspace runtime service authz helper", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects CEO runtime service mutations for low-trust workspace issues without runtime.manage", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const { projectId, projectWorkspaceId } = await seedProjectWorkspace(companyId);
|
||||
const ceoAgentId = await seedAgent(companyId, { role: "ceo", name: "CEO" });
|
||||
|
||||
await db.insert(issues).values({
|
||||
id: randomUUID(),
|
||||
companyId,
|
||||
projectId,
|
||||
projectWorkspaceId,
|
||||
title: "Issue-scoped low-trust workspace",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
assigneeAgentId: ceoAgentId,
|
||||
executionPolicy: {
|
||||
authorizationPolicy: {
|
||||
trustBoundary: {
|
||||
mode: LOW_TRUST_REVIEW_PRESET,
|
||||
companyId,
|
||||
projectIds: [projectId],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(assertCanManageProjectWorkspaceRuntimeServices(db, {
|
||||
actor: {
|
||||
type: "agent",
|
||||
agentId: ceoAgentId,
|
||||
companyId,
|
||||
source: "agent_key",
|
||||
},
|
||||
} as any, {
|
||||
companyId,
|
||||
projectWorkspaceId,
|
||||
})).rejects.toMatchObject({
|
||||
status: 403,
|
||||
message: "Low-trust runs cannot manage workspace runtime services unless the boundary grants runtime.manage",
|
||||
});
|
||||
});
|
||||
|
||||
it("allows agents with a non-terminal assigned issue in the target project workspace", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const { projectId, projectWorkspaceId } = await seedProjectWorkspace(companyId);
|
||||
|
||||
@@ -117,11 +117,6 @@ async function assertAgentCanManageRuntimeServicesForWorkspace(
|
||||
});
|
||||
}
|
||||
|
||||
if (actorAgent.role === "ceo") {
|
||||
return;
|
||||
}
|
||||
|
||||
const eligibleAgentIds = await listReportingSubtreeAgentIds(db, input.companyId, actorAgent.id);
|
||||
const workspaceScopeConditions = [
|
||||
input.projectWorkspaceId ? eq(issues.projectWorkspaceId, input.projectWorkspaceId) : null,
|
||||
input.executionWorkspaceId ? eq(issues.executionWorkspaceId, input.executionWorkspaceId) : null,
|
||||
@@ -132,6 +127,40 @@ async function assertAgentCanManageRuntimeServicesForWorkspace(
|
||||
throw forbidden("Missing permission to manage workspace runtime services");
|
||||
}
|
||||
|
||||
const workspaceScopeCondition = workspaceScopeConditions.length === 1
|
||||
? workspaceScopeConditions[0]!
|
||||
: or(...workspaceScopeConditions);
|
||||
|
||||
const linkedScopeIssues = await db
|
||||
.select({
|
||||
id: issues.id,
|
||||
companyId: issues.companyId,
|
||||
projectId: issues.projectId,
|
||||
executionPolicy: issues.executionPolicy,
|
||||
projectExecutionWorkspacePolicy: projects.executionWorkspacePolicy,
|
||||
})
|
||||
.from(issues)
|
||||
.leftJoin(projects, and(eq(projects.id, issues.projectId), eq(projects.companyId, issues.companyId)))
|
||||
.where(and(
|
||||
eq(issues.companyId, input.companyId),
|
||||
isNull(issues.hiddenAt),
|
||||
inArray(issues.status, WORKSPACE_RUNTIME_ELIGIBLE_ISSUE_STATUSES),
|
||||
workspaceScopeCondition,
|
||||
));
|
||||
|
||||
for (const linkedScopeIssue of linkedScopeIssues) {
|
||||
assertLowTrustCanManageRuntimeForIssue({
|
||||
actorAgent,
|
||||
issue: linkedScopeIssue,
|
||||
projectExecutionWorkspacePolicy: linkedScopeIssue.projectExecutionWorkspacePolicy,
|
||||
});
|
||||
}
|
||||
|
||||
if (actorAgent.role === "ceo") {
|
||||
return;
|
||||
}
|
||||
|
||||
const eligibleAgentIds = await listReportingSubtreeAgentIds(db, input.companyId, actorAgent.id);
|
||||
const linkedIssue = await db
|
||||
.select({
|
||||
id: issues.id,
|
||||
@@ -147,9 +176,7 @@ async function assertAgentCanManageRuntimeServicesForWorkspace(
|
||||
isNull(issues.hiddenAt),
|
||||
inArray(issues.status, WORKSPACE_RUNTIME_ELIGIBLE_ISSUE_STATUSES),
|
||||
inArray(issues.assigneeAgentId, eligibleAgentIds),
|
||||
workspaceScopeConditions.length === 1
|
||||
? workspaceScopeConditions[0]!
|
||||
: or(...workspaceScopeConditions),
|
||||
workspaceScopeCondition,
|
||||
))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
|
||||
|
||||
Reference in new issue
Block a user