{"record":{"id":"af0d97035da108ed","repo":"mastra-ai/mastra","slug":"access-denied-unable-to-verify-message-ownership","errorCode":null,"errorMessage":"Access denied: unable to verify message ownership","messagePattern":"Access denied: unable to verify message ownership","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"packages/server/src/server/handlers/memory.ts","lineNumber":1758,"sourceCode":"      } else if (typeof messageIds === 'string') {\n        // Single string ID - wrap in array\n        normalizedIds = [messageIds];\n      } else {\n        // Single object with id property - wrap in array\n        normalizedIds = [messageIds];\n      }\n\n      // Extract string IDs for validation and deletion\n      const stringIds = normalizedIds.map(id => (typeof id === 'string' ? id : id.id));\n\n      const memory = await getMemoryFromContext({ mastra, agentId, requestContext, allowMissingAgent: true });\n\n      // If effectiveResourceId is set, validate ownership of all messages before deletion\n      // Fail closed: if we can't verify ownership, deny deletion\n      if (effectiveResourceId && stringIds.length > 0) {\n        const storage = memory?.storage || getStorageFromContext({ mastra });\n        if (!storage) {\n          throw new HTTPException(403, { message: 'Access denied: unable to verify message ownership' });\n        }\n        const memoryStore = await storage.getStore('memory');\n        if (!memoryStore) {\n          throw new HTTPException(400, { message: 'Memory is not initialized' });\n        }\n\n        await enforceDeleteMessagesThreadAccess({\n          mastra,\n          requestContext,\n          memoryStore,\n          messageIds: stringIds,\n          effectiveResourceId,\n        });\n      } else if (stringIds.length > 0) {\n        const storage = memory?.storage || getStorageFromContext({ mastra });\n        if (!storage) {\n          throw new HTTPException(400, { message: 'Memory is not initialized' });\n        }","sourceCodeStart":1740,"sourceCodeEnd":1776,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/memory.ts#L1740-L1776","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Configure storage on the Mastra instance: new Mastra({ storage: ... }) so ownership can be verified.","Attach storage to the agent's Memory (new Memory({ storage })) so memory?.storage resolves.","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).","Check requestContext/resource overrides that inject an unintended effectiveResourceId."],"exampleFix":"// before\nnew Mastra({ agents: { assistant } }); // no storage\n\n// after\nnew Mastra({\n  agents: { assistant },\n  storage: new PgStorage({ connectionString: process.env.DATABASE_URL }),\n});","handlingStrategy":"validation","validationCode":"const storage = mastra.getStorage?.();\nif (resourceId && !storage) {\n  throw new Error('resourceId-scoped message deletion requires Mastra-level storage for ownership checks');\n}","typeGuard":"function canVerifyOwnership(m: { storage?: unknown } | undefined, mastraStorage: unknown, resourceId?: string): boolean {\n  return !resourceId || !!(m?.storage ?? mastraStorage);\n}","tryCatchPattern":"try {\n  await client.deleteMessages(ids, { resourceId });\n} catch (e) {\n  if (e instanceof HTTPException && e.status === 403) {\n    console.error('Cannot verify message ownership: configure storage or drop resourceId');\n    return;\n  }\n  throw e;\n}","preventionTips":["In multi-tenant setups, always configure Mastra-level storage — ownership checks fail closed without it.","Only pass resourceId when you actually need resource-scoped enforcement.","Audit stored agents: unresolved memory + resourceId triggers this deny path."],"tags":["http-403","authorization","storage","multi-tenancy"],"backgroundTag":"access-denied","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}