Skip to content

🛡️ Sentinel: [HIGH] Enforce strict tenant isolation on cluster sync endpoint - #51

Merged
lucivskvn merged 7 commits into
nextfrom
sentinel/fix-cluster-sync-isolation-18203014218256310329
Jul 24, 2026
Merged

lucivskvn merged 7 commits into
nextfrom
sentinel/fix-cluster-sync-isolation-18203014218256310329

Conversation

@lucivskvn

@lucivskvn lucivskvn commented Jul 22, 2026 •

Copy link
Copy Markdown
Owner

🚨 Severity: HIGH
💡 Vulnerability: Prior to this change, /api/cluster/sync route accepted memory sync payloads without validating the authenticated tenant boundaries. Any authenticated user could submit a sync request claiming any user_id (allowing writing memories to other tenant's space) or specify another tenant's existing memory id and overwrite/hijack its contents.
🎯 Impact: Exploitable multi-tenant data hijacking, deletion, or manipulation across shared server environments.
🔧 Fix:

  • Applied require_tenant to authenticate/derive the tenant.
  • Enforced reject_tenant_mismatch to prevent spoofing of the user_id in the synced payload.
  • Coerced unspecified payload user_id values to the verified tenant ID.
  • Queried any existing memory with the target ID, throwing a 403 Forbidden if it belongs to a different tenant.
  • Coalesced all optional fields to null to prevent SQLite/libSQL TypeError crashes on missing inputs.
  • Cleaned up dead duplicated code (reset_last_decay in decay.ts).
    ✅ Verification: Added comprehensive test assertions in packages/openmemory-js/tests/sectors_tenant.test.ts covering missing tenant validation, mismatch validation, coercion, and cross-tenant overwrite blocks. All tests pass successfully.

PR created automatically by Jules for task 18203014218256310329 started by @lucivskvn

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened tenant isolation for cluster synchronisation, rejecting tenant/user mismatches and overwrite attempts across tenants.
    • Scopes dashboard views to the authenticated tenant to prevent cross-tenant data exposure.
    • Improved syncing of optional fields and tightened input limits for system classifier training.
  • Tests
    • Added coverage for missing authentication, successful syncing, tenant mismatch handling, overwrite/hijack protection, and tenant-scoped dashboard responses.
  • Chores
    • Removed a duplicate decay helper export and updated related test schema/imports.

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@lucivskvn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d68d46b0-5e4e-4ce6-982c-d381291c1542

📥 Commits

Reviewing files that changed from the base of the PR and between 2749e05 and 2b6acb9.

📒 Files selected for processing (2)
  • packages/openmemory-js/src/server/routes/dashboard.ts
  • packages/openmemory-py/src/openmemory/memory/hsg.py

Walkthrough

Cluster sync and dashboard routes now authenticate and scope requests to tenants, with tests covering isolation boundaries. Classifier payload limits, test schema setup, a duplicate decay export, and a Python import were also updated.

Changes

Tenant isolation

Layer / File(s) Summary
Cluster sync tenant enforcement
packages/openmemory-js/src/server/routes/system.ts
The sync route validates tenant identity and record ownership, normalises nullable upsert fields, and bounds classifier training inputs.
Dashboard tenant-scoped queries
packages/openmemory-js/src/server/routes/dashboard.ts
Dashboard endpoints require a tenant and apply tenant- and project-scoped SQL filters with corrected parameter indexing.
Tenant isolation test coverage
packages/openmemory-js/tests/sectors_tenant.test.ts, packages/openmemory-js/tests/setup.ts
Tests cover sync authentication, mismatch and hijack rejection, dashboard filtering, and the updated waypoints schema.
Isolation incident record
.jules/sentinel.md
The Sentinel Journal documents the cluster sync isolation bypass and prevention guidance.

Maintenance cleanup

Layer / File(s) Summary
Decay and chunking maintenance
packages/openmemory-js/src/memory/decay.ts, packages/openmemory-py/src/openmemory/memory/hsg.py
The duplicate reset_last_decay export is removed, and chunk_text is imported from the chunking utility.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ClusterSync
  participant TenantMiddleware
  participant MemoryDatabase
  Client->>ClusterSync: Submit sync payload
  ClusterSync->>TenantMiddleware: Authenticate tenant
  TenantMiddleware-->>ClusterSync: Return verified tenant
  ClusterSync->>MemoryDatabase: Check memory ownership
  MemoryDatabase-->>ClusterSync: Return ownership result
  ClusterSync->>MemoryDatabase: Perform tenant-scoped upsert
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the fix and testing, but it does not follow the required PR template sections or checklists. Add the template sections and fill in type of change, testing checkboxes, code review checklist, related issues, deployment notes, and optional screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: enforcing strict tenant isolation on the cluster sync endpoint.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel/fix-cluster-sync-isolation-18203014218256310329

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

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/openmemory-js/src/server/routes/system.ts`:
- Around line 84-115: Make the ownership validation and deduplicated upsert in
the route’s `q.get_mem.get`/`q.ins_mem.run` flow atomic: wrap both statements in
the database transaction mechanism, or change the upsert to conditionally
preserve the existing `user_id` when a conflicting row already belongs to
another tenant. Ensure concurrent requests cannot overwrite ownership after the
check, while retaining the existing 403 response and version behavior.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f201ab70-eff2-48f8-b3bd-1c9170354a32

📥 Commits

Reviewing files that changed from the base of the PR and between bd486c4 and fccac46.

📒 Files selected for processing (4)
  • .jules/sentinel.md
  • packages/openmemory-js/src/memory/decay.ts
  • packages/openmemory-js/src/server/routes/system.ts
  • packages/openmemory-js/tests/sectors_tenant.test.ts
💤 Files with no reviewable changes (1)
  • packages/openmemory-js/src/memory/decay.ts

Comment on lines 84 to 115
const existing = await q.get_mem.get(data.id);
if (existing && existing.user_id !== tenant) {
return res.status(403).json({
error: "tenant_mismatch",
message: "Target memory belongs to another tenant.",
});
}

// Handle version tracker deduplication check
if (!existing || (data.version ?? 1) > existing.version) {
// We do an upsert
// We do an upsert, coalescing undefined fields to null to avoid libSQL TypeError
await q.ins_mem.run(
data.id,
data.user_id,
data.project_id,
data.segment,
data.user_id ?? null,
data.project_id ?? null,
data.segment ?? null,
data.content,
data.simhash,
data.simhash ?? null,
data.primary_sector,
data.tags,
data.meta,
data.created_at,
data.updated_at,
data.last_seen_at,
data.salience,
data.decay_lambda,
data.tags ?? null,
data.meta ?? null,
data.created_at ?? null,
data.updated_at ?? null,
data.last_seen_at ?? null,
data.salience ?? null,
data.decay_lambda ?? null,
data.version ?? 1,
data.mean_dim,
data.mean_vec,
data.compressed_vec,
data.feedback_score,
data.mean_dim ?? null,
data.mean_vec ?? null,
data.compressed_vec ?? null,
data.feedback_score ?? null,
);

Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect q.ins_mem and q.get_mem SQL and any transaction helpers
fd -t f -e ts -p 'core/db' | xargs rg -nP -C3 '(ins_mem|get_mem)\s*[:=]'
rg -nP -C2 '(INSERT|REPLACE|ON CONFLICT|BEGIN|transaction)' packages/openmemory-js/src/core/db* 2>/dev/null

Repository: lucivskvn/OpenMemory-OSS

Length of output: 1999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -t f -e ts packages/openmemory-js/src | rg 'db|routes/system' || true

echo "== db query definitions around ins_mem/get_mem =="
sed -n '305,430p' packages/openmemory-js/src/core/db.ts

echo "== system route around ownership check =="
sed -n '60,130p' packages/openmemory-js/src/server/routes/system.ts

echo "== schema/index definitions for memories =="
rg -n -C4 'CREATE TABLE[[:space:]]+memories|CREATE UNIQUE|primary key|CREATE INDEX' packages/openmemory-js/src -S

Repository: lucivskvn/OpenMemory-OSS

Length of output: 19365


Make the ownership check transactional before the upsert.

q.get_mem.get(data.id) and q.ins_mem.run(...) are separate statements while ins_mem uses an unconditional INSERT ... ON CONFLICT DO UPDATE. With two concurrent requests from different tenants for the same non-existent data.id, both can pass the existing === null check, with the later write overwriting the earlier tenant’s record. Wrap the read + upsert in a transaction, or use a write-time ownership guard such as an insert-conditioned/statement that leaves user_id unchanged unless the row is still absent.

🤖 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/openmemory-js/src/server/routes/system.ts` around lines 84 - 115,
Make the ownership validation and deduplicated upsert in the route’s
`q.get_mem.get`/`q.ins_mem.run` flow atomic: wrap both statements in the
database transaction mechanism, or change the upsert to conditionally preserve
the existing `user_id` when a conflicting row already belongs to another tenant.
Ensure concurrent requests cannot overwrite ownership after the check, while
retaining the existing 403 response and version behavior.

google-labs-jules Bot and others added 3 commits July 23, 2026 01:29
…d protect classifier training against DoS

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
…rd routes and mitigate training DoS risks

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
…rd routes and fix CI errors

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/openmemory-js/src/server/routes/dashboard.ts`:
- Around line 134-147: Remove or tenant-scope the global telemetry exposed by
the tenant-facing dashboard routes: in
packages/openmemory-js/src/server/routes/dashboard.ts lines 134-147, update the
/dashboard/stats flow and its underlying metrics query/schema to filter request
metrics by tenant, or omit those aggregates; at lines 448-449, likewise
tenant-scope maintenance metrics or remove them from /dashboard/maintenance.
Ensure no tenant can infer other tenants’ workload.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1851036c-cae4-49c4-a96a-b62dfe115038

📥 Commits

Reviewing files that changed from the base of the PR and between d7bb0db and 2749e05.

📒 Files selected for processing (4)
  • packages/openmemory-js/src/server/routes/dashboard.ts
  • packages/openmemory-js/tests/sectors_tenant.test.ts
  • packages/openmemory-js/tests/setup.ts
  • packages/openmemory-py/src/openmemory/memory/hsg.py

Comment thread packages/openmemory-js/src/server/routes/dashboard.ts
google-labs-jules Bot and others added 2 commits July 23, 2026 02:53
…rd routes and fix CI errors

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
…rd routes and fix CI errors

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 1 unresolved review comment.

Files modified:

  • packages/openmemory-js/src/core/db.ts
  • packages/openmemory-js/src/server/routes/system.ts

Commit: 4f17fc88ed8a9f9fd7b748ccb82997931e6fec5d

The changes have been pushed to the sentinel/fix-cluster-sync-isolation-18203014218256310329 branch.

Time taken: 13m 37s

Fixed 2 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant