mediumCVSS 4.3Vulnerability

GHSA-p7mm-r948-4q3q

## Summary The approval-resolution endpoints (`POST /approvals/:id/approve`, `/reject`, `/request-revision`) accept a client-supplied `decidedByUserId` field in the request body and write it verbatim into the authoritative `approvals.decidedByUserId` column — without cross-checking it against the authenticated actor. Any board user who can access an approval's company can record the decision as having been made by another user (e.g. the CEO), forging the governance audit trail. For `hire_agent` approvals with a monthly budget, the same attacker-controlled string is also stamped onto the resulting `budget_policies` row as `createdByUserId`/`updatedByUserId`. ## Details **Entry point** — `server/src/routes/approvals.ts:130`: ```ts router.post("/approvals/:id/approve", validate(resolveApprovalSchema), async (req, res) => { assertBoard(req); const id = req.params.id as string; if (!(await requireApprovalAccess(req, id))) { res.status(404).json({ error: "Approval not found" }); return; } const { approval, applied } = await svc.approve( id, req.body.decidedByUserId ?? "board", // ← client-controlled req.body.decisionNote, ); ``` **Authorization check** — `server/src/routes/authz.ts:4`: ```ts export function assertBoard(req: Request) { if (req.actor.type !== "board") { throw forbidden("Board access required"); } } ``` `assertBoard` only checks that the caller is some board user; it never ties `req.body.decidedByUserId` to `req.actor.userId`. `requireApprovalAccess`/`assertCompanyAccess` only verify the attacker is allowed to touch the approval's company, which every board user in that company already is. **Validator** — `packages/shared/src/validators/approval.ts:13`: ```ts export const resolveApprovalSchema = z.object({ decisionNote: z.string().optional().nullable(), decidedByUserId: z.string().optional().default("board"), }); ``` The Zod schema accepts any string for `decidedByUserId` — no UUID check, no membership

Properties

ghsa_id
GHSA-p7mm-r948-4q3q
severity
medium
summary
Paperclip: Approval decision attribution spoofing via client-controlled `decidedByUserId` in paperclip server
cvss_score
4.3
cve_id
GHSA-p7mm-r948-4q3q
cvss_vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
is_ghsa_only
true
ghsa_published
2026-04-16T22:48:46Z
source_url
https://github.com/advisories/GHSA-p7mm-r948-4q3q
ghsa_updated
2026-04-16T22:48:47Z

Related Entities (4)

VULNERABLE_TO (1)

[Software]npm/@paperclipai/server

AFFECTS (1)

[Software]npm/@paperclipai/server

HAS_WEAKNESS (1)

[Weakness]Insufficient Verification of Data Authenticity

REPORTED_BY (1)

[Source]GitHub Advisory Database

Explore deeper with Ninja Signal's threat intelligence graph

GHSA-p7mm-r948-4q3q (CVSS 4.3) — Ninja Signal Threat Intelligence | Ninja Signal