Lorekeep compiles team documents into a temporal knowledge graph and serves it to AI coding agents over MCP. This document describes the threat model and the configuration decisions that keep a deployment safe.
- Compile + journal-based writes. The graph (
graph/facts.jsonl) is produced bylorekeep compileand never mutated directly by the server. Agents read via MCP and propose facts through journal-based write tools that append topending/— facts enter the graph only after a resolve pass (confidence-gated). No concurrency control is needed on the read path becausefacts.jsonlis replaced atomically. - Per-process namespace scope. An MCP server's visible data is fixed at startup
by
LOREKEEP_NS(comma-separated namespaces). Visibility is enforced by a single chokepoint,ScopedGraph(src/lorekeep/perm/ns.py), applied to every query. - Deny-by-default.
effective_ns = allowed ∪ {public}. A node is visible iffnode.ns ∩ effective_ns ≠ ∅; an edge is visible iff both endpoints are visible andedge.ns ∩ effective_ns ≠ ∅. - No information oracle.
get_nodereturns the same"not found or out of scope"whether a node is absent or merely outside scope.list_namespacesreturns only the caller's owneffective_ns— it does not enumerate namespace names that exist but are hidden.
At compile, every file under raw/ is sent to the configured LLM provider
for extraction. This is by design (the documents are the knowledge graph's
source), but it has two consequences:
- Treat
raw/as trusted content. Do not pointLOREKEEP_RAWat a directory that holds secrets. - Symlink guard.
compile/ingest.pyskips any file whose resolved target escapesraw_rootand warns on stderr. This prevents a planted symlink (raw/x/leak.md -> ~/.ssh/id_rsa) from exfiltrating files outsideraw/to the provider. Keep this guard; do not disable it.
For team/shared raw/ directories (a stated target), the trust boundary is
"anyone who can write to raw/". Isolate raw/ per team and compile per team.
- Prefer
api_key_env(the name of an environment variable) over an inlineapi_key. The provider resolves the env var first and only falls back to the inline value if the env var is unset. config.yamlis gitignored by default (.lorekeep/*except the.exampletemplate). Never commit a realconfig.yaml. If an inline key is used,lorekeep compileprints a warning recommendingapi_key_env.- Keys are passed only to
litellm.completionat compile; the server never reads or transmits a key.
- No
subprocess, shell,eval,exec,pickle, ormarshal. Config usesyaml.safe_load; all user-supplied data is parsed as JSON. SQLite FTS uses parameterized queries. - Filesystem writes are confined to the data home (
raw/,graph/,.lorekeep/) and, forlorekeep mcp add, the agent config file (.mcp.json/.cursor/mcp.json/config.toml), which is merged, not clobbered. facts.jsonlandmanifest.jsonare written atomically (temp file +os.replace), so a concurrent read during compile never sees a partial file.
Please open a private security advisory on github.com/manhhailua/lorekeep rather than a public issue. Include the affected version, a reproduction, and impact. Reports are acknowledged within 7 days. A fix and disclosure are coordinated with the reporter.
- An inline
api_keymay live in a gitignoredconfig.yaml. Owners are responsible for keeping that file local. - Shared
raw/directories trust everyone with write access (see egress above). - The MCP client (the coding agent) is trusted within its
LOREKEEP_NSscope: it can read everything in scope, which is the intended behavior. Scope assignment is an operational responsibility, not enforced by the server.