Skip to content

fix(memory): reject peer: key prefix and colon-bearing peer_id at substrate boundary#5161

Merged
houko merged 2 commits into
mainfrom
fix/5119-5120-memory-key-validation
May 17, 2026
Merged

fix(memory): reject peer: key prefix and colon-bearing peer_id at substrate boundary#5161
houko merged 2 commits into
mainfrom
fix/5119-5120-memory-key-validation

Conversation

@houko

@houko houko commented May 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes two paired confused-deputy holes in the shared-memory substrate:

Changes

  • KernelHandle::MemoryAccess::memory_store / memory_recall / memory_list reject peer:-prefixed key and colon-bearing peer_id with KernelOpError::InvalidInput.
  • peer_scoped_key now returns Result<String, KernelOpError> and refuses both vectors. Internal callers (messaging.rs, agent_execution.rs) handle the new Result by skipping the user_name lookup with a WARN on malformed peer_id, keeping prompt-build best-effort rather than failing the turn.
  • WASM host_kv_set / host_kv_get (crates/librefang-runtime/src/host_functions.rs) mirror the peer: prefix rejection.

Backward compatibility

Pre-fix rows already at (shared_agent_id, "peer:...") stay readable via direct substrate access by internal kernel code. This change only blocks new writes from the tool layer, so no on-disk migration is required. Operators can audit residual planted rows with a one-shot SQL scan against the shared-memory agent id (00000000-0000-0000-0000-000000000001) filtering on key LIKE 'peer:%'.

Tests

  • Existing kernel/tests.rs::test_peer_scoped_key extended with rejection cases for both vectors.
  • 5 new contract tests in crates/librefang-kernel/tests/kernel_handle_contract_memory.rs:
    • test_memory_store_rejects_peer_prefix_key
    • test_memory_recall_rejects_peer_prefix_key
    • test_memory_list_rejects_peer_id_with_colon
    • test_memory_store_rejects_peer_id_with_colon
    • test_memory_store_with_clean_peer_id_still_works

Verification note

The implementing agent dispatch terminated before running cargo verification locally; rustfmt clean, pre-commit hook (CHANGELOG attribution + secret scan) passed. CI is the authoritative gate for clippy + scoped tests on this PR.

Closes #5119
Closes #5120

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b69a2e2e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +18 to +21
if pid.contains(':') {
return Err(KernelOpError::InvalidInput(format!(
"peer_id '{pid}' must not contain ':' (reserved namespace separator)"
)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve valid channel IDs before rejecting colons

When the sender context comes from channels that legitimately use colon-bearing IDs, this new check makes all memory_store, memory_recall, and memory_list tool calls fail with InvalidInput. For example, the Matrix adapter puts the room id in ChannelUser.platform_id and build_sender_context falls back to that as SenderContext.user_id; Matrix room/user ids are of the form !room:server / @user:server, and WebUI IPv6 client IPs also contain :. The security boundary still needs an injective encoding/escaping scheme, but rejecting every colon disables shared memory for those supported contexts.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot added area/docs Documentation and guides area/runtime Agent loop, LLM drivers, WASM sandbox area/kernel Core kernel (scheduling, RBAC, workflows) has-conflicts PR has merge conflicts that need resolution size/L 250-999 lines changed labels May 16, 2026
…strate boundary

Two paired confused-deputy holes in the shared-memory substrate.
(#5119) peer_scoped_key rendered peer-scoped rows as peer:{peer_id}:{key}
which is non-injective when peer_id contains ':' (Slack T1:U2, IRC
user:42) or is empty (peer::{key} collides with None-scope ':{key}').
(#5120) memory_store accepted any LLM-supplied key, letting an agent
without peer context write peer:victim:user_name into the shared
namespace and have memory_list(Some("victim")) recover it.

Both vectors rejected at the KernelHandle::MemoryAccess boundary with
KernelOpError::InvalidInput. peer_scoped_key now returns Result and
also refuses empty peer_id. memory_list no longer blindly strip_prefixes:
each recovered inner key must round-trip through the now-strict
peer_scoped_key for that exact peer_id, dropping nested/double-scoped
plants and structurally-impossible rows; colon-bearing list queries are
rejected before recovery. WASM host_kv_set/host_kv_get mirror the peer:
prefix rejection as documented defense-in-depth (the <agent_id>: prefix
is what actually neutralizes a clean peer: row from a guest). Internal
callers handle the new Result with a WARN-and-skip so prompt build stays
best-effort.

Residual requiring maintainer sign-off: a pre-fix row written by a
None-scope agent at the exact bytes peer:{colon-free-pid}:{non-peer-key}
is byte-identical to a legitimate post-fix row and cannot be separated
in-code without a writer-attribution column; closing that finite
pre-fix-window set needs a one-shot DB scrub (documented in CHANGELOG).

Closes #5119
Closes #5120
@houko
houko force-pushed the fix/5119-5120-memory-key-validation branch from 5b69a2e to 3a5182a Compare May 17, 2026 02:29

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a5182a4b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +177 to +178
match peer_scoped_key(inner, Some(pid)) {
Ok(canonical) if canonical == k => Some(inner.to_string()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Migrate historical colon-peer rows

When an existing database contains rows written before this fix by a colon-bearing peer ID, this round-trip still accepts the collided key for the shorter peer. For example, a historical peer_id = "victim:sub" / key = "car" row is stored as peer:victim:sub:car; memory_list(Some("victim")) strips peer:victim: and peer_scoped_key("sub:car", Some("victim")) recreates the same bytes, so it exposes sub:car, and memory_recall("sub:car", Some("victim")) reads it as well. This leaves #5119 exploitable on upgraded installs unless a migration/scrub or writer-attribution check removes/blocks those legacy collision rows.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels May 17, 2026
@houko
houko merged commit 00c7c8a into main May 17, 2026
@houko
houko deleted the fix/5119-5120-memory-key-validation branch May 17, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides area/kernel Core kernel (scheduling, RBAC, workflows) area/runtime Agent loop, LLM drivers, WASM sandbox ready-for-review PR is ready for maintainer review size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memory: memory_store accepts attacker-controlled peer:<id>: key prefix memory: peer_scoped_key is non-injective, causes cross-peer key collisions

1 participant