mastra-ai/mastra · error · HTTPException

Access denied: cannot save messages for a different resource

Error message

Access denied: cannot save messages for a different resource

What it means

When the server resolves an effective resourceId (from request context, e.g. authenticated user or agent-scoped resource), all messages in a save-messages batch must belong to that resource. Any message whose resourceId differs from the effective one triggers HTTP 403, blocking cross-resource data pollution and unauthorized writes.

Source

Thrown at packages/server/src/server/handlers/memory.ts:1341

          throw new HTTPException(400, {
            message: 'All messages for the same threadId must use the same resourceId.',
          });
        }
      }

      // Validate that all messages have threadId and resourceId
      const invalidMessages = incomingMessages.filter(message => !message.threadId || !message.resourceId);
      if (invalidMessages.length > 0) {
        throw new HTTPException(400, {
          message: `All messages must have threadId and resourceId fields. Found ${invalidMessages.length} invalid message(s).`,
        });
      }

      // If effectiveResourceId is set, validate all messages belong to this resource
      if (effectiveResourceId) {
        const unauthorizedMessages = incomingMessages.filter(message => message.resourceId !== effectiveResourceId);
        if (unauthorizedMessages.length > 0) {
          throw new HTTPException(403, {
            message: 'Access denied: cannot save messages for a different resource',
          });
        }

        // Validate that all threads belong to this resource (prevents cross-resource data pollution)
        const threadIds = [...new Set(incomingMessages.map(m => m.threadId).filter(Boolean))] as string[];
        for (const threadId of threadIds) {
          const thread = await memory.getThreadById({ threadId });
          await enforceThreadAccess({
            mastra,
            requestContext,
            threadId,
            thread,
            effectiveResourceId,
            permission: MastraFGAPermissions.MEMORY_WRITE,
          });
        }
      } else {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Set every message's `resourceId` to the resource bound to your request context (the authenticated/effective one).
  2. Drop messages belonging to other resources from the batch before sending.
  3. If you legitimately need to write for another resource, use credentials/context authorized for that resource (e.g., admin impersonation path) rather than mixing ids.
  4. Check for stale cached resourceId in the client after a login/user switch and refresh it.

Example fix

// before
await client.saveMessages({ messages: msgs.map(m => ({ ...m, resourceId: 'user-b' })) }); // effectiveResourceId is 'user-a'
// after
await client.saveMessages({ messages: msgs.map(m => ({ ...m, resourceId: effectiveResourceId })) });
Defensive patterns

Strategy: validation

Validate before calling

const unauthorized = messages.filter(m => effectiveResourceId && m.resourceId !== effectiveResourceId);
if (unauthorized.length) {
  throw new Error(`${unauthorized.length} message(s) belong to a different resource`);
}

Try / catch

try {
  await saveMessages({ messages });
} catch (e) {
  if (isHttpError(e) && e.status === 403 && e.message.includes('different resource')) {
    // drop foreign-resource messages or re-authenticate as the owning resource
  } else throw e;
}

Prevention

When it happens

Trigger: POST /memory/save-messages made in a context where effectiveResourceId is set (request context / auth provides it) while one or more messages carry a different `resourceId`, e.g. saving another user's messages while impersonating/locked to your own resource.

Common situations: Multi-tenant apps where a user token pins resourceId but client code sends a hardcoded or stale resourceId; admin tooling replaying another user's history without overriding context; copy-pasted test payloads with someone else's resourceId.

Understand the failure class

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/fa862feff7ea7fb9. Report an issue: GitHub.