fix(memory): add qmd mcporter search tool override#57363
Conversation
Greptile SummaryThis PR introduces an optional Key changes:
Minor observations (all P2):
Confidence Score: 5/5Safe to merge — all remaining findings are style/test-coverage suggestions with no impact on runtime correctness The core logic is correct: explicitToolOverride cleanly separates the override path from the default path, v1-fallback and v2-probe are correctly skipped for custom tools, and flat query args are correctly used. Default behavior is fully preserved when searchTool is unset. The three open findings are all P2 and none affect present behavior. No files require special attention beyond the P2 suggestions on zod-schema.ts and the test files
|
| Filename | Overview |
|---|---|
| extensions/memory-core/src/memory/qmd-manager.ts | Adds explicitToolOverride flag throughout the mcporter search path. Logic for bypassing v1-fallback, the typed searches-array format, and the v2 version probe is correctly gated on the flag. Minor: tool param type widened to string. |
| packages/memory-host-sdk/src/host/backend-config.ts | Adds resolveSearchTool helper (trims whitespace, returns undefined for blank values) and threads the result into ResolvedQmdConfig. Clean and correct. |
| packages/memory-host-sdk/src/host/backend-config.test.ts | New test verifies that a whitespace-padded searchTool value is trimmed and preserved in resolved config while searchMode is unchanged. |
| extensions/memory-core/src/memory/qmd-manager.test.ts | New test validates single-collection flat-query dispatch for the hybrid_search override. Multi-collection override path is not covered. |
| src/config/zod-schema.ts | Adds searchTool as an optional string; accepts empty strings at the schema level even though resolveSearchTool normalises them away. |
| src/config/types.memory.ts | Minimal addition of searchTool?: string to MemoryQmdConfig. |
Comments Outside Diff (2)
-
extensions/memory-core/src/memory/qmd-manager.test.ts, line 1052-1091 (link)Multi-collection override path not covered by tests
The test uses a single
pathsentry, so it exercises only the single-collection branch (runQmdSearchViaMcporterdirectly). The multi-collection branch viarunMcporterAcrossCollections— which also has the newexplicitToolOverrideparam threaded through it — is not tested.While the code path looks correct from the diff alone, adding a second paths entry to this test (or a new dedicated test) would lock in the multi-collection flat-query behavior and protect against future regressions in
runMcporterAcrossCollections.Prompt To Fix With AI
This is a comment left during a code review. Path: extensions/memory-core/src/memory/qmd-manager.test.ts Line: 1052-1091 Comment: **Multi-collection override path not covered by tests** The test uses a single `paths` entry, so it exercises only the single-collection branch (`runQmdSearchViaMcporter` directly). The multi-collection branch via `runMcporterAcrossCollections` — which also has the new `explicitToolOverride` param threaded through it — is not tested. While the code path looks correct from the diff alone, adding a second paths entry to this test (or a new dedicated test) would lock in the multi-collection flat-query behavior and protect against future regressions in `runMcporterAcrossCollections`. How can I resolve this? If you propose a fix, please make it concise.
-
src/config/zod-schema.ts, line 1368 (link)searchToolaccepts empty strings at the Zod validation layersearchTool: z.string().optional(),
An empty-string value passes Zod validation and is only normalised to
undefinedlater inresolveSearchTool. This is harmless at runtime, but the schema will silently acceptmemory.qmd.searchTool: ""without surfacing a validation error to the user. Consider aligning schema and resolution:Prompt To Fix With AI
This is a comment left during a code review. Path: src/config/zod-schema.ts Line: 1368 Comment: **`searchTool` accepts empty strings at the Zod validation layer** ```ts searchTool: z.string().optional(), ``` An empty-string value passes Zod validation and is only normalised to `undefined` later in `resolveSearchTool`. This is harmless at runtime, but the schema will silently accept `memory.qmd.searchTool: ""` without surfacing a validation error to the user. Consider aligning schema and resolution: How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/memory/qmd-manager.ts
Line: 1503
Comment:
**Tool type widened to `string` without guard for built-in names**
The `tool` parameter type was changed from the union `"query" | "search" | "vector_search" | "deep_search"` to plain `string`. TypeScript no longer enforces the built-in tool constraint for the non-override path, so a caller passing `explicitToolOverride: false` with an arbitrary string would silently bypass `resolveQmdMcpTool` without the compiler catching it.
This is only a concern at the non-override call-site, since both callers always derive `tool` from `resolveQmdMcpTool` when the override is absent. Still worth noting for future maintenance: consider a discriminated union like `{ tool: string; explicitToolOverride: true } | { tool: "query" | "search" | "vector_search" | "deep_search"; explicitToolOverride: false }` to preserve compile-time safety on the default path.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: extensions/memory-core/src/memory/qmd-manager.test.ts
Line: 1052-1091
Comment:
**Multi-collection override path not covered by tests**
The test uses a single `paths` entry, so it exercises only the single-collection branch (`runQmdSearchViaMcporter` directly). The multi-collection branch via `runMcporterAcrossCollections` — which also has the new `explicitToolOverride` param threaded through it — is not tested.
While the code path looks correct from the diff alone, adding a second paths entry to this test (or a new dedicated test) would lock in the multi-collection flat-query behavior and protect against future regressions in `runMcporterAcrossCollections`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/config/zod-schema.ts
Line: 1368
Comment:
**`searchTool` accepts empty strings at the Zod validation layer**
```ts
searchTool: z.string().optional(),
```
An empty-string value passes Zod validation and is only normalised to `undefined` later in `resolveSearchTool`. This is harmless at runtime, but the schema will silently accept `memory.qmd.searchTool: ""` without surfacing a validation error to the user. Consider aligning schema and resolution:
```suggestion
searchTool: z.string().min(1).optional(),
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Merge branch 'main' into fix/qmd-search-..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c734186a0
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e3c76b64b
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e85162b77
ℹ️ 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".
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
Vulnerabilities1. 🟠 Arbitrary MCP tool invocation via configurable
|
| Property | Value |
|---|---|
| Severity | High |
| CWE | CWE-284 |
| Location | extensions/memory-core/src/memory/qmd-manager.ts:1577-1619 |
Description
memory.qmd.searchTool allows a configuration-provided string to be used as the MCP tool name for mcporter calls, without an allowlist or capability check.
-
Input:
memory.qmd.searchTool(resolved byresolveSearchToolviatrim()only) -
Sink: mcporter CLI invocation
mcporter call <serverName>.<tool> -
Issue: Any tool exposed by the configured MCP server can be invoked during a normal “search” flow when
searchToolis set. If an attacker can influence configuration (e.g., untrusted workspace settings, tenant-controlled config, env var injection), they can trigger unexpected tools with side effects (filesystem access, network exfiltration, admin actions) under the host’s privileges.
Vulnerable code:
const selector = `${params.mcporter.serverName}.${effectiveTool}`;
...
result = await this.runMcporter([
"call",
selector,
"--args",
JSON.stringify(callArgs),
...
]);This is a form of unsafe capability exposure: the search API is expected to perform retrieval, but can be redirected to arbitrary server tools.
Recommendation
Constrain memory.qmd.searchTool to a safe allowlist of read-only search tools, and reject/ignore any other value.
Options:
- Prefer an enum in schema/config (most robust):
// e.g. only allow built-ins plus a vetted custom list
const AllowedSearchTools = ["query", "search", "vector_search", "deep_search", "hybrid_search"] as const;
searchTool: z.enum(AllowedSearchTools).optional();- Runtime allowlist check (if tools differ per server): fetch advertised tools from the MCP server and allow only those tagged/known as safe (read-only), otherwise fall back to the built-in mapping.
Also consider making searchTool admin-only (not user/workspace controllable) if this runs in a multi-tenant/SaaS context.
2. 🔵 Log/telemetry injection via unvalidated mcporter selector component (memory.qmd.searchTool)
| Property | Value |
|---|---|
| Severity | Low |
| CWE | CWE-117 |
| Location | extensions/memory-core/src/memory/qmd-manager.ts:1582-1560 |
Description
memory.qmd.searchTool is only trim()ed and then used to build the mcporter selector string. That selector is embedded into commandSummary (used in thrown error messages) without any neutralization.
-
Input:
searchToolfrom config (resolveSearchTool) can contain control characters (e.g.,\n,\r, tabs) or other log-breaking bytes. -
Propagation:
searchToolbecomes part ofselector =${serverName}.${effectiveTool}``. -
Sink:
selectorbecomes part ofcommandSummaryviaspawnInvocation.argv.join(" ")and is included in error messages thrown byrunCliCommand.
This enables log forging / log injection if a malicious repository/workspace can influence the config value, potentially:
- creating fake log lines (newline injection),
- obscuring real errors,
- poisoning downstream telemetry/metrics that parse command summaries.
Vulnerable code:
const selector = `${params.mcporter.serverName}.${effectiveTool}`;
...
commandSummary: `${spawnInvocation.command} ${spawnInvocation.argv.join(" ")}`,Recommendation
Validate and/or neutralize searchTool (and other selector components) before incorporating them into command summaries/logs.
Recommended defense-in-depth:
- Constrain allowed tool names (best): allow only a safe character set and a reasonable length.
- Escape control characters when building strings used for logs/errors.
Example (validation + escaping):
function validateToolName(tool: string): string {
const trimmed = tool.trim();
if (!/^[a-zA-Z0-9_\-]{1,64}$/.test(trimmed)) {
throw new Error("Invalid searchTool; expected 1-64 chars of [a-zA-Z0-9_-]");
}
return trimmed;
}
function escapeForLog(s: string): string {
return s.replace(/[\r\n\t]/g, (ch) => ({"\r":"\\r","\n":"\\n","\t":"\\t"}[ch]!));
}
const selector = `${serverName}.${validateToolName(tool)}`;
const commandSummary = `${cmd} ${argv.map(escapeForLog).join(" ")}`;Analyzed PR: #57363 at commit c4989e2
Last updated on: 2026-03-30T09:57:42Z
* fix(memory): add qmd mcporter search tool override * fix(memory): tighten qmd search tool override guards * chore(config): drop generated docs baselines from qmd pr * fix(memory): keep explicit qmd query override on v2 args * docs(changelog): normalize qmd search tool attribution * fix(memory): reuse v1 qmd tool after query fallback
* fix(memory): add qmd mcporter search tool override * fix(memory): tighten qmd search tool override guards * chore(config): drop generated docs baselines from qmd pr * fix(memory): keep explicit qmd query override on v2 args * docs(changelog): normalize qmd search tool attribution * fix(memory): reuse v1 qmd tool after query fallback
Summary
memory.qmd.searchModeis intentionally validated to a small semantic enum, but mcporter-backed QMD deployments can expose custom MCP tool names such ashybrid_search.memory.qmd.searchToolas an exact mcporter tool override, keepsearchModesemantic, and use the override only for mcporter calls.searchModeenum, and the normal built-in mcporter tool mapping when no override is set.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause / Regression History (if applicable)
searchModewas doing two jobs at once: semantic retrieval mode selection and mcporter tool-name binding.git blame, prior PR, issue, or refactor if known): fix: QMD 1.1+ mcporter compatibility with legacy fallback [AI-assisted] #54728 fixed the built-in QMD MCP tool migration path, but custom tool names remained impossible without patching core.Regression Test Plan (if applicable)
packages/memory-host-sdk/src/host/backend-config.test.ts,extensions/memory-core/src/memory/qmd-manager.test.tssearchTool, and mcporter searches call the explicit tool with flat query args instead of forcing the built-in tool mapping.searchToolis unset.User-visible / Behavior Changes
memory.qmd.searchToolmemory.qmd.mcporter.enabled=true, users can target an exact mcporter tool name such ashybrid_searchsearchModemappingDiagram (if applicable)
Security Impact (required)
Yes, explain risk + mitigation:The command surface change is config-gated and mcporter-only. The override is an explicit exact tool name, so users opt into a narrower, auditable behavior instead of broadening
searchModevalidation to arbitrary strings.Repro + Verification
Environment
memory.backend=qmd,memory.qmd.mcporter.enabled=true,memory.qmd.searchTool=hybrid_searchSteps
memory.backend=qmd,memory.qmd.mcporter.enabled=true, and a custommemory.qmd.searchTool.Expected
searchToolis unset.Actual
Evidence
Human Verification (required)
pnpm test -- packages/memory-host-sdk/src/host/backend-config.test.tspnpm test -- extensions/memory-core/src/memory/qmd-manager.test.tspnpm buildpnpm config:docs:checksearchToolconfig valuesReview Conversations
Compatibility / Migration
Risks and Mitigations