# How ai‑memory's Per‑Operator Memory Slots Isolate Context Injection Between Shared‑Server Users

> Learn how ai-memory's per-operator memory slots isolate context injection for shared-server users. Discover deterministic namespace derivation, visibility filtering, and placement guards preventing cross-user slot injection.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: internals
- Published: 2026-08-30

---

**ai‑memory isolates per‑operator context through deterministic namespace derivation, read‑time `SlotVisibility` filtering, and write‑time `SlotPlacement` guards that prevent cross‑user slot injection.**

ai‑memory provides a memory layer for AI‑augmented workflows, designed to run on shared servers where multiple operators might coexist. Its **per‑operator memory slots** feature ensures that one user cannot inject context into another user's session brief—a critical security boundary for multi‑tenant deployments. This article explains the three‑layer isolation mechanism implemented in the `akitaonrails/ai-memory` repository.

## Namespace Derivation with IdentityKey

Every operator in ai‑memory is represented by an **`IdentityKey`** type. When converted to a storage path, it produces a deterministic, URL‑safe identifier through `IdentityKey::path_segment()`.

In [`crates/ai-memory-core/src/slots.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/slots.rs) lines 15–21, the namespace is derived as follows:

- `IdentityKey::User("alice")` → `path_segment()` → `"u-alice"`
- Personal slots live at `"_slots/{namespace}/{filename}"`

This design eliminates raw username injection. Because the segment is produced by a typed transformation rather than string concatenation, glob characters or path traversal sequences in usernames cannot create wildcard namespaces that match other operators' slots.

## SlotVisibility: Read‑Time Filtering

The **`SlotVisibility`** enum controls which `_slots/*` pages a viewer may see. It is constructed via `SlotVisibility::for_viewer(per_user, viewer)` and evaluated through `SlotVisibility::allows`.

From [`crates/ai-memory-core/src/slots.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/slots.rs) lines 67–85:

- When `[slots] per_user = true` in configuration, the viewer's namespace is extracted from their `IdentityKey`
- Shared slots (paths without an owner namespace, such as `"_slots/current-focus.md"`) are always allowed because `slot_owner()` returns `None`
- Personal slots are allowed only if `slot_owner(path) == viewer_namespace`

The `session_brief_pages_with_slot_visibility` function in [`crates/ai-memory-store/src/reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/reader.rs) lines 4392–4406 applies this filter to all briefing queries. This ensures that even if a malicious actor somehow guesses another user's slot path, the query layer drops it before the brief is assembled.

## SlotPlacement: Write‑Time Protection

To prevent **context injection**—where one operator writes text that appears in another's brief—ai‑memory enforces namespace ownership on every upsert. The `slot_placement` function in [`crates/ai-memory-core/src/slots.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/slots.rs) lines 64–81 examines:

- The target slot path
- The writer's `IdentityKey` (if provided)

It returns one of three outcomes:

| Outcome | Meaning |
|---------|---------|
| `OwnNamespace` | Writer owns this slot; write proceeds |
| `RewriteToOwn` | Redirect into writer's personal namespace |
| `ForeignNamespace` | Reject: writer cannot inject into another's slot |

This guard operates before any page content reaches storage. A shared‑server deployment can safely expose write endpoints because the engine itself prevents cross‑boundary injection.

## Practical Isolation Examples

### Creating a Personal Slot

```rust
use ai_memory_core::{IdentityKey, NewPage, PagePath, Tier};
use ai_memory_store::Store;

let store = Store::open("/tmp/ai-memory").unwrap();
let ws = store.writer.get_or_create_workspace("default".into()).await.unwrap();
let proj = store.writer.get_or_create_project(ws, "demo".into(), None).await.unwrap();

let alice = IdentityKey::User("alice".into());
let alice_slot = format!("_slots/{}/current-focus.md", alice.path_segment());

store.writer
    .upsert_page(NewPage {
        workspace_id: ws,
        project_id: proj,
        path: PagePath::new(&alice_slot).unwrap(),
        title: "Alice’s focus".into(),
        body: "Working on feature X".into(),
        tier: Tier::Semantic,
        frontmatter_json: serde_json::json!({}),
        pinned: true,
        links: vec![],
        author_id: None,
        expires_at: None,
        entities: vec![],
    })
    .await
    .unwrap();

```

This creates `"_slots/u-alice/current-focus.md"`—visible only to Alice when `per_user` isolation is enabled.

### Querying with Visibility Constraints

```rust
use ai_memory_core::SlotVisibility;

let vis = SlotVisibility::for_viewer(true, Some(&alice));
let pages = store
    .reader
    .session_brief_pages_with_slot_visibility(ws, proj, 100, 100, vis)
    .await
    .unwrap()
    .0;
// Contains Alice's slot, excludes all other personal slots

```

### Blocked Cross‑User Write

```rust
let bob = IdentityKey::User("bob".into());
let bob_slot = format!("_slots/{}/current-focus.md", bob.path_segment());

let placement = ai_memory_core::slot_placement(&bob_slot, Some(&alice));
assert_eq!(placement, ai_memory_core::SlotPlacement::ForeignNamespace);
// Write rejected: Alice cannot inject context into Bob's namespace

```

## Integration Across Surfaces

The same visibility rules apply consistently:

- **HTTP layer**: [`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs) lines 1502–1504 constructs `SlotVisibility` from the authenticated request identity
- **MCP endpoint**: [`crates/ai-memory-mcp/src/server.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs) lines 2291–2294 builds the filter for remote Model Context Protocol clients
- **Storage tests**: [`crates/ai-memory-store/tests/slot_visibility.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/tests/slot_visibility.rs) verifies that hostile viewer names cannot match foreign namespaces and that recent‑page pointers hide other operators' personal slots

## Summary

- **Deterministic namespaces** from `IdentityKey::path_segment()` prevent username‑based traversal attacks
- **`SlotVisibility`** filters reads at query time, dropping unauthorized personal slots from session briefs
- **`SlotPlacement`** blocks writes to foreign namespaces, eliminating context injection vectors
- **Consistent application** across HTTP, MCP, and internal storage APIs ensures no leakage paths

## Frequently Asked Questions

### What happens if `per_user` is disabled in configuration?

When `[slots] per_user = false`, `SlotVisibility::for_viewer` returns `SlotVisibility::All`. All slots—both shared and personal—become visible to every viewer. This preserves backward compatibility for single‑user deployments but removes isolation guarantees.

### Can an operator access another user's slot if they guess the exact path?

No. The `SlotVisibility::allows` check in [`reader.rs`](https://github.com/akitaonrails/ai-memory/blob/main/reader.rs) validates ownership against the viewer's derived namespace, not just the string path. Even with exact path knowledge, unauthorized slots are filtered from briefing results.

### How does ai‑memory handle legacy shared slots?

Slots at paths like `"_slots/current-focus.md"` without an owner namespace are treated as shared. `slot_owner()` returns `None` for these, and `SlotVisibility::allows` always permits them regardless of viewer identity.

### Where is the isolation mechanism tested?

The [`slot_visibility.rs`](https://github.com/akitaonrails/ai-memory/blob/main/slot_visibility.rs) test suite in `crates/ai-memory-store/tests/` covers namespace collision resistance, cross‑operator visibility blocking, and recent‑page pointer sanitization.