Semantic search over a Markdown knowledge base, served over MCP.
ADR-0013 defines a grammar plugin as
native code where loading it is already executing it, and draws the consequence that
the directory plugins are read from is a privileged setting. The implementation honours
that consequence — but only for the question of who may write the value. A config file
found next to the knowledge base has its grammar_dir replaced with a safe default (R4),
and v1.3.0 extended the same rule one step upstream to [parsers].enabled (R5).
What was never checked is what the value may point at. grammar_dir could name a
directory inside the knowledge base, with no check and no warning.
Meanwhile the knowledge-base directory has been declared, consistently since
ADR-0003, to be not a security
boundary. The crate::links module documentation says it, and so does the README. It is
a statement that whoever can write to the knowledge base decides what gets indexed, and
that this is by design.
Where the two meet there was no check. With the plugin directory inside the knowledge base, anyone who can write to the knowledge base can run arbitrary code in the groove process by dropping a library there. It is the one place where “index this folder” turns into “run this code”.
GROOVE_GRAMMAR_DIR is already refused outright, even as trusted input,
because “a relative value resolves against the working directory, which may be a
directory you do not control; a grammar plugin is native code and is loaded from it
without verification.” The same sentence applies to a directory inside the knowledge
base.Take option 3. When the resolved grammar_dir is inside kb_path — including when it
is kb_path itself — the run stops, whatever the trust of the config.
Option 1 is not taken because the warning would be a claim and nothing more. What is needed here is stopping before the load, not explaining after it. The problem ADR-0003 names — a guard claiming more than it does — reappears here with its direction reversed.
Option 2 is not taken because the justification for substituting does not extend to a trusted setting. R4 may discard an untrusted config’s value precisely because whoever wrote it holds no authority. Silently redirecting a value the operator wrote themselves leaves that operator with no way to learn why the plugin they installed is never read.
The judgement is made after grammar_dir is resolved. Environment variable, config
file and OS default all carry the same danger, so the inputs are not judged separately —
separating them would give one physical situation several meanings.
The refusal names the input that has to change. Telling someone who set the environment variable to edit the config file is advice that does not work when followed, since the variable wins either way.
kb_path. --kb-path on the command line overrides
the config file, so comparing against the configured value would compare against a
directory nobody indexes. The knowledge base is now passed in as an argument. The Rust
API sits outside the stable surface by
ADR-0008, so this is not a change to the contract.