· about 30 minutes · runs on ubuntu-26.04-claude · unrated
An unrated lab. It runs on your machine with no account and no network, and everything about it — the faults, the checks, the hints and the reference solution — is in the repository. An attempt on it is recorded on your profile and never moves a rating: only rated labs do. Rated and unrated labs.
The on-call team asks questions about its notes through ~/bin/ask-notes: Claude Code, in
~/notes-bot, reads ~/notes through the team's files MCP server, /opt/mcp-files/files_mcp.py.
The server's settings are in /etc/mcp-files/config.json.
A security review of last week's answers found that the assistant had quoted a line from a private
key and the alert webhook's secret. Neither belongs in ~/notes — or so everyone thought.
This lab runs in a container, as learner with sudo. claude here is the real Claude Code
2.1.270, talking to a scripted model on this machine; it starts the MCP server and calls it for
real. The job's last answer is ~/answer.json.
What is expected, and graded — the grader runs ~/bin/ask-notes with a model that asks for files
of its own choosing:
~/notes, including new ones, can still be listed and read — and none changed.~/notes can be read through the server.~/notes that points outside it does not open what it points to.~/notes are neither listed nor read.Leave the server's code alone: it does what its settings tell it to.
This lab runs in a container — Docker or Podman, no VM — so there is no boot to survive: the checks run once, against the state you left.
| Check | Objective |
|---|---|
| 01_the_notes_are_read_only | Let an MCP server share exactly the folder a job needs, and only for reading |
| 02_nothing_outside_the_notes | Let an MCP server share exactly the folder a job needs, and only for reading |
| 03_links_do_not_lead_out | Keep links and hidden files from widening what a server exposes |
| 04_hidden_files_stay_hidden | Keep links and hidden files from widening what a server exposes |
Where the lab's hints send you, level by level, as you ask for them (h, then l opens a journal section in the TUI).
4 questions on the same topic, in the lab's Theory tab. They never affect the lab's grade. Three of them, to answer here:
Claude Code starts a stdio MCP file server from a project's .mcp.json. Which files can that server open?
Any file the user running Claude Code can open, unless the server limits itself
A stdio server is a child process of Claude Code and runs as the same user, so Unix permissions allow it whatever they allow that user. Claude Code's Read rules govern Claude Code's own tools, not what an MCP server does inside a tool call; the server has to enforce its own boundary, such as a list of roots.
https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices · https://code.claude.com/docs/en/mcp
A file server allows paths inside a root. Inside the root, keys is a symbolic link to a folder outside it. What does this print — the path check as written, then the check on where the path leads?
import os, tempfile
base = tempfile.mkdtemp()
root, secret = os.path.join(base, "notes"), os.path.join(base, "secret")
os.makedirs(root)
os.makedirs(secret)
open(os.path.join(secret, "key"), "w").close()
os.symlink(secret, os.path.join(root, "keys"))
path = os.path.join(root, "keys", "key")
written = os.path.abspath(path).startswith(root + "/")
real = os.path.realpath(path).startswith(os.path.realpath(root) + "/")
print(written, real)
True False
abspath only normalises the text of the path, so a link inside the root still looks inside it; realpath follows the link and shows the file lives under secret/. A server that means to keep paths inside a root has to check the resolved path against the resolved root.
man 3 realpath · man 7 symlink · executed in a sandbox
A read-only notes server has a write_file tool that checks a read_only flag and refuses. What is the better design, and why?
Not offering write_file at all when read-only, so it never appears in tools/list
A tool that is listed invites calls and plans built around it, and its refusal depends on one check staying right. A tool that is not offered cannot be called by any client or model; the client's permission rules can then narrow further, but the server's tool list is the first boundary.
https://modelcontextprotocol.io/specification/2026-07-28/server/tools