Skip to content

fix(security): config trust boundary + injection hardening (#594)#597

Open
thijs-hakkenberg wants to merge 1 commit into
zilliztech:mainfrom
thijs-hakkenberg:security/config-trust-boundary-and-injection
Open

fix(security): config trust boundary + injection hardening (#594)#597
thijs-hakkenberg wants to merge 1 commit into
zilliztech:mainfrom
thijs-hakkenberg:security/config-trust-boundary-and-injection

Conversation

@thijs-hakkenberg

Copy link
Copy Markdown

Summary

Hardens the four highest-confidence findings from the security review in #594. Each fix is test-backed and the full suite passes (235 passed, 7 skipped).

The remaining findings in #594 (JS/TS bash -c → argv, prompts.summarize shell-side restriction, PID validation, curl | sh bootstrap, unbounded transcript reads) are intentionally left for follow-up PRs to keep this one reviewable.

Changes

🔴 Config trust boundary — .memsearch.toml can no longer set endpoint/credential fields (#594 findings 1/3/6)

resolve_config() now strips security-sensitive fields from the project-local .memsearch.toml before merging, since that file ships inside any cloned/opened repo and is therefore untrusted. Stripped: embedding.{provider,base_url,api_key}, llm.{provider,base_url,api_key,providers}, compact.{llm_provider,base_url,api_key}, milvus.{uri,token}, reranker.model, and all of prompts.*. A UserWarning lists what was ignored.

Without this, a malicious repo could set embedding.base_url = "https://evil/v1" and the OpenAI SDK would attach the victim's ambient OPENAI_API_KEY to requests sent there — exfiltrating the key (and all indexed content / conversation summaries) on the next index, which the Claude Code plugin triggers automatically on session start. Global config and explicit CLI flags are trusted and unaffected.

🔴 os.systemsubprocess in OpenCode capture-daemon (#594 finding 2)

capture-daemon.py built a shell string from memory_dir (derived from the project path) and ran it via os.system on every poll cycle — a path component containing a shell metacharacter was command execution. Replaced with subprocess.Popen([...argv], start_new_session=True) using the split_memsearch_cmd helper already in the file. No shell.

🟡 Escape chunk_hash in expand (#594 finding 8)

cli.py was the one caller building a Milvus filter without the _escape_filter_value helper used everywhere else. Now escaped.

🟠 Pin-able model revisions for reranker + ONNX embeddings (#594 finding 7, CWE-367)

Model ids now accept a repo@revision suffix, threaded through every hf_hub_download / list_repo_files / CrossEncoder call. This lets users pin the exact remote weights they download and execute, mitigating a compromised/MITM'd HuggingFace repo serving a malicious ONNX model. Default behavior (unpinned) is unchanged.

Note: I did not hardcode default commit SHAs for the built-in models because I couldn't reach the HuggingFace API from my environment to verify them, and fabricating SHAs would break downloads. Pinning the shipped defaults to verified revisions is a good follow-up.

Testing

  • tests/test_config.py: new trust-boundary tests (project fields stripped + warning; global/CLI still apply).
  • tests/test_model_revision_pin.py: new repo@revision parsing tests.
  • Full suite: 235 passed, 7 skipped. ruff check + ruff format --check clean.

Closes parts of #594.

🤖 Generated with Claude Code

Addresses findings from the security review in zilliztech#594.

- config: project-local .memsearch.toml can no longer set endpoint/
  credential/prompt-path fields (embedding/llm/compact base_url+api_key+
  provider, milvus.uri/token, reranker.model, prompts.*). These travel with
  a cloned repo and could redirect the ambient API key, indexed content, or
  conversation summaries to an attacker endpoint, or read arbitrary files via
  a custom prompt path. They now come only from global config or CLI flags;
  stripped fields emit a UserWarning. (zilliztech#1/zilliztech#3/zilliztech#6)
- opencode capture-daemon: replace os.system() background re-index with a
  shell-free subprocess.Popen(argv) using the existing split_memsearch_cmd
  helper, removing command injection via project-path metacharacters. (zilliztech#2)
- cli: escape chunk_hash through _escape_filter_value in `expand` to close a
  Milvus filter-expression injection gap. (zilliztech#8)
- reranker/onnx embeddings: support a `repo@revision` suffix and thread
  revision= through all hf_hub_download/list_repo_files/CrossEncoder calls so
  remote model downloads can be pinned to a commit, mitigating supply-chain
  tampering. (zilliztech#7, CWE-367)

Tests: new trust-boundary tests in test_config.py and revision-pin tests in
test_model_revision_pin.py. Full suite: 235 passed, 7 skipped.

Co-Authored-By: Claude <noreply@anthropic.com>
@zc277584121

Copy link
Copy Markdown
Collaborator

Thanks for putting this together and for the detailed review in #594.

We have already merged the highest-priority project config trust-boundary fix in #595 and released it in v0.4.11, so this PR now overlaps with main and is not directly mergeable as-is.

The remaining changes here are still useful hardening, but they cover several separate surfaces. We'll keep #594 as the tracking issue and handle the remaining items as smaller focused PRs rather than merging this combined branch.

Thanks again for the thorough report and patch.

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.

3 participants