How the `[slots] per_user = true` Option Limits Prompt Injection Without Changing Page Access
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, 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, the engine applies automatic path rewriting at write time:
| Write Target | Resulting Path | Visibility |
|---|---|---|
_slots/current-focus.md (Alice writes) |
_slots/u-alice/current-focus.md |
Alice only |
_slots/current-focus.md (Bob writes) |
_slots/u-bob/current-focus.md |
Bob only |
_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— Documents the "pinned" guard and slot injection boundary, explaining how_slots/paths receive special treatmentcrates/ai-memory-wiki/src/wiki.rs— Contains the path checkpath.as_str().starts_with("_slots/")that triggers slot-specific handling at line 2199crates/ai-memory-store/tests/slot_visibility.rs— Unit tests verifying that personal slots returnSlotVisibility::PerUserrestrictions 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.mdfiles 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
# 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)
// 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. Other operators' briefings exclude this content entirely.
Visibility Verification
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
// 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 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 that checks isolation boundaries.
Summary
[slots] per_user = truenamespaces 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 inwiki.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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →