mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 19:50:38 +09:00
fix(authz): scope import, approvals, activity, and heartbeat routes (#3315)
## Thinking Path > - Paperclip orchestrates AI agents and company-scoped control-plane actions for zero-human companies. > - This change touches the server authz boundary around company portability, approvals, activity, and heartbeat-run operations. > - The vulnerability was that board-authenticated callers could cross company boundaries or create new companies through import paths without the same authorization checks enforced elsewhere. > - Once that gap existed, an attacker could chain it into higher-impact behavior through agent execution paths. > - The fix needed to harden every confirmed authorization gap in the reported chain, not just the first route that exposed it. > - This pull request adds the missing instance-admin and company-access checks and adds regression tests for each affected route. > - The benefit is that cross-company actions and new-company import flows now follow the same control-plane authorization rules as the rest of the product. ## What Changed - Required instance-admin access for `new_company` import preview/apply flows in `server/src/routes/companies.ts`. - Required company access before approval decision routes in `server/src/routes/approvals.ts`. - Required company access for activity creation and heartbeat-run issue listing in `server/src/routes/activity.ts`. - Required company access before heartbeat cancellation in `server/src/routes/agents.ts`. - Added regression coverage in the corresponding server route tests. ## Verification - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/company-portability-routes.test.ts src/__tests__/approval-routes-idempotency.test.ts src/__tests__/activity-routes.test.ts src/__tests__/agent-permissions-routes.test.ts` - `pnpm --filter @paperclipai/server typecheck` - Prior verification on the original security patch branch also included `pnpm build`. ## Risks - Low code risk: the change is narrow and only adds missing authorization gates to existing routes. - Operational risk: the advisory is already public, so this PR should be merged quickly to minimize the public unpatched window. - Residual product risk remains around open signup / bootstrap defaults, which was intentionally left out of this patch because the current first-user onboarding flow depends on it. ## Model Used - OpenAI GPT-5 Codex coding agent with tool use and local code execution in the Codex CLI environment. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Forgotten <forgottenrunes@protonmail.com>
This commit is contained in:
parent
4477ca2a7e
commit
ac664df8e4
8 changed files with 203 additions and 10 deletions
|
|
@ -59,6 +59,8 @@ const mockBudgetService = vi.hoisted(() => ({
|
|||
const mockHeartbeatService = vi.hoisted(() => ({
|
||||
listTaskSessions: vi.fn(),
|
||||
resetRuntimeSession: vi.fn(),
|
||||
getRun: vi.fn(),
|
||||
cancelRun: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockIssueApprovalService = vi.hoisted(() => ({
|
||||
|
|
@ -397,4 +399,26 @@ describe("agent permission routes", () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects heartbeat cancellation outside the caller company scope", async () => {
|
||||
mockHeartbeatService.getRun.mockResolvedValue({
|
||||
id: "run-1",
|
||||
companyId: "33333333-3333-4333-8333-333333333333",
|
||||
agentId,
|
||||
status: "running",
|
||||
});
|
||||
|
||||
const app = await createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
source: "session",
|
||||
isInstanceAdmin: false,
|
||||
companyIds: [companyId],
|
||||
});
|
||||
|
||||
const res = await request(app).post("/api/heartbeat-runs/run-1/cancel").send({});
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue