Skip to content

Fix #2160: sanitise agent_id and learning_id paths in markdown_store (CWE-22)#903

Open
AlexMikhalev wants to merge 10 commits into
mainfrom
task/2160-path-traversal-markdown-store
Open

Fix #2160: sanitise agent_id and learning_id paths in markdown_store (CWE-22)#903
AlexMikhalev wants to merge 10 commits into
mainfrom
task/2160-path-traversal-markdown-store

Conversation

@AlexMikhalev
Copy link
Copy Markdown
Contributor

Summary

Fixes Gitea terraphim/terraphim-ai#2160 — P2 security: path traversal (CWE-22) in shared_learning markdown_store.

source_agent was joined directly into filesystem paths in five places without
any sanitisation, allowing a crafted agent ID such as ../../../etc/passwd to
escape the learnings_dir boundary.

Changes

  • Added sanitise_path_component(s: &str) -> String — replaces every character
    outside [a-zA-Z0-9._-] with _ and emits tracing::warn! on sanitisation
    (audit trail for injection attempts).
  • Applied to all path-construction sites: agent_dir, save, save_to_shared,
    load, delete.
  • 5 new regression tests: slash stripping, null-byte stripping, full traversal
    sequences, end-to-end save with malicious source_agent, and save_to_shared
    with traversal value.

Vulnerable functions fixed

Function Vector Fix applied
agent_dir(agent_id) ../ in agent_id sanitise_path_component
save(learning) ../ in learning.id sanitise_path_component
save_to_shared(learning) ../ in source_agent + id sanitise_path_component
load(agent_id, learning_id) both params sanitise_path_component
delete(agent_id, learning_id) both params sanitise_path_component

Test plan

cargo test -p terraphim_agent --lib --features shared-learning -- shared_learning::markdown_store
# 13 passed, 0 failed
cargo clippy -p terraphim_agent --features shared-learning -- -D warnings
# clean (exit 0)

Refs terraphim/terraphim-ai#2160 (Gitea)

Test User and others added 10 commits June 4, 2026 00:14
…1992

reqwest::Error from error_for_status() on a 400 response is an HTTP
application error, not a transport/connectivity failure. Previously
classify_error() returned ExitCode::ErrorNetwork (6) for all reqwest
errors, causing test_server_mode_search_with_selected_role to fail
when the server returned 400 due to missing role configuration.

Now check re.status() before falling back to ErrorNetwork:
- 401 / 403  → ErrorAuth (5)
- 404        → ErrorNotFound (4)
- other 4xx/5xx → ErrorGeneral (1)
- no status  → ErrorNetwork (6, true connectivity failure)

Adds five regression tests using a real one-shot TCP server so no
mocks are needed, covering 400, 401, 403, 404, 500 paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an integration test that starts a real TCP listener replying with
HTTP 400 Bad Request and asserts the binary exits 0 or 1 (never 6).
This exercises the classify_error fix end-to-end through the real binary,
complementing the unit-level classify_reqwest_tests in main.rs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…_node_ids Refs #2039

Deserialising a SerializableRoleGraph JSON written before issue #84 (trigger-based
KG retrieval) was merged would fail with a missing-field error because
trigger_descriptions and pinned_node_ids had no serde(default) annotation.

Adds the annotation to both fields and a round-trip regression test that strips
the fields from a serialised graph and confirms deserialisation succeeds with
empty collections, matching the existing learning_document_ids pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2133

Replace `for stream in incoming() { if let Ok(s) = stream { ... } }` with
`for s in incoming().flatten() { ... }` to satisfy clippy::manual_flatten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(feature = "firecracker")

firecracker.rs called get_vm_status() and execute_vm_code() from ApiClient
which are #[cfg(feature = "firecracker")] but the module was compiled unconditionally.
This broke cargo test -p terraphim_agent with default features.

Fix: add #[cfg(feature = "firecracker")] to pub mod firecracker in modes/mod.rs
and update HybridExecutor to conditionally use FirecrackerExecutor only when
the feature is enabled, falling back to LocalExecutor otherwise.

Refs #2164

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ths (CWE-22)

source_agent was joined directly into filesystem paths in agent_dir(), save(),
save_to_shared(), load(), and delete() — enabling path traversal attacks (CWE-22)
via crafted agent IDs such as "../../../etc/passwd".

Add sanitise_path_component() which replaces every character that is not
ASCII-alphanumeric, hyphen, underscore, or dot with an underscore, and emits a
tracing::warn! when sanitisation is required (audit trail for injection attempts).

Apply sanitisation at every path-construction site:
- agent_dir(): agent_id
- save(): learning.id in filename
- save_to_shared(): both learning.source_agent and learning.id in filename
- load(): learning_id in filename
- delete(): learning_id in filename

Add 5 regression tests covering strip of slashes, null bytes, traversal sequences,
and end-to-end save/load with malicious source_agent values.

Fixes #2160
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