{"record":{"id":"f119cf376d8240a6","repo":"jumpserver/jumpserver","slug":"approval-belongs-to-another-user","errorCode":null,"errorMessage":"Approval belongs to another user.","messagePattern":"Approval belongs to another user\\.","errorType":"http","errorClass":"PermissionDenied","httpStatus":403,"severity":"error","filePath":"apps/chat_ai/approvals/service.py","lineNumber":96,"sourceCode":"            method=operation.method,\n            path=path,\n            request_payload=payload,\n            request_hash=self.request_hash(payload),\n            nonce=uuid.uuid4().hex,\n            signing_key_id=str(getattr(settings, 'CHAT_AI_APPROVAL_KEY_ID', 'v1') or 'v1'),\n            signature='',\n            risk_level=decision.risk_level,\n            expires_at=timezone.now() + timedelta(seconds=getattr(settings, 'CHAT_AI_APPROVAL_TTL', 600)),\n        )\n        approval.signature = self.signature_for(approval)\n        approval.save(update_fields=('signature', 'date_updated'))\n        return approval\n\n    def prepare_confirmation(self, approval_id, user, org_id):\n        with transaction.atomic():\n            approval = Approval.objects.select_for_update().get(pk=approval_id)\n            if approval.user_id != user.id:\n                raise PermissionDenied('Approval belongs to another user.')\n            if str(approval.org_id) != str(org_id):\n                raise PermissionDenied('Approval belongs to another organization.')\n            if approval.status != Approval.Status.PENDING:\n                raise ValidationError(f'Approval is already {approval.status}.')\n            if approval.expires_at <= timezone.now():\n                approval.status = Approval.Status.EXPIRED\n                approval.save(update_fields=('status', 'date_updated'))\n                if approval.agent_run:\n                    approval.agent_run.status = AgentRun.Status.FAILED\n                    approval.agent_run.finished_at = timezone.now()\n                    approval.agent_run.error = 'APPROVAL_EXPIRED'\n                    approval.agent_run.save(update_fields=('status', 'finished_at', 'error', 'date_updated'))\n                    if approval.agent_run.assistant_message:\n                        approval.agent_run.assistant_message.status = Message.Status.FAILED\n                        approval.agent_run.assistant_message.error = 'APPROVAL_EXPIRED'\n                        approval.agent_run.assistant_message.save(update_fields=('status', 'error', 'date_updated'))\n                raise ValidationError('Approval has expired.')\n            if not hmac.compare_digest(approval.request_hash, self.request_hash(approval.request_payload)):","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/jumpserver/jumpserver/blob/6ec464fabd61b95912d539455a3a5f15f5c59fe0/apps/chat_ai/approvals/service.py#L78-L114","documentation":"Raised in prepare_confirmation when the Approval row's user_id does not match the confirming user. Approvals are user-scoped and locked with select_for_update, so only the user who created an approval may confirm it.","triggerScenarios":"Calling the confirm endpoint with an approval_id belonging to a different user: shared links to a confirmation URL, switching accounts mid-flow, or passing the wrong approval id.","commonSituations":"A confirmation link forwarded to a teammate, stale browser session under another account, or frontend passing an approval id from another user's list.","solutions":["Ensure the confirm request is authenticated as the user who created the approval","Regenerate the approval under the current user instead of reusing someone else's","If cross-user confirmation is a product requirement, add an explicit delegate/owner field and check in the service"],"exampleFix":"# before\nresp = client2.post(f'/api/approvals/{approval_id}/confirm/')  # client2 != creator\n\n# after\nresp = client1.post(f'/api/approvals/{approval_id}/confirm/')  # same user who created it","handlingStrategy":"validation","validationCode":"approval = Approval.objects.filter(pk=approval_id, user_id=request.user.id).first()\nif approval is None:\n    return HttpResponseNotFound()  # avoid PermissionDenied path entirely","typeGuard":"null","tryCatchPattern":"try {\n  await confirm(approvalId);\n} catch (e) {\n  if (e.status === 403 && /another user/.test(e.message)) showLoginHint();\n  else throw e;\n}","preventionTips":["List confirmations scoped to the current user only","Do not share confirmation URLs between accounts","Disable confirm buttons when session user changes"],"tags":["approvals","permissions","authorization"],"backgroundTag":"resource-owned-by-another-user","analyzedSha":"6ec464fabd61b95912d539455a3a5f15f5c59fe0","analyzedAt":"2026-08-28T11:33:00.925Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}