mastra-ai/mastra · error · HTTPException

Access denied: unable to verify message ownership

Error message

Access denied: unable to verify message ownership

What it means

In DELETE_MESSAGES_ROUTE, when an effectiveResourceId is present the handler must verify that every message being deleted belongs to that resource. It resolves storage from memory?.storage or getStorageFromContext({ mastra }); if neither yields a storage adapter, it fails closed with HTTPException 403 'Access denied: unable to verify message ownership'. This is a deliberate security default, not a bug signal.

Source

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

      } else if (typeof messageIds === 'string') {
        // Single string ID - wrap in array
        normalizedIds = [messageIds];
      } else {
        // Single object with id property - wrap in array
        normalizedIds = [messageIds];
      }

      // Extract string IDs for validation and deletion
      const stringIds = normalizedIds.map(id => (typeof id === 'string' ? id : id.id));

      const memory = await getMemoryFromContext({ mastra, agentId, requestContext, allowMissingAgent: true });

      // If effectiveResourceId is set, validate ownership of all messages before deletion
      // Fail closed: if we can't verify ownership, deny deletion
      if (effectiveResourceId && stringIds.length > 0) {
        const storage = memory?.storage || getStorageFromContext({ mastra });
        if (!storage) {
          throw new HTTPException(403, { message: 'Access denied: unable to verify message ownership' });
        }
        const memoryStore = await storage.getStore('memory');
        if (!memoryStore) {
          throw new HTTPException(400, { message: 'Memory is not initialized' });
        }

        await enforceDeleteMessagesThreadAccess({
          mastra,
          requestContext,
          memoryStore,
          messageIds: stringIds,
          effectiveResourceId,
        });
      } else if (stringIds.length > 0) {
        const storage = memory?.storage || getStorageFromContext({ mastra });
        if (!storage) {
          throw new HTTPException(400, { message: 'Memory is not initialized' });
        }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Configure storage on the Mastra instance: new Mastra({ storage: ... }) so ownership can be verified.
  2. Attach storage to the agent's Memory (new Memory({ storage })) so memory?.storage resolves.
  3. If you do not need resource-scoped deletion, omit resourceId so the ownership-check branch is skipped (access is then enforced via thread access instead).
  4. Check requestContext/resource overrides that inject an unintended effectiveResourceId.

Example fix

// before
new Mastra({ agents: { assistant } }); // no storage

// after
new Mastra({
  agents: { assistant },
  storage: new PgStorage({ connectionString: process.env.DATABASE_URL }),
});
Defensive patterns

Strategy: validation

Validate before calling

const storage = mastra.getStorage?.();
if (resourceId && !storage) {
  throw new Error('resourceId-scoped message deletion requires Mastra-level storage for ownership checks');
}

Type guard

function canVerifyOwnership(m: { storage?: unknown } | undefined, mastraStorage: unknown, resourceId?: string): boolean {
  return !resourceId || !!(m?.storage ?? mastraStorage);
}

Try / catch

try {
  await client.deleteMessages(ids, { resourceId });
} catch (e) {
  if (e instanceof HTTPException && e.status === 403) {
    console.error('Cannot verify message ownership: configure storage or drop resourceId');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/memory/messages/delete with resourceId set (or a requestContext resource override) while the Mastra instance has no storage configured and the resolved memory is undefined or has no .storage — ownership cannot be checked, so deletion is denied.

Common situations: Multi-tenant deployments passing resourceId for isolation but forgetting to configure storage on the server's Mastra instance; running with an in-memory-only setup in production; stored agents whose memory can't be resolved leaving memory undefined while resourceId is still supplied.

Understand the failure class

Related errors


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