Security
scree reads files on your disk, sends some of their text to a remote API with your key, and hands text to a language model. This page says what it defends against, how, and what it leaves to you.
Threat model
| Threat | Example |
|---|---|
| A cloned repo you don’t control | Its committed .mcp.json, .gitignore, .git contents, symlinks and markdown are all chosen by someone else. |
| A shared machine | Other local users can create files in /tmp, own a .git above your folder, or try to read your settings and index. |
| Prompt injection | A markdown file contains text written to look like instructions to the model, or like a forged search result. |
| Data leaving the machine | Private files sent to the embedding API, or kept by a provider. |
Configuration comes only from your settings file
- The settings file is the only config source for the wrappers.
scree-mcp,scree-runandscree-mcp-dockerload~/.config/scree/envand nothing else. They refuse to start if it is missing, not owned by you, or has any permission bits for group or others. - Inherited environment and arguments are ignored. A repo’s committed
.mcp.jsoncan launchscree-mcpwith its own environment and arguments. So before reading the file, the wrappers resetPATH, unset every inheritedSCREE_*,OPENROUTER_API_KEYand proxy variable, and refuse all arguments (scree-mcp,scree-mcp-docker). A repo can’t redirect your key to another endpoint through a proxy orSCREE_EMBED_URL, swap in another binary, or point scree at another folder. - The file is data. It is parsed line by line as
NAME=value, never sourced, so nothing in it is expanded or run, and only scree’s own names and the proxy variables are accepted. - The binary is fixed.
scree-mcpruns thescreein its own folder, never one found onPATH.scree-mcp-dockerruns onlydockerorpodman. - The folder follows from the repo. Under Claude Code,
serverefuses-notes,-dbandSCREE_DB, and indexes the git root aboveCLAUDE_PROJECT_DIR. - Opt-in per repo. Only a repo whose
.mcp.jsonnamesscree-mcpis indexed, and Claude Code asks you to approve a project server once. New (step 6-1): the first-sync cap (SCREE_FIRST_SYNC_MB, default 50) bounds what an approved repo can cost before you look at it.
Git files from a repo are untrusted
- Trust only your own
.git. A work tree is used only if its.gitis owned by you and is not a symlink; a linked worktree’s git dir and common dir must be folders you own. A.gitowned by someone else is an error, not a silent skip, so a folder is never indexed without the ignore rules its owner expects. This is the same class of issue as git’s CVE-2022-24765. - Stop at sticky folders. scree never looks for a
.gitin or above a sticky folder such as/tmp, where anyone could plant one. - No symlinked git files.
.gitignore,.git/info/exclude, a.gitfile and.screeignoreare never followed through a symlink and must be regular files. Git ignore files are read up to 256 KB. - No repo
.git/config. The global excludes file is taken only from your own git config. A repo’s config could otherwise name any file on your disk. - Nothing echoed. Messages about these files name the file and line number, never their content, so a crafted ignore file can’t use scree to print another file.
- Fail closed. An unreadable
.gitignoreexcludes its folder; an unreadable or symlinked.screeignorestops indexing until fixed.
One policy for every read
Indexing, search, read_doc and reindex go through the same policy, checked against the name as stored on disk:
- symlinks are followed only inside the folder, with the rules checked on the target too; symlinked folders are never descended;
- non-regular files are skipped without being opened, and files are opened without blocking, so a FIFO can’t hang a session;
- a file is used only if what was opened is the regular file the policy checked, so swapping in a symlink mid-sync doesn’t work;
- files over
SCREE_MAX_FILE_BYTES(1 MiB) and names with control characters are refused.
The home guard
A folder found automatically (from CLAUDE_PROJECT_DIR or the current directory, lifted to its git root) is refused if it is your home directory, a folder above it, or a filesystem root. A dotfiles repo at ~ would otherwise make every non-repo folder under it lift to ~ and upload your whole home directory. The Docker wrapper applies the same rule to what it mounts. Only an explicit -notes on the command line can index such a folder.
Model-facing output: data, not instructions
- Each search result is a
<hit>element andread_docreturns a<doc>element. Any<hit,</hit,<docor</docinside file text, in any letter case, has its<replaced by<, so a file can’t close its element and forge a result. - Attribute values are quoted, and control characters are stripped from text, attributes and error messages. Tool errors are collapsed to one line.
- The server’s instructions tell Claude Code that text inside
<hit>and<doc>is file content, possibly copied from elsewhere, and is data, never instructions. - Hits are capped at 4000 bytes each and 64 KB per response;
read_docat 256 KB. - Log lines, which can contain file names and API messages, are made terminal-safe.
Zero data retention
By default every embedding request asks OpenRouter for endpoints with zero data retention and data_collection: deny. A model with no such endpoint fails with an error naming the model, rather than silently using one that keeps data. SCREE_ZDR=0 turns this off, for other endpoints. Search queries are embedded the same way.
HTTP mode
Stdio through the wrapper is the recommended setup. scree serve -http addr:
- listens on loopback only (
127.0.0.1,::1,localhost); an empty host,0.0.0.0and::count as remote; - needs
-allow-remoteandSCREE_HTTP_TOKENof at least 16 characters for any other address; - with a token set, requires
Authorization: Bearer <token>on every request, on loopback too, compared in constant time; - rejects cross-origin browser requests before the token check;
- has a 10 s header read timeout, 120 s idle timeout and 64 KB header limit.
Keep the token in the settings file, not on a command line, for example printf 'SCREE_HTTP_TOKEN=%s\n' "$(openssl rand -hex 32)" >> ~/.config/scree/env. A Claude Code HTTP registration stores the header, token included, in ~/.claude.json.
Index files
The index directory is created with mode 0700 and its files 0600. The index stores chunk text in plain form so search can return it.
Docker and SELinux
The Docker wrapper never pulls an image (--pull=never), since the container receives your key; mounts the repo read-only; refuses your home directory, a folder above it, / and paths containing :; accepts only a volume name for the data volume; and passes variables by name so the key never appears on a command line. The image runs as an unprivileged user.
On SELinux hosts, the container can read the bind-mounted folder only with :z, which permanently relabels every file in that folder on the host to container_file_t, even for a read-only mount. That is a trade-off you choose with SCREE_MOUNT_OPTS=ro,z: mount a repo or a notes folder, never your home directory, or use the native binary, which relabels nothing. See Docker.
What scree does not protect against
- The provider seeing your text. Chunks and queries are sent to the embedding API over HTTPS. Zero data retention is a routing request honoured by OpenRouter and its providers, not something scree can verify.
- Files you didn’t exclude. Anything the rules allow is embedded. Run
doctor -vbefore opting a repo in, and use.screeignoreorscree: ignore. - Prompt injection, fully. Neutralizing tags and labelling text as data reduce the risk; a model can still be swayed by text it reads.
- Your own user account. Any process running as you can read the settings file, the key and the index.
- A repo you approved. Once you approve its
.mcp.json, its markdown is embedded with your key, within the first-sync cap. - Plain HTTP off loopback.
-allow-remoteserves HTTP without TLS; put it behind TLS or a VPN. - The settings file’s location. The wrappers choose the file from
SCREE_ENV_FILEif it is set in their environment, before clearing other variables. The chosen file still has to be yours, mode 600, and hold only scree settings. - Platform gaps. Windows has no ownership checks. File locking may not hold on network filesystems. Under Docker, your global git excludes file isn’t visible, so the container may index files native scree ignores.
Reporting a problem
Open an issue at github.com/jrapakko/scree/issues. There is no security email. For a vulnerability, describe the class of problem and the affected component; leave out a working exploit, and the maintainer will follow up.