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

  1. Ensure the confirm request is authenticated as the user who created the approval
  2. Regenerate the approval under the current user instead of reusing someone else's
  3. 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

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


AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28). Data as JSON: /api/errors/f119cf376d8240a6. Report an issue: GitHub.