jumpserver/jumpserver · error · PermissionDenied
Approval belongs to another user.
Error message
Approval belongs to another user.
What it means
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.
Source
Thrown at apps/chat_ai/approvals/service.py:96
method=operation.method,
path=path,
request_payload=payload,
request_hash=self.request_hash(payload),
nonce=uuid.uuid4().hex,
signing_key_id=str(getattr(settings, 'CHAT_AI_APPROVAL_KEY_ID', 'v1') or 'v1'),
signature='',
risk_level=decision.risk_level,
expires_at=timezone.now() + timedelta(seconds=getattr(settings, 'CHAT_AI_APPROVAL_TTL', 600)),
)
approval.signature = self.signature_for(approval)
approval.save(update_fields=('signature', 'date_updated'))
return approval
def prepare_confirmation(self, approval_id, user, org_id):
with transaction.atomic():
approval = Approval.objects.select_for_update().get(pk=approval_id)
if approval.user_id != user.id:
raise PermissionDenied('Approval belongs to another user.')
if str(approval.org_id) != str(org_id):
raise PermissionDenied('Approval belongs to another organization.')
if approval.status != Approval.Status.PENDING:
raise ValidationError(f'Approval is already {approval.status}.')
if approval.expires_at <= timezone.now():
approval.status = Approval.Status.EXPIRED
approval.save(update_fields=('status', 'date_updated'))
if approval.agent_run:
approval.agent_run.status = AgentRun.Status.FAILED
approval.agent_run.finished_at = timezone.now()
approval.agent_run.error = 'APPROVAL_EXPIRED'
approval.agent_run.save(update_fields=('status', 'finished_at', 'error', 'date_updated'))
if approval.agent_run.assistant_message:
approval.agent_run.assistant_message.status = Message.Status.FAILED
approval.agent_run.assistant_message.error = 'APPROVAL_EXPIRED'
approval.agent_run.assistant_message.save(update_fields=('status', 'error', 'date_updated'))
raise ValidationError('Approval has expired.')
if not hmac.compare_digest(approval.request_hash, self.request_hash(approval.request_payload)):View on GitHub (pinned to 6ec464fabd)
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
Example fix
# before
resp = client2.post(f'/api/approvals/{approval_id}/confirm/') # client2 != creator
# after
resp = client1.post(f'/api/approvals/{approval_id}/confirm/') # same user who created it Defensive patterns
Strategy: validation
Validate before calling
approval = Approval.objects.filter(pk=approval_id, user_id=request.user.id).first()
if approval is None:
return HttpResponseNotFound() # avoid PermissionDenied path entirely Type guard
null
Try / catch
try {
await confirm(approvalId);
} catch (e) {
if (e.status === 403 && /another user/.test(e.message)) showLoginHint();
else throw e;
} Prevention
- List confirmations scoped to the current user only
- Do not share confirmation URLs between accounts
- Disable confirm buttons when session user changes
When it happens
Trigger: 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.
Common situations: A confirmation link forwarded to a teammate, stale browser session under another account, or frontend passing an approval id from another user's list.
Related errors
- Approval is not accessible.
- perm_account_invalid
- user `{}` not has asset `{}` permission for login `{}`
- Approval belongs to another organization.
- You do not have access rights to {} assets
AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28).
Data as JSON: /api/errors/f119cf376d8240a6.
Report an issue: GitHub.