# How the `[slots] per_user = true` Option Limits Prompt Injection Without Changing Page Access

> Discover how the `slots` per_user = true option in ai-memory secures against prompt injection by namespacing slot writes privately without altering page access.

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

---

**The `[slots] per_user = true` configuration prevents cross-operator prompt injection by automatically namespacing slot writes to private per-user paths while leaving shared page access unchanged.**

In multi-operator AI systems, **memory slots** serve as injection points for LLM context—making them prime targets for prompt injection attacks. The `ai-memory` repository implements a `per_user` flag that isolates slot content per operator without breaking existing page permissions. This article explains the mechanism using actual source paths and test results from the codebase.

## What Prompt Injection Means for Memory Slots

Prompt injection occurs when a malicious operator writes content into a shared slot that later gets injected into another operator's LLM prompt. In `ai-memory`, slots under `_slots/` are automatically included in session briefings, creating a trusted data boundary that attackers try to poison.

The default `per_user = false` allows any operator to write to [`_slots/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/current-focus.md), making that content appear in everyone's briefing. The `per_user = true` flag eliminates this shared attack surface.

## How Per-User Namespacing Works

When `[slots] per_user = true` is enabled in [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml), the engine applies **automatic path rewriting** at write time:

| Write Target | Resulting Path | Visibility |
|-------------|----------------|------------|
| [`_slots/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/current-focus.md) (Alice writes) | [`_slots/u-alice/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/u-alice/current-focus.md) | Alice only |
| [`_slots/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/current-focus.md) (Bob writes) | [`_slots/u-bob/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/u-bob/current-focus.md) | Bob only |
| [`_slots/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/current-focus.md) (anonymous) | `_slots/u-<uuid>/current-focus.md` | That session only |

This transformation happens transparently. The `session_brief_pages_with_slot_visibility` function in the storage layer filters results based on the caller's identity, ensuring operators see only their own namespaced slots plus legitimate shared slots.

## Core Implementation Files

The namespacing logic spans three layers of the codebase:

- **[`docs/ARCHITECTURE.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/ARCHITECTURE.md)** — Documents the "pinned" guard and slot injection boundary, explaining how `_slots/` paths receive special treatment
- **[`crates/ai-memory-wiki/src/wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-wiki/src/wiki.rs)** — Contains the path check `path.as_str().starts_with("_slots/")` that triggers slot-specific handling at line 2199
- **[`crates/ai-memory-store/tests/slot_visibility.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/tests/slot_visibility.rs)** — Unit tests verifying that personal slots return `SlotVisibility::PerUser` restrictions correctly

## Visible vs. Private Slot Access

The key insight: **page access semantics remain unchanged**. Shared slots created before `per_user` was enabled stay globally readable. New writes to generic slot paths get redirected, but:

- Existing [`_slots/shared.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/shared.md) files continue working for all operators
- Direct reads of specific paths still succeed if permissions allow
- Admin users retain cross-namespace write capability via `AuthLevel::authorize(Capability::Admin)`

This preserves backward compatibility while closing the injection vector.

## Enabling Per-User Slots in Practice

### Configuration

```toml

# config.toml

[slots]
per_user = true          # Activate per-operator slot isolation

```

The flag is read once at startup by `Config::load()` and governs all subsequent slot writes.

### Write Behavior (Automatic Rewriting)

```rust
// Operator "alice" writes to a generic slot path
let resp = mcp(
    alice, 
    "memory_write_page", 
    json!({
        "path": "_slots/current-focus.md",
        "title": "Alice focus",
        "body": "ALICE-SECRET-FOCUS",
        "tier": "semantic"
    })
);

```

The engine stores this at [`_slots/u-alice/current-focus.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/u-alice/current-focus.md). Other operators' briefings exclude this content entirely.

### Visibility Verification

```rust
let pages = store.session_brief_pages_with_slot_visibility(
    ws, proj, 100, 100, SlotVisibility::PerUser
).await?;

// Bob cannot see Alice's private slot
assert!(!pages.contains(&"_slots/u-alice/current-focus.md".to_string()));

```

### Admin Override Capability

```rust
// Requires Admin capability; normal users blocked at authorization layer
let admin_write = mcp(
    admin, 
    "memory_write_page", 
    json!({
        "path": "_slots/u-bob/current-focus.md",
        "title": "Admin override",
        "body": "ADMIN CONTENT",
        "tier": "semantic"
    })
);

```

## Test Coverage and Verification

The [`docker/multiuser-test/drive.sh`](https://github.com/akitaonrails/ai-memory/blob/main/docker/multiuser-test/drive.sh) integration script validates the full flow: multiple operators write to identical slot paths, and assertions confirm each write lands in a distinct per-user namespace. This matches the unit test coverage in [`slot_visibility.rs`](https://github.com/akitaonrails/ai-memory/blob/main/slot_visibility.rs) that checks isolation boundaries.

## Summary

- **`[slots] per_user = true`** namespaces slot writes transparently—operators use familiar paths while the engine enforces isolation
- **Prompt injection is blocked** because attackers cannot pollute shared briefing content; malicious writes stay trapped in private namespaces
- **Page access unchanged**—existing shared slots remain readable, and the permission model for non-slot pages is unaffected
- **Three-layer enforcement** — configuration, storage filtering via `session_brief_pages_with_slot_visibility`, and wiki path guards in [`wiki.rs`](https://github.com/akitaonrails/ai-memory/blob/main/wiki.rs)

## Frequently Asked Questions

### Does `per_user = true` break existing shared slots?

No. Slots created before enabling the flag remain globally visible. The setting only affects **new writes** to generic `_slots/` paths, which get redirected to per-user namespaces. Direct access to specific paths works unchanged.

### Can operators still share slot content intentionally?

Yes. Operators can write to explicitly shared paths like [`_slots/shared.md`](https://github.com/akitaonrails/ai-memory/blob/main/_slots/shared.md) or use non-slot pages for collaboration. The `per_user` flag specifically protects the injection boundary for automatic briefing context, not all inter-operator communication.

### What happens with anonymous or API-authenticated sessions?

Anonymous sessions receive UUID-based namespaces (`_slots/u-<uuid>/`). API keys or session tokens map to consistent identifiers, so returning users access their previous private slots. The [`slot_visibility.rs`](https://github.com/akitaonrails/ai-memory/blob/main/slot_visibility.rs) tests verify both named and anonymous isolation.

### Can administrators bypass per-user isolation?

Admins with `Capability::Admin` can write to any namespaced slot path, but this requires explicit authorization through `AuthLevel::authorize()`. Normal operators cannot escalate to cross-namespace access, maintaining the injection barrier for non-privileged accounts.