Skip to content

🛡️ Sentinel: Enforce strict tenant isolation in dynamics routes - #43

Merged
lucivskvn merged 6 commits into
nextfrom
sentinel-enforce-dynamics-tenant-isolation-1666522845447929763
Jul 19, 2026
Merged

lucivskvn merged 6 commits into
nextfrom
sentinel-enforce-dynamics-tenant-isolation-1666522845447929763

Conversation

@lucivskvn

@lucivskvn lucivskvn commented Jul 16, 2026 •

Copy link
Copy Markdown
Owner

🚨 Severity: HIGH
💡 Vulnerability: Cross-Tenant Data Leakage in Advanced Memory Dynamics Routes
🎯 Impact: Authenticated users could retrieve, reinforce, or query memories and waypoint graphs belonging to other tenants by invoking /dynamics endpoints without proper tenant scoping or ownership checks.
🔧 Fix:

  • Added require_tenant to /dynamics/retrieval/energy-based, /dynamics/reinforcement/trace, /dynamics/activation/spreading, /dynamics/waypoints/graph, and /dynamics/waypoints/calculate-weight.
  • Verified resource ownership (user_id === tenant) for specified memory IDs.
  • Propagated tenant string down to retrieveMemoriesWithEnergyThresholding, performSpreadingActivationRetrieval, and buildAssociativeWaypointGraphFromMemories.
  • Corrected upd_feedback parameter ordering mismatch in hsg.ts.
    ✅ Verification: Ran the test suite successfully and verified that the omnibus test Phase 2 passes regression-free.

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

Summary by CodeRabbit

  • Security
    • Strengthened API key validation to reject mismatched key lengths early and use constant-time comparison.
    • Added a security notice covering a cross-tenant data leakage risk in dynamics endpoints and how to prevent it.
  • Bug Fixes
    • Improved tenant isolation across dynamics retrieval, spreading activation, waypoint graphs, waypoint link-weight calculations, and trace reinforcement, with appropriate 403/404 handling when tenant context is missing or mismatched.
    • Ensured memory feedback updates record their latest update time correctly.

Enforce tenant validation and scoping in advanced memory dynamics
endpoints and propagate the tenant identifier down to the corresponding
helper functions. Also resolve a parameter order mismatch in q.upd_feedback.

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 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Dynamics endpoints now require tenant context and enforce tenant-scoped memory and waypoint access. Retrieval operations propagate tenant filters, API key comparison uses length checks and raw-byte timing-safe comparison, and feedback persistence includes a timestamp with reordered arguments. A security notice documents the tenant-isolation issue.

Changes

Tenant isolation and security hardening

Layer / File(s) Summary
API key validation hardening
packages/openmemory-js/src/server/middleware/auth.ts
API key validation checks equal lengths and compares raw key bytes with a timing-safe comparison.
Tenant-scoped retrieval operations
packages/openmemory-js/src/ops/dynamics.ts
Retrieval, spreading activation, and waypoint graph construction require tenant context and filter records by user_id.
Dynamics route authorisation
.jules/sentinel.md, packages/openmemory-js/src/server/routes/dynamics.ts
Dynamics routes resolve tenant context, validate memory ownership, filter waypoint links, and propagate tenant context to retrieval operations.

Feedback persistence update

Layer / File(s) Summary
Feedback update call
packages/openmemory-js/src/memory/hsg.ts
The feedback database update receives the feedback value, current timestamp, and memory identifier in the updated order.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant dynroutes
  participant require_tenant
  participant dynamics
  participant Database
  Client->>dynroutes: dynamics request
  dynroutes->>require_tenant: resolve tenant
  require_tenant-->>dynroutes: tenant context
  dynroutes->>dynamics: invoke tenant-scoped operation
  dynamics->>Database: query tenant-scoped memories and waypoints
  Database-->>dynamics: filtered records
  dynamics-->>dynroutes: operation result
  dynroutes-->>Client: response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the fix and testing, but it does not follow the required template or include sections like Type of Change and Code Review Checklist. Reformat the PR description to match the template and add the missing sections: Type of Change, Testing, Code Review Checklist, Related Issues, Deployment Notes, and Additional Context.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly captures the main change: enforcing tenant isolation in dynamics routes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel-enforce-dynamics-tenant-isolation-1666522845447929763
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch sentinel-enforce-dynamics-tenant-isolation-1666522845447929763

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: 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/openmemory-js/src/ops/dynamics.ts`:
- Around line 171-179: Make tenant context mandatory across the dynamics
retrieval APIs: update buildAssociativeWaypointGraphFromMemories to require
tenant and always use the tenant-scoped waypoint query, update the
spreading-activation function at lines 209-211 to require tenant, and update the
memories retrieval function at lines 241-247 to require tenant and always use
its scoped query. Apply these changes at
packages/openmemory-js/src/ops/dynamics.ts lines 171-179, 209-211, and 241-247,
removing optional and unscoped fallback paths.
- Around line 175-179: Validate both waypoint endpoints belong to the active
tenant before they are used: update the waypoint query in
packages/openmemory-js/src/ops/dynamics.ts lines 175-179 to enforce ownership
for src_id and dst_id, and add an ownership check before upd_seen in
packages/openmemory-js/src/server/routes/dynamics.ts lines 271-276 so
tenant-scoped waypoints cannot reinforce another tenant’s memory.

In `@packages/openmemory-js/src/server/routes/dynamics.ts`:
- Around line 245-249: Replace truthiness-based ownership checks in the
reinforced memory authorization block (lines 245-249), activation seed
validation block (lines 340-352), and source/target memory authorization block
(lines 498-505) with strict equality checks requiring each relevant user_id to
equal tenant; reject missing or mismatched owners in all three sites.
🪄 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

Run ID: 17a16819-b05e-4e53-9afe-2cb845f89ce7

📥 Commits

Reviewing files that changed from the base of the PR and between 398be15 and 279327a.

📒 Files selected for processing (4)
  • .jules/sentinel.md
  • packages/openmemory-js/src/memory/hsg.ts
  • packages/openmemory-js/src/ops/dynamics.ts
  • packages/openmemory-js/src/server/routes/dynamics.ts

Comment thread packages/openmemory-js/src/ops/dynamics.ts Outdated
Comment thread packages/openmemory-js/src/ops/dynamics.ts Outdated
Comment thread packages/openmemory-js/src/server/routes/dynamics.ts Outdated
google-labs-jules Bot and others added 4 commits July 19, 2026 01:04
Enforce tenant validation and scoping in advanced memory dynamics
endpoints and propagate the tenant identifier down to the corresponding
helper functions. Also resolve a parameter order mismatch in q.upd_feedback.

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
Enforce tenant validation and scoping in advanced memory dynamics
endpoints and propagate the tenant identifier down to the corresponding
helper functions. Also resolve a parameter order mismatch in q.upd_feedback.

Co-authored-by: lucivskvn <7908015+lucivskvn@users.noreply.github.com>
Enforce tenant validation and scoping in advanced memory dynamics
endpoints and propagate the tenant identifier down to the corresponding
helper functions. Also resolve a parameter order mismatch in q.upd_feedback.

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/middleware/auth.ts`:
- Around line 92-96: Update validate_api_key to convert provided and expected to
buffers before validation, compare their byteLength values, and return false
when they differ before calling crypto.timingSafeEqual. Preserve the timing-safe
comparison for equal-length buffers.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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

Run ID: 43b5f9bf-4217-4296-877f-23cfea2c11d5

📥 Commits

Reviewing files that changed from the base of the PR and between 279327a and e9e2469.

📒 Files selected for processing (3)
  • packages/openmemory-js/src/ops/dynamics.ts
  • packages/openmemory-js/src/server/middleware/auth.ts
  • packages/openmemory-js/src/server/routes/dynamics.ts

Comment on lines 92 to 96
function validate_api_key(provided: string, expected: string): boolean {
if (!provided || !expected) return false;

// Hash both values to prevent key length leakage via timing or error channels
const provided_hash = crypto.createHash("sha256").update(provided).digest();
const expected_hash = crypto.createHash("sha256").update(expected).digest();

return crypto.timingSafeEqual(provided_hash, expected_hash);
if (provided?.length !== expected?.length)
return false;
return crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Compare buffer byte lengths to prevent RangeError crashes.

crypto.timingSafeEqual strictly requires the two buffers to have identical byte lengths; otherwise, it throws a RangeError. Since a string's length counts characters rather than bytes, comparing string lengths does not guarantee equal buffer byte lengths (for instance, if an API key contains multi-byte characters). This can lead to uncaught exceptions and 500 errors.

Convert the strings to buffers first, then compare their byteLength.

🔧 Proposed fix
 function validate_api_key(provided: string, expected: string): boolean {
-    if (provided?.length !== expected?.length)
-        return false;
-    return crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
+    const providedBuf = Buffer.from(provided || "");
+    const expectedBuf = Buffer.from(expected || "");
+    if (providedBuf.byteLength !== expectedBuf.byteLength) {
+        return false;
+    }
+    return crypto.timingSafeEqual(providedBuf, expectedBuf);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function validate_api_key(provided: string, expected: string): boolean {
if (!provided || !expected) return false;
// Hash both values to prevent key length leakage via timing or error channels
const provided_hash = crypto.createHash("sha256").update(provided).digest();
const expected_hash = crypto.createHash("sha256").update(expected).digest();
return crypto.timingSafeEqual(provided_hash, expected_hash);
if (provided?.length !== expected?.length)
return false;
return crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
}
function validate_api_key(provided: string, expected: string): boolean {
const providedBuf = Buffer.from(provided || "");
const expectedBuf = Buffer.from(expected || "");
if (providedBuf.byteLength !== expectedBuf.byteLength) {
return false;
}
return crypto.timingSafeEqual(providedBuf, expectedBuf);
}
🤖 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/middleware/auth.ts` around lines 92 - 96,
Update validate_api_key to convert provided and expected to buffers before
validation, compare their byteLength values, and return false when they differ
before calling crypto.timingSafeEqual. Preserve the timing-safe comparison for
equal-length buffers.

@coderabbitai

coderabbitai Bot commented Jul 19, 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.

❌ Cannot run autofix: This PR has merge conflicts.

Please resolve the conflicts with the base branch and try again.

Alternatively, use @coderabbitai resolve merge conflict to automatically resolve the conflicts.

@lucivskvn

Copy link
Copy Markdown
Owner Author

@coderabbitai resolve merge conflict

@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

✅ Merge conflicts resolved successfully!

Resolved 4 conflict file(s). Commit: 39d919ce54748bb763118e84880f2569124957f5 pushed to sentinel-enforce-dynamics-tenant-isolation-1666522845447929763.

3 file operation(s)
  • .jules/sentinel.md (update)
  • packages/openmemory-js/src/ops/dynamics.ts (update)
  • packages/openmemory-js/src/server/routes/dynamics.ts (update)
View agent analysis

Resolved conflicts in:
- .jules/sentinel.md (content)
- packages/openmemory-js/src/ops/dynamics.ts (content)
- packages/openmemory-js/src/server/middleware/auth.ts (content)
- packages/openmemory-js/src/server/routes/dynamics.ts (content)

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

Copy link
Copy Markdown

@lucivskvn
lucivskvn merged commit a1908db into next Jul 19, 2026
9 of 11 checks passed
@lucivskvn
lucivskvn deleted the sentinel-enforce-dynamics-tenant-isolation-1666522845447929763 branch July 19, 2026 10:18
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