Skip to content

fix(server-core): enforce memory conversation ownership - #1388

Open
zcxGGmu wants to merge 7 commits into
VoltAgent:mainfrom
zcxGGmu:fix/issue-1371-memory-ownership-check
Open

fix(server-core): enforce memory conversation ownership#1388
zcxGGmu wants to merge 7 commits into
VoltAgent:mainfrom
zcxGGmu:fix/issue-1371-memory-ownership-check

Conversation

@zcxGGmu

@zcxGGmu zcxGGmu commented Jul 30, 2026

Copy link
Copy Markdown

Summary

  • Enforces memory conversation ownership for authenticated requests before returning conversations, listing messages, updating conversations, or deleting conversations.
  • Passes the authenticated user id from Hono and Elysia memory routes into the shared server-core handlers.
  • Adds regression coverage for cross-user memory conversation access attempts.

Test Plan

  • pnpm --filter @voltagent/server-core test -- --run
  • pnpm --filter @voltagent/server-core typecheck
  • pnpm --filter @voltagent/server-core --filter @voltagent/server-hono --filter @voltagent/server-elysia build
  • biome check packages/server-core/src/handlers/memory.handlers.ts packages/server-core/src/handlers/memory.handlers.spec.ts packages/server-hono/src/routes/memory.routes.ts packages/server-elysia/src/routes/memory.routes.ts

Fixes #1371


Summary by cubic

Enforces conversation ownership across memory APIs, handlers, and adapters to prevent cross-user reads and mutations (IDOR). Authenticated identity is used end-to-end; guarded updates/deletes verify the owner and return 403. Fixes #1371.

  • Bug Fixes

    • Return 403 for cross-user reads, message lists, updates, and deletes in @voltagent/server-core; map ConversationOwnershipMismatchError to 403; reject empty authenticated identities; prefer the authenticated user over client userId when listing.
    • Pass the authenticated user from @voltagent/server-hono and @voltagent/server-elysia to handlers as requestingUserId (id/sub) for list/get/messages/update/delete.
    • Add ConversationMutationOptions.expectedUserId and enforce ownership in Memory.updateConversation/deleteConversation; pre-check owner, skip vector cleanup when the pre-check finds no conversation, and keep vector cleanup best-effort for unguarded deletes or read failures.
    • Enforce guarded mutations in adapters: @voltagent/libsql, @voltagent/postgres, @voltagent/supabase, @voltagent/cloudflare-d1, and in-memory add user_id guards to update/delete; D1 uses a guarded parent delete (cascades for child rows); throw ConversationOwnershipMismatchError when no owned rows are affected.
    • @voltagent/voltagent-memory validates expected owner before delegating updates/deletes and rejects empty expected owners; the VoltOps client forwards expectedUserId on managed-memory update/delete calls.
    • Add tests for handler ownership checks, route propagation, guarded adapter queries, managed-memory delegation, and asserting guarded delete arguments.
  • Dependencies

    • Raise peer dependency minimums to @voltagent/core ^2.9.1 across adapters, server packages, and @voltagent/voltagent-memory.

Written for commit a4d2f31. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Security

    • Restricted memory conversation access and modifications to the conversation owner.
    • Unauthorized reads, message listings, updates, and deletions now return a 403 Forbidden response.
    • Memory operations use the authenticated identity, preventing client-supplied identity overrides.
    • Ownership is revalidated during updates and deletions to prevent unauthorized changes or data loss.
  • Tests

    • Added comprehensive coverage for owner access, unauthorized actions, identity handling, guarded mutations, and data preservation.

Reject authenticated memory conversation reads, message listing, updates, and deletes when the conversation belongs to another user.\n\nAdd regression tests for the IDOR paths reported in VoltAgent#1371 and pass authenticated user context from Hono and Elysia memory routes.
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a4d2f31

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@voltagent/core Patch
@voltagent/cloudflare-d1 Patch
@voltagent/libsql Patch
@voltagent/postgres Patch
@voltagent/server-core Patch
@voltagent/server-elysia Patch
@voltagent/server-hono Patch
@voltagent/supabase Patch
@voltagent/voltagent-memory Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Memory routes pass authenticated user identifiers to handlers. Handlers enforce ownership for conversation listing, reads, messages, updates, and deletes. Memory services and storage adapters guard mutations with expected owner IDs. Tests cover unauthorized access and ownership propagation.

Changes

Memory ownership authorization

Layer / File(s) Summary
Ownership contracts and guarded mutations
packages/core/src/memory/*
Conversation mutations accept expectedUserId. Core services and in-memory storage reject ownership mismatches and preserve guarded deletion behavior.
Persistent adapter ownership predicates
packages/cloudflare-d1/src/*, packages/libsql/src/*, packages/postgres/src/*, packages/supabase/src/*
Storage adapters constrain updates and deletes by owner and report mismatches. Adapter tests verify SQL filters and affected-row handling.
Handler ownership gates
packages/server-core/src/handlers/memory.handlers.ts, packages/server-core/src/handlers/memory.handlers.spec.ts
Handlers use authenticated requester IDs, ignore client-supplied listing identities, validate access, and return 403 when ownership fails.
Authenticated identity propagation and remote mutation wiring
packages/server-elysia/src/*, packages/server-hono/src/*, packages/core/src/voltops/*, packages/voltagent-memory/src/*, packages/*/package.json, .changeset/*
Routes extract authenticated IDs and pass them to handlers. Managed-memory clients forward guarded mutation options. Tests cover route propagation and managed-memory ownership checks. Package metadata requires the updated core version.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MemoryRoutes
  participant MemoryHandlers
  participant Memory
  participant StorageAdapters
  Client->>MemoryRoutes: Request conversation operation
  MemoryRoutes->>MemoryRoutes: Extract authenticated user ID
  MemoryRoutes->>MemoryHandlers: Pass requestingUserId
  MemoryHandlers->>Memory: Fetch or mutate conversation
  Memory->>StorageAdapters: Apply expectedUserId
  StorageAdapters-->>Memory: Return result or ownership mismatch
  Memory-->>MemoryHandlers: Return result or ownership error
  MemoryHandlers-->>MemoryRoutes: Return result or 403 Forbidden
  MemoryRoutes-->>Client: HTTP response
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #1371 by enforcing authenticated ownership for reads, listings, updates, and deletes across routes, handlers, and storage adapters.
Out of Scope Changes check ✅ Passed The adapter, authentication, dependency, release metadata, and test changes directly support the memory conversation ownership fix.
Title check ✅ Passed The title clearly and concisely describes the primary change: enforcing memory conversation ownership in server-core.
Description check ✅ Passed The description explains the changes, tests, linked issue, and changeset-related work, but it omits several template headings and checklist items.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/issue-1371-memory-ownership-check
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/server-core/src/handlers/memory.handlers.spec.ts`:
- Around line 120-128: Add a test for the conversation-list handler covering an
authenticated Bob requesting Alice’s userId through /api/memory/conversations,
and assert Alice’s conversation is excluded from the response before the
existing ownership tests. Reuse the existing test fixtures, dependencies,
handler symbol, and response shape in memory.handlers.spec.ts; keep the current
known-conversation message-listing test unchanged.

In `@packages/server-core/src/handlers/memory.handlers.ts`:
- Around line 568-581: Make updateConversation and deleteConversation use
storage-level mutations guarded by the verified owner via an expectedUserId
condition, rather than relying only on the preceding
getConversation/assertConversationOwner check. In
packages/server-core/src/handlers/memory.handlers.ts lines 568-581 and 614-627,
return 403 when the conditional update or delete affects no owned record, while
preserving the existing 404 response for a missing conversation.

In `@packages/server-elysia/src/routes/memory.routes.ts`:
- Around line 44-53: Update the list-conversations handlers in
packages/server-elysia/src/routes/memory.routes.ts (lines 44-53) and
packages/server-hono/src/routes/memory.routes.ts (lines 44-53) to derive the
authenticated identity via getAuthenticatedUserId and pass it as
requestingUserId to handleListMemoryConversations; remove use of the
client-controlled query.userId while preserving the existing route behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 60ab5139-c2e8-4847-9ff8-4578c2c97f9a

📥 Commits

Reviewing files that changed from the base of the PR and between 3377f6d and 6e64a3c.

📒 Files selected for processing (4)
  • packages/server-core/src/handlers/memory.handlers.spec.ts
  • packages/server-core/src/handlers/memory.handlers.ts
  • packages/server-elysia/src/routes/memory.routes.ts
  • packages/server-hono/src/routes/memory.routes.ts

Comment thread packages/server-core/src/handlers/memory.handlers.spec.ts
Comment thread packages/server-core/src/handlers/memory.handlers.ts
Comment thread packages/server-elysia/src/routes/memory.routes.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server-core/src/handlers/memory.handlers.ts Outdated
Comment thread packages/server-elysia/src/routes/memory.routes.ts Outdated
Comment thread packages/server-elysia/src/routes/memory.routes.ts Outdated
Comment thread packages/server-core/src/handlers/memory.handlers.spec.ts
Comment thread packages/server-core/src/handlers/memory.handlers.ts
Comment thread packages/server-core/src/handlers/memory.handlers.spec.ts Outdated
@zcxGGmu

zcxGGmu commented Aug 2, 2026

Copy link
Copy Markdown
Author

Thanks for the review — I pushed 95ba4d94 addressing the remaining memory ownership feedback.

Changes made:

  • Treat an explicitly empty authenticated user id as an ownership mismatch instead of bypassing the check.
  • List memory conversations by the authenticated user id when present, ignoring a client-supplied userId for authenticated requests.
  • Pass authenticated identity through the Elysia and Hono list-conversations routes.
  • Move Elysia memory route ownership lookups to request-scoped auth state instead of reading the shared store directly.
  • Add storage-level expectedUserId guards for conversation update/delete and map guarded mutation misses to 403 while preserving 404 for missing conversations.
  • Import InMemoryStorageAdapter from the public @voltagent/core entry in the regression tests.

Validation:

  • npm exec -- vitest run src/handlers/memory.handlers.spec.ts --typecheck in packages/server-core — passed, 9 tests passed, no type errors.
  • npm exec -- vitest run src/routes/memory.routes.spec.ts --typecheck in packages/server-elysia — passed, 1 test passed, no type errors.
  • npm exec -- vitest run src/routes/memory.routes.spec.ts --typecheck in packages/server-hono — passed, 1 test passed, no type errors.
  • npm exec -- biome check <changed files> — passed.
  • npm run typecheck && npm run build in packages/core — passed.
  • npm run typecheck && npm run build in packages/server-core — passed.
  • npm run build in packages/server-elysia and packages/server-hono — passed.

Note: package-wide npm run typecheck in packages/server-elysia / packages/server-hono still reports existing unrelated route typing errors outside this memory-route change; the new focused route tests typecheck cleanly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/server-elysia/src/routes/memory.routes.ts (1)

117-130: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce conversation ownership for working-memory, clone, update-working-memory, delete-messages, and search routes.

These routes still pass only client-supplied IDs/userId. The handlers only reject another user when userId is explicitly provided; they do not accept or reject based on the authenticated requestingUserId, so a non-owner can fetch, update, clone, delete, or search that conversation’s memory data by supplying the conversation ID. Add requestingUserId guards before the operation, with userId checked against the conversation owner where required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server-elysia/src/routes/memory.routes.ts` around lines 117 - 130,
Enforce authenticated ownership across the working-memory, clone,
update-working-memory, delete-messages, and search route handlers by obtaining
the authenticated requestingUserId and passing it into the corresponding memory
operations before they execute. Validate that requestingUserId owns the
conversation, and where a client-supplied userId is accepted, require it to
match the conversation owner; reject unauthorized requests consistently while
preserving existing success and HTTP status handling.
🧹 Nitpick comments (2)
packages/server-core/src/handlers/memory.handlers.spec.ts (1)

143-156: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a list-handler test for an explicit empty requestingUserId.

handleListMemoryConversations uses query.requestingUserId ?? query.userId, a different code path than assertConversationOwner used by handleGetMemoryConversation. The empty-string case tested at lines 133-141 for reads doesn't cover this path. Add a test that passes requestingUserId: "" to handleListMemoryConversations and asserts it returns no conversations, to lock in the "explicitly empty identity" contract across all ownership-sensitive handlers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server-core/src/handlers/memory.handlers.spec.ts` around lines 143 -
156, Add a test alongside the existing handleListMemoryConversations tests that
passes requestingUserId as an explicit empty string while retaining the
authenticated user setup. Assert the handler succeeds with zero total
conversations and an empty conversations list, covering the
query.requestingUserId ?? query.userId path without altering the existing
non-empty identity test.
packages/server-elysia/src/routes/memory.routes.ts (1)

45-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated getAuthenticatedUserId helper.

This function is duplicated verbatim in packages/server-hono/src/routes/memory.routes.ts. Since it implements the exact claim-extraction logic that this PR's ownership enforcement depends on, keeping one shared implementation in @voltagent/server-core reduces the risk of the two copies drifting and silently weakening authorization in one framework but not the other.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server-elysia/src/routes/memory.routes.ts` around lines 45 - 58,
Move the duplicated getAuthenticatedUserId helper into the shared
`@voltagent/server-core` package, export it, and replace the local implementations
in the Elysia and Hono memory routes with that shared import. Preserve the
existing id-then-sub claim extraction and undefined fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/memory/types.ts`:
- Around line 447-452: Update updateConversation and deleteConversation across
D1MemoryAdapter, PostgreSQLMemoryAdapter, LibSQLMemoryCore,
SupabaseMemoryAdapter, and ManagedMemoryAdapter to accept and enforce
ConversationMutationOptions.expectedUserId. Use a single atomic
ownership-filtered UPDATE or DELETE statement keyed by both conversation id and
expectedUserId, and preserve the existing behavior for calls without an expected
user ID.

---

Outside diff comments:
In `@packages/server-elysia/src/routes/memory.routes.ts`:
- Around line 117-130: Enforce authenticated ownership across the
working-memory, clone, update-working-memory, delete-messages, and search route
handlers by obtaining the authenticated requestingUserId and passing it into the
corresponding memory operations before they execute. Validate that
requestingUserId owns the conversation, and where a client-supplied userId is
accepted, require it to match the conversation owner; reject unauthorized
requests consistently while preserving existing success and HTTP status
handling.

---

Nitpick comments:
In `@packages/server-core/src/handlers/memory.handlers.spec.ts`:
- Around line 143-156: Add a test alongside the existing
handleListMemoryConversations tests that passes requestingUserId as an explicit
empty string while retaining the authenticated user setup. Assert the handler
succeeds with zero total conversations and an empty conversations list, covering
the query.requestingUserId ?? query.userId path without altering the existing
non-empty identity test.

In `@packages/server-elysia/src/routes/memory.routes.ts`:
- Around line 45-58: Move the duplicated getAuthenticatedUserId helper into the
shared `@voltagent/server-core` package, export it, and replace the local
implementations in the Elysia and Hono memory routes with that shared import.
Preserve the existing id-then-sub claim extraction and undefined fallback
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 510b6c75-7d80-4689-bf86-c0025a60f667

📥 Commits

Reviewing files that changed from the base of the PR and between 6e64a3c and 95ba4d9.

📒 Files selected for processing (11)
  • packages/core/src/memory/adapters/storage/in-memory.ts
  • packages/core/src/memory/errors.ts
  • packages/core/src/memory/index.ts
  • packages/core/src/memory/types.ts
  • packages/server-core/src/handlers/memory.handlers.spec.ts
  • packages/server-core/src/handlers/memory.handlers.ts
  • packages/server-elysia/src/auth/middleware.ts
  • packages/server-elysia/src/routes/memory.routes.spec.ts
  • packages/server-elysia/src/routes/memory.routes.ts
  • packages/server-hono/src/routes/memory.routes.spec.ts
  • packages/server-hono/src/routes/memory.routes.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server-hono/src/routes/memory.routes.ts

Comment thread packages/core/src/memory/types.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/src/memory/types.ts">

<violation number="1" location="packages/core/src/memory/types.ts:452">
P1: Persistent storage adapters ignore the new `expectedUserId` option, so the ownership check is not enforced at the storage boundary for update/delete operations. Updating every adapter to apply the user ID atomically, or enforcing the check centrally before delegation, would prevent a race or direct `Memory` caller from mutating another user’s conversation.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

options?: ConversationMutationOptions,
): Promise<Conversation>;
deleteConversation(id: string): Promise<void>;
deleteConversation(id: string, options?: ConversationMutationOptions): Promise<void>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Persistent storage adapters ignore the new expectedUserId option, so the ownership check is not enforced at the storage boundary for update/delete operations. Updating every adapter to apply the user ID atomically, or enforcing the check centrally before delegation, would prevent a race or direct Memory caller from mutating another user’s conversation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/memory/types.ts, line 452:

<comment>Persistent storage adapters ignore the new `expectedUserId` option, so the ownership check is not enforced at the storage boundary for update/delete operations. Updating every adapter to apply the user ID atomically, or enforcing the check centrally before delegation, would prevent a race or direct `Memory` caller from mutating another user’s conversation.</comment>

<file context>
@@ -443,8 +447,9 @@ export interface StorageAdapter {
+    options?: ConversationMutationOptions,
   ): Promise<Conversation>;
-  deleteConversation(id: string): Promise<void>;
+  deleteConversation(id: string, options?: ConversationMutationOptions): Promise<void>;
 
   saveConversationSteps?(steps: ConversationStepRecord[]): Promise<void>;
</file context>

Comment thread packages/server-core/src/handlers/memory.handlers.ts
Comment thread packages/core/src/memory/index.ts
Apply expectedUserId checks to persistent conversation mutations, preserve best-effort vector cleanup for unguarded deletes, and reject empty authenticated identities when listing conversations.
@zcxGGmu

zcxGGmu commented Aug 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review — pushed 96bcbabd3 with the ownership follow-ups:

  • Applies expectedUserId guards to persistent conversation update/delete mutations across D1, LibSQL, PostgreSQL, Supabase, and managed memory.
  • Keeps unguarded vector cleanup best-effort by moving the vector-only conversation lookup back inside the catchable path.
  • Rejects an empty authenticated requestingUserId before list-conversation queries can treat it as an omitted filter.
  • Adds regression coverage for the adapter mutation guards, empty identity listing, and unguarded vector-delete fallback.

Validation passed:

  • pnpm exec vitest run packages/server-core/src/handlers/memory.handlers.spec.ts packages/core/src/memory/index.spec.ts packages/cloudflare-d1/src/memory-adapter.spec.ts packages/libsql/src/memory-v2-adapter.spec.ts packages/postgres/src/memory-adapter.spec.ts packages/supabase/src/memory-adapter.spec.ts packages/voltagent-memory/src/index.spec.ts --reporter=default — 7 files / 78 tests passed.
  • pnpm --filter @voltagent/core typecheck
  • pnpm --filter @voltagent/server-core typecheck
  • pnpm --filter @voltagent/cloudflare-d1 build
  • pnpm --filter @voltagent/libsql build
  • pnpm --filter @voltagent/postgres build
  • pnpm --filter @voltagent/supabase build
  • pnpm --filter @voltagent/voltagent-memory build
  • pnpm exec biome check on the changed files

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/voltagent-memory/src/index.spec.ts`:
- Around line 68-110: Expand the mutation ownership tests around
ManagedMemoryAdapter.updateConversation and deleteConversation to cover both
outcomes: add an owner-matched update case asserting conversations.update is
delegated with the expected arguments, and add a mismatched-owner delete case
asserting ConversationOwnershipMismatchError and that conversations.delete is
not called.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc96dae9-7ee0-48b5-8b55-39f964b86a35

📥 Commits

Reviewing files that changed from the base of the PR and between 95ba4d9 and 96bcbab.

📒 Files selected for processing (14)
  • packages/cloudflare-d1/src/memory-adapter.spec.ts
  • packages/cloudflare-d1/src/memory-adapter.ts
  • packages/core/src/memory/index.spec.ts
  • packages/core/src/memory/index.ts
  • packages/libsql/src/memory-core.ts
  • packages/libsql/src/memory-v2-adapter.spec.ts
  • packages/postgres/src/memory-adapter.spec.ts
  • packages/postgres/src/memory-adapter.ts
  • packages/server-core/src/handlers/memory.handlers.spec.ts
  • packages/server-core/src/handlers/memory.handlers.ts
  • packages/supabase/src/memory-adapter.spec.ts
  • packages/supabase/src/memory-adapter.ts
  • packages/voltagent-memory/src/index.spec.ts
  • packages/voltagent-memory/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/server-core/src/handlers/memory.handlers.spec.ts
  • packages/core/src/memory/index.ts
  • packages/server-core/src/handlers/memory.handlers.ts

Comment on lines +68 to +110
it("rejects guarded updates before delegating when the owner does not match", async () => {
const { client, conversations } = createVoltOpsClient();
conversations.get.mockResolvedValue({
id: "conv-1",
userId: "user-2",
resourceId: "agent-1",
title: "Private",
metadata: {},
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
});

const adapter = new ManagedMemoryAdapter({ databaseId: "db-1", voltOpsClient: client });

await expect(
adapter.updateConversation("conv-1", { title: "Updated" }, { expectedUserId: "user-1" }),
).rejects.toBeInstanceOf(ConversationOwnershipMismatchError);

expect(conversations.update).not.toHaveBeenCalled();
});

it("checks ownership before delegated deletes", async () => {
const { client, conversations } = createVoltOpsClient();
conversations.get.mockResolvedValue({
id: "conv-1",
userId: "user-1",
resourceId: "agent-1",
title: "Private",
metadata: {},
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
});
conversations.delete.mockResolvedValue(undefined);

const adapter = new ManagedMemoryAdapter({ databaseId: "db-1", voltOpsClient: client });

await expect(
adapter.deleteConversation("conv-1", { expectedUserId: "user-1" }),
).resolves.toBeUndefined();

expect(conversations.get).toHaveBeenCalledWith("db-1", "conv-1");
expect(conversations.delete).toHaveBeenCalledWith("db-1", "conv-1");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Cover both ownership outcomes for each mutation.

Lines 68-87 test only a denied update. Add an owner-matched update test that verifies conversations.update is called.

Lines 89-110 test only an allowed delete. Add a mismatched-owner delete test that verifies ConversationOwnershipMismatchError and that conversations.delete is not called.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/voltagent-memory/src/index.spec.ts` around lines 68 - 110, Expand
the mutation ownership tests around ManagedMemoryAdapter.updateConversation and
deleteConversation to cover both outcomes: add an owner-matched update case
asserting conversations.update is delegated with the expected arguments, and add
a mismatched-owner delete case asserting ConversationOwnershipMismatchError and
that conversations.delete is not called.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 14 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/postgres/src/memory-adapter.ts">

<violation number="1" location="packages/postgres/src/memory-adapter.ts:11">
P1: Installing this adapter with an older `@voltagent/core` still allowed by its `^2.0.0` peer range can now fail at module load because the new value import is missing. The package peer minimum should be raised to the first core release exporting `ConversationOwnershipMismatchError` (and the corresponding release metadata updated).</violation>
</file>

<file name="packages/cloudflare-d1/src/memory-adapter.spec.ts">

<violation number="1" location="packages/cloudflare-d1/src/memory-adapter.spec.ts:96">
P3: The new adapter tests do not verify the security-critical failure path: a guarded update or delete must reject when the owner does not match or when the database affects zero rows. Adding negative tests for each adapter, including assertions that the conversation and child data remain unchanged, would protect the revalidation behavior claimed by this change.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/core/src/memory/index.ts
import {
ConversationAlreadyExistsError,
ConversationNotFoundError,
ConversationOwnershipMismatchError,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Installing this adapter with an older @voltagent/core still allowed by its ^2.0.0 peer range can now fail at module load because the new value import is missing. The package peer minimum should be raised to the first core release exporting ConversationOwnershipMismatchError (and the corresponding release metadata updated).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/postgres/src/memory-adapter.ts, line 11:

<comment>Installing this adapter with an older `@voltagent/core` still allowed by its `^2.0.0` peer range can now fail at module load because the new value import is missing. The package peer minimum should be raised to the first core release exporting `ConversationOwnershipMismatchError` (and the corresponding release metadata updated).</comment>

<file context>
@@ -5,9 +5,14 @@
+import {
+  ConversationAlreadyExistsError,
+  ConversationNotFoundError,
+  ConversationOwnershipMismatchError,
+} from "@voltagent/core";
 import type {
</file context>

Comment thread packages/voltagent-memory/src/index.ts
Comment thread packages/libsql/src/memory-v2-adapter.spec.ts
Comment thread packages/cloudflare-d1/src/memory-adapter.ts Outdated
Comment thread packages/voltagent-memory/src/index.ts
Comment thread packages/core/src/memory/index.spec.ts Outdated
updated_at: "2024-01-01T00:00:00.000Z",
};

it("adds expectedUserId to updateConversation mutations", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new adapter tests do not verify the security-critical failure path: a guarded update or delete must reject when the owner does not match or when the database affects zero rows. Adding negative tests for each adapter, including assertions that the conversation and child data remain unchanged, would protect the revalidation behavior claimed by this change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cloudflare-d1/src/memory-adapter.spec.ts, line 96:

<comment>The new adapter tests do not verify the security-critical failure path: a guarded update or delete must reject when the owner does not match or when the database affects zero rows. Adding negative tests for each adapter, including assertions that the conversation and child data remain unchanged, would protect the revalidation behavior claimed by this change.</comment>

<file context>
@@ -81,3 +81,59 @@ describe("D1MemoryAdapter queryWorkflowRuns", () => {
+    updated_at: "2024-01-01T00:00:00.000Z",
+  };
+
+  it("adds expectedUserId to updateConversation mutations", async () => {
+    vi.spyOn(D1MemoryAdapter.prototype as any, "ensureInitialized").mockResolvedValue(undefined);
+    const adapter = new D1MemoryAdapter({ binding: createMockBinding(), tablePrefix: "test" });
</file context>

@zcxGGmu

zcxGGmu commented Aug 4, 2026

Copy link
Copy Markdown
Author

Thanks for the review — I pushed 8c5ff4fc addressing the guarded memory mutation follow-ups.

Changes made:

  • Prevent guarded deletes from re-reading vectors when the ownership lookup already found no conversation.
  • Rely on the ownership-filtered D1 parent delete instead of deleting child rows first, avoiding partial child cleanup before ownership is established.
  • Pass expectedUserId through managed-memory remote update/delete calls and reject explicitly empty expected owners.
  • Add regression coverage for matched/mismatched managed-memory mutations, D1 guarded delete/update failures, LibSQL guarded mutation failures, and isolated warning spies.
  • Add a changeset and raise affected package peer minimums to require the core release that exports ownership mismatch errors.

Validation:

  • vitest run packages/core/src/memory/index.spec.ts packages/cloudflare-d1/src/memory-adapter.spec.ts packages/libsql/src/memory-v2-adapter.spec.ts packages/voltagent-memory/src/index.spec.ts --reporter=default — passed, 4 files / 18 tests.
  • npm run typecheck in packages/core — passed.
  • npm run build in packages/core, packages/voltagent-memory, packages/cloudflare-d1, packages/libsql, packages/postgres, and packages/supabase — passed.
  • npm run typecheck in packages/server-core — passed.
  • biome check on the changed source/test files and changeset — passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/core/src/memory/index.spec.ts (2)

64-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the as any cast from the message fixture.

Use the concrete message type or a shared typed fixture. The cast disables TypeScript checks and can hide an invalid stored-message shape in this regression test.

As per coding guidelines, **/*.ts files must maintain type safety in this TypeScript-first codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/memory/index.spec.ts` around lines 64 - 70, Remove the `as
any` cast from the message fixture passed to `storage.getMessages` in the
`getMessagesSpy` setup. Type the fixture using the concrete stored-message type
or an existing shared typed fixture, preserving the same test data while
retaining TypeScript validation.

Source: Coding guidelines


73-82: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the guarded storage call.

Capture the storage.deleteConversation spy and assert that it receives { expectedUserId: "user-1" }. The current test only verifies error propagation. A regression that drops expectedUserId would still pass because the mock rejects for every argument.

Proposed test assertion
-    vi.spyOn(storage, "deleteConversation").mockRejectedValueOnce(deleteError);
+    const deleteSpy = vi
+      .spyOn(storage, "deleteConversation")
+      .mockRejectedValueOnce(deleteError);
...
+    expect(deleteSpy).toHaveBeenCalledWith("conv-1", {
+      expectedUserId: "user-1",
+    });

The storage adapter contract checks expectedUserId before deletion in packages/core/src/memory/adapters/storage/in-memory.ts:456-475.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/memory/index.spec.ts` around lines 73 - 82, Update the
deleteConversation test to retain the spy returned by vi.spyOn(storage,
"deleteConversation"), then assert it was called with { expectedUserId: "user-1"
} after verifying the rejection. Keep the existing error-propagation and
call-count assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/memory/index.spec.ts`:
- Around line 64-70: Remove the `as any` cast from the message fixture passed to
`storage.getMessages` in the `getMessagesSpy` setup. Type the fixture using the
concrete stored-message type or an existing shared typed fixture, preserving the
same test data while retaining TypeScript validation.
- Around line 73-82: Update the deleteConversation test to retain the spy
returned by vi.spyOn(storage, "deleteConversation"), then assert it was called
with { expectedUserId: "user-1" } after verifying the rejection. Keep the
existing error-propagation and call-count assertions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fbc0494-6c42-43eb-9dc2-df2111fbe731

📥 Commits

Reviewing files that changed from the base of the PR and between 96bcbab and 8c5ff4f.

📒 Files selected for processing (16)
  • .changeset/tender-memory-ownership.md
  • packages/cloudflare-d1/package.json
  • packages/cloudflare-d1/src/memory-adapter.spec.ts
  • packages/cloudflare-d1/src/memory-adapter.ts
  • packages/core/src/memory/index.spec.ts
  • packages/core/src/memory/index.ts
  • packages/core/src/voltops/client.ts
  • packages/core/src/voltops/types.ts
  • packages/libsql/package.json
  • packages/libsql/src/memory-v2-adapter.spec.ts
  • packages/postgres/package.json
  • packages/server-core/package.json
  • packages/supabase/package.json
  • packages/voltagent-memory/package.json
  • packages/voltagent-memory/src/index.spec.ts
  • packages/voltagent-memory/src/index.ts
💤 Files with no reviewable changes (1)
  • packages/cloudflare-d1/src/memory-adapter.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/libsql/src/memory-v2-adapter.spec.ts
  • packages/core/src/memory/index.ts
  • packages/voltagent-memory/src/index.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 16 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread .changeset/tender-memory-ownership.md
Comment thread packages/server-core/package.json
@zcxGGmu

zcxGGmu commented Aug 5, 2026

Copy link
Copy Markdown
Author

Thanks for the review — I pushed ca88a846 addressing the latest memory ownership follow-ups.

Changes made:

  • Added @voltagent/server-elysia and @voltagent/server-hono to the memory ownership changeset so those route-level fixes are versioned.
  • Aligned @voltagent/server-core's @voltagent/core dependency floor with its peer dependency at ^2.9.1.
  • Replaced the remaining as any message fixture cast in the memory regression test with a typed UIMessage fixture.

Validation:

  • npm exec -- vitest run packages/core/src/memory/index.spec.ts --reporter=default — passed, 1 file / 2 tests.
  • npm run typecheck in packages/core — passed.
  • npm exec -- biome check .changeset/tender-memory-ownership.md packages/core/src/memory/index.spec.ts packages/server-core/package.json — passed.
  • git diff --cached --check — passed before commit.
  • Staged credential pattern scan — passed.

@zcxGGmu

zcxGGmu commented Aug 5, 2026

Copy link
Copy Markdown
Author

Follow-up: I pushed c7c2c283 to cover the remaining test assertion feedback.

Changes made:

  • Captured the guarded storage.deleteConversation spy and asserted it receives { expectedUserId: "user-1" }.

Validation:

  • npm exec -- vitest run packages/core/src/memory/index.spec.ts --reporter=default — passed, 1 file / 2 tests.
  • npm run typecheck in packages/core — passed.
  • npm exec -- biome check packages/core/src/memory/index.spec.ts — passed.
  • git diff --cached --check — passed before commit.
  • Staged credential pattern scan — passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/server-core/package.json`:
- Line 7: Regenerate pnpm-lock.yaml after the dependency update in
packages/server-core/package.json so the server-core importer records
`@voltagent/core` at ^2.9.1 instead of the stale ^2.8.1 range, then commit the
synchronized lockfile.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 02c8dea7-6095-4eae-9001-265b5254ee21

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5ff4f and c7c2c28.

📒 Files selected for processing (3)
  • .changeset/tender-memory-ownership.md
  • packages/core/src/memory/index.spec.ts
  • packages/server-core/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/tender-memory-ownership.md
  • packages/core/src/memory/index.spec.ts

Comment thread packages/server-core/package.json
@zcxGGmu

zcxGGmu commented Aug 5, 2026

Copy link
Copy Markdown
Author

Follow-up: I pushed a4d2f319 to sync the lockfile with the @voltagent/server-core dependency bump.

Changes made:

  • Updated the packages/server-core importer in pnpm-lock.yaml so @voltagent/core records specifier: ^2.9.1 while keeping the workspace link.

Validation:

  • Lockfile guard script — passed, packages/server-core now matches package.json's @voltagent/core dependency specifier.
  • npm exec -- vitest run packages/core/src/memory/index.spec.ts --reporter=default — passed, 1 file / 2 tests.
  • npm run typecheck in packages/core — passed.
  • npm exec -- biome check packages/core/src/memory/index.spec.ts packages/server-core/package.json — passed.
  • git diff --cached --check — passed before commit.
  • Staged credential pattern scan — passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Memory API Missing Ownership Check -- Cross-User Conversation Access (IDOR) in @voltagent/server-hono

2 participants