Lab Notes // MCP best practices
The sandbox was never a wall.
I thought "sandboxed" meant my files stayed home. It doesn't. Here's what an AI connector really sees, where it goes, how it already went wrong for other people, and twelve habits that keep more of your private stuff out of the pipe. Then the other half: the rules your repos, scripts and databases need before the AI starts writing to them.
By SCRILLANOIZE · written with Claude, which is the joke · 6 Nov 2026
Confession
I got this wrong first
I build my own AI connectors. I wrote a whole post telling you your data is already out there. And the entire time, I was leaning on one word: sandboxed.
I heard "sandbox" and pictured a wall. My files on one side, the company on the other. So I let the machine read whatever the job needed.
The night before I posted, I finally asked it straight: does what you touch get sent to the company? Yes. Every file it opens, nearly every result a tool hands back, rides out with the next message. It always did. Nobody hid it. It's in the docs. I just never read that page, which is the exact shrug I yell at everybody else for.
So this is the page I needed six months ago. The sandbox was never a wall. It's a leash. It limits what the AI can do. It was never about what the AI can see.
The basics
MCP is how the AI grows hands
A chatbot can only talk. The Model Context Protocol, MCP, is the plug that lets it reach things: your files, your inbox, your calendar, your database, a website. Each plug is called a server. The Claude apps call them connectors.
A server can offer three things: tools the AI can call, resources it can read as background, and prompts, which are ready-made messages. Every hand you add is useful. Every hand you add can also carry something out.
The view from inside
What it sees, and what it doesn't
It sees
- The menu. The name, description and input form of every tool, from every server you've connected.
- What it asks for. The tool it calls and the details it sends along.
- What comes back. Whatever the tool returns, handed over as part of the conversation.
It doesn't see
- The server's code.
- The drive or database sitting behind it.
- Any file no tool has returned. Yet.
The second list isn't a promise anyone made. It just follows from how the plug works: only what the server hands over crosses the line. Sounds comforting until you get to the word "yet". The moment a tool returns something, it's in the conversation, and the conversation is the part that travels.
The trip
Then it leaves the building
Your machine
Your fileswhatever a tool is allowed to open
The MCP serverruns with your access, outside the sandbox
The AI appcollects the results into the conversation
Their servers
The modelreads the whole conversation each time
Storage30 days or five years by your setting, longer if flagged
Claude Code is Anthropic's coding tool, and the one I use. Its docs say it plainly. On every message the app re-sends the full context: the instructions, every earlier message, every earlier tool result, then your new line. So a file the AI read at nine in the morning goes out again with every message after it, until the conversation is cleared or squeezed into a summary.
And the sandbox? Same docs: "The sandbox covers shell commands only." Shell commands are the typed commands the AI runs on your computer. Its own file tools and your local MCP servers run outside it. On plain Windows there isn't one at all.
A local server runs on your computer, and that feels private. By itself it isn't: its results still get handed to the model. A remote connector added in the Claude apps is reached from Anthropic's servers, not from your machine, so now a third party is in the loop too, under its own terms.
The sandbox keeps the AI from knocking things over. It doesn't stop it from reading everything on the table and sending a copy to head office.
The fine print: Anthropic, consumer plans, checked 4 Nov 2026
Training. On Free, Pro, Max and Ultra, Anthropic trains new models on your data when the model improvement setting is on. That includes Claude Code used from those accounts.
How long. With the setting on, the Claude Code docs give a five-year retention period. Off, 30 days. Feedback you submit and conversations flagged for policy violations are kept longer either way.
Flagged chats. The privacy policy says conversations flagged for safety review can be used for model improvement even if you opted out.
Connectors. In fairness: Anthropic says the data it may train on does not include raw content from connectors or MCP servers, unless that content is copied directly into the conversation. Training and keeping are two different questions. It was still sent, and it's still stored for the stated period.
Business plans. Under Team, Enterprise and API terms, no training on Claude Code prompts or code unless the customer opts in.
On your own disk. Claude Code keeps session transcripts, tool results included, as plain unencrypted text in the .claude folder in your user folder. Thirty days by default. Sessions from the desktop app are kept at any age unless you set a limit.
Third parties. Anthropic's retention promises don't cover what somebody else's MCP server does with the data it handles.
Deleting. A deleted chat leaves your history at once and Anthropic's back-end storage within 30 days, with the same exceptions as above. Anything already used in training stays used.
Every line in that box is Anthropic's, because that's the tool I use and those are the docs I finally read. I did not check Cursor, ChatGPT or anybody else. Don't assume theirs is kinder. Read theirs.
The record
It already went wrong for other people
Some of this hit real people. Some is researchers proving it would. Here's seventeen months of it.
- Apr 2025 Invariant Labs shows "tool poisoning": instructions hidden inside a tool's description, which the AI reads and you never see. In their demo the agent handed over the private key that logs the user into their servers.
- May 2025 Same lab, GitHub's own MCP server. In their demo, one booby-trapped bug report on a public project steered an agent into pulling data out of the owner's private projects and posting it in public. Not a bug in the server, so there was nothing to patch.
- Jun 2025 Asana finds a flaw in its hosted MCP server that exposed data from one organisation's workspace to users in others. It had been live for over a month. About 1,000 customers affected, a spokesperson told BleepingComputer.
- Jul 2025 General Analysis: an attacker opens a support ticket. Later a developer asks their AI to look at the tickets. The AI, holding a database key that skips every access rule, reads a table of secrets and writes them into the ticket. Dummy data. Real mechanism.
- Jul 2025 A flaw in mcp-remote, a helper for reaching remote servers: connect to a malicious server and it could run commands on your computer. Severity: 9.6 out of 10.
- Sep 2025 Postmark warns about a fake package called postmark-mcp. It behaved itself for 15 versions, then shipped a backdoor that secretly BCC'd emails to an outside server. Postmark: nothing to do with us, remove it now.
- Feb 2026 Socket reports a worm spreading through at least 19 free code packages that developers install. One piece of it quietly adds a rogue MCP server to every AI coding assistant it finds on the disk. The rogue tools tell the assistant to read your keys and not mention it to you.
- Apr 2026 Trend Micro counts 1,467 MCP servers sitting exposed on the open internet. Its count the July before was 492.
- Aug 2026 OX Security reports a server listed in the official MCP Registry whose linked project ran malware the moment the folder was opened in VS Code or Claude Code.
Look at what's missing from that list: somebody cracking encryption. Four of the nine are the same story: somebody's text got read by an AI that had hands. The rest is plain old bad software with a new plug on it.
The one rule
Count to three
Simon Willison calls it the lethal trifecta. Meta calls its version the Rule of Two. Same idea: an agent gets dangerous when one session holds all three of these. Tick what your setup has right now.
- of 3. Nothing ticked. Nothing for a stranger to steer. That's a plain chatbot. Better. Not bulletproof. Keep it at two or fewer for this session. That's all three. One poisoned web page, email or ticket can now talk your AI into walking your data out the door. Switch one off before you start.
Two warnings. One connector can be two or three of these by itself: the GitHub server was private repos, strangers' issues and a way to post, all in one plug. And nobody has a guaranteed fix for the trick underneath: text that gives your AI orders. It's called prompt injection. OWASP, a security nonprofit, ranks it number one on its AI risk list and says it's unclear whether a fool-proof prevention exists.
So the fix isn't a smarter filter. It's fewer hands in the same room.
The list
Twelve habits. Tick the ones you already have.
- of 12. Nobody starts at twelve. I didn't. One a week. Don't run code or build tools? Three of these are free ticks.
The exact switches
Where the settings actually are
These are for Claude Code and the Claude apps, as the docs read on 4 Nov 2026. Open what you need.
The deny list, and making tools ask first
The file is settings.json, inside the .claude folder in your user folder. No file? Make one. Already has a permissions block? Put these lines inside it. Don't paste a second one.
{
"permissions": {
"deny": [
"Read(~/Documents/Taxes/**)",
"Read(//c/Users/you/Private/**)",
"Read(//**/.env)",
"Read(//**/.env.*)"
],
"ask": ["mcp__github__*"]
}
}
Path traps. A rule that starts with ./ only covers the folder you launched from. For a fixed folder use ~/ or two slashes. One slash is not enough: in this file /Users/you means a folder inside .claude. On Windows, C:\Users\you is written //c/Users/you.
Order. Deny beats ask, and ask beats allow. Type /permissions in a session to see your rules, and /status to check the file loaded.
Test it. The paths above are examples. Swap in your own, then ask the AI to read one. It should refuse.
The ask line is an example. It makes every tool from a server named github ask first. Copy your real server names from /mcp, then swap every dot and space for an underscore: the app's Slack connector shows as "claude.ai Slack" and is written mcp__claude_ai_Slack__*. A name that matches nothing fails without a word.
The limit. A deny rule covers the AI's file tools and the Bash file commands it recognises, like cat and head. The docs promise nothing for PowerShell, or for a search run across the whole folder. It is not a lock on the file. A script, or a local MCP server pointed at that folder, can still read it.
Find them, switch them off, remove them
claude mcp list
claude mcp logout <name>
claude mcp remove <name>
Inside a session, /mcp shows what's connected and lets you switch a server off. That switch is per project, not everywhere.
To keep the connectors from your Claude app out of Claude Code in a terminal or editor, add "disableClaudeAiConnectors": true to the same settings file, at the top level, not inside permissions. Inside the desktop app that key does nothing. There you disconnect the connector itself.
In the Claude apps: Customize, then Connectors. Remove what you don't use, and in each chat switch on only what that chat needs.
The sandbox, honestly
On macOS, Linux and WSL2, /sandbox opens the panel that turns it on. It's off by default, and Linux and WSL2 need bubblewrap and socat installed first. Turn it on: it puts real operating-system limits on the shell commands the AI runs. Pick regular permissions if you still want to be asked. The other mode, auto-allow, runs sandboxed commands with no prompt.
Just know what you bought. A leash on commands. Not a filter on what the AI reads, not a cover for MCP servers, and not there at all on plain Windows.
Cleaning up
Training. The model improvement setting lives at claude.ai/settings/data-privacy-controls.
Transcripts on your disk. "cleanupPeriodDays": 7 in the settings file shortens how long Claude Code keeps them. The minimum is 1. Older sessions disappear from resume. Sessions from the desktop app ignore that number until you also set desktopSessionCleanupPeriodDays in the same file. Set both: they go only once they're older than both. Your typed prompts sit in a separate history file that the sweep skips.
Starting fresh. /clear stops old results from being re-sent. It is not an undo. What already went out stays under the normal retention, and the transcript stays on your disk until the sweep.
Asking them. A formal privacy or deletion request goes to privacy@anthropic.com.
Filters and placeholders
You can put a filter in front of the cloud: something that swaps names, numbers and addresses for tags before the text leaves, and swaps them back after. Anthropic describes that pattern for developers. It isn't a switch in the app.
Presidio is an open-source tool that does the detecting. Its own front page warns that it can't promise to catch everything. Seatbelt. Not a vault.
In Claude Code, a hook can replace a tool's result before the model sees it. It runs after the tool has already acted, and it takes a script to set up.
The other half
It doesn't just read. It writes.
Everything above is about what the AI sees. But I don't only let it look. It commits my code, runs my scripts and fills my databases. A connector carrying a file out is one way to leak. A git push is another, and a push doesn't expire after 30 days. It's forever.
Git keeps every version of every file you ever committed. Delete the file tomorrow and the old copy is still in the history, and the history is what GitHub holds. Private doesn't save you either. GitHub can read a private repo. So can every collaborator you ever add, every app you authorise, and anyone who gets your login. Private means fewer people. Not nobody.
So I wrote myself a setup playbook. This is that playbook with my own folders taken out. It starts with the four things that never go in a repo.
SecretsKeys, tokens, passwords, .env files, cookies. Never. If one ever got in, get a new one from the provider first. Deleting the line doesn't un-leak it.
PeopleNames, phone numbers, emails, addresses, chat exports with other people in them. Never raw. Swap them for tags first.
Media & dumpsVideo, audio, phone backups, zips, databases. Not in git. Keep them on disk, list them, back them up encrypted.
Machine junknode_modules, build folders, logs, Thumbs.db. Ignore it all. It regenerates.
Repo setup
Ten steps, in the order that hurts least
The order is the whole trick. Every one of these is cheap before the first git add and miserable after the first push.
- Once per machineInstall the tools.Git, GitHub's command line tool, and gitleaks, which hunts for secrets. On Windows, winget installs all three. Open a fresh terminal afterwards so it can find them.
- Once per machineTell git who you are. Leave your email out of it.GitHub gives you a private noreply address for commits. Use it. If you've been committing with your real email, it's already in every one of those commits. Switching fixes the new ones. The old ones keep it.
- Before you add anythingLook at what's actually in the folder.Sort it by size and by type. This is how you find the eight-hour video in a subfolder you forgot existed. GitHub warns about a file over 50 MB, refuses one over 100 MB, caps a push at 2 GB, and would like the whole repo under 1 GB.
- Before the first git addWrite the .gitignore.Secrets, media and junk go in it before git sees a single file. Then test it:
git check-ignore -vnames the rule that caught a file. Watch the greedy rules. A*token*line meant for API tokens will also hide your design-tokens file. - Before the first commitHook up the secret scan.gitleaks as a pre-commit hook checks what you're about to commit and refuses the commit if it finds a key. That includes commits an AI session makes for you. Run it with redaction on, so a found secret doesn't get printed on your screen.
- If other people are in itSwap names for tags.A salted hash turns each name into the same short tag in every file, and nobody can reverse it without your salt. Emails and phone numbers get placeholders. Filenames leak too, so rename those. Keep the dates if the dates are the point.
- After git add, before commitCheck the size of what you staged.The step everybody skips and then regrets at push time. If it's over 2 GB, something that isn't code got in.
- If you left originals outWrite down what you left out.A generated list of every ignored file with its size, date and a SHA-256 hash, committed with the repo. Future you can rebuild the folder and prove a backup copy is the same file. Read the list before you commit it. A filename with a real name in it is a real name in the repo.
- Same dayBack up the left-out files, encrypted.7-Zip with AES-256 and the file names encrypted too. Test the archive, hash it, keep two copies in two places, and never on the same drive as the originals. Passphrase in a password manager and on paper. Lose it and the files are gone. There's no reset link.
- Then, finallyLog in through the browser, then push.
gh auth loginkeeps the login in Windows' own credential store, so no token sits in a file. After the first push, open the repo on GitHub once and look. If something's there that shouldn't be, you can't unpush it, but now you know which key to replace.
Who you are, once per machine
In GitHub's email settings, turn on "Keep my email addresses private" and copy the noreply address it shows you. Then, in PowerShell or Command Prompt:
git config --global user.name "Your Name"
git config --global user.email "ID+USERNAME@users.noreply.github.com"
git config --global init.defaultBranch main
git config --global core.autocrlf true
git config --global core.longpaths true
git config --global push.autoSetupRemote true
autocrlf stops Windows line endings from making every file look changed. longpaths lifts Windows' old 260-character path limit for git. autoSetupRemote makes the first push of a new branch just work.
A starter .gitignore
Save it as .gitignore in the repo's top folder, before the first git add. Add your own tools' build folders.
# secrets: never
.env
.env.*
!.env.example
.dev.vars*
*.pem
*.key
*.pfx
id_rsa*
id_ed25519*
credentials*
*.cookies
# media and dumps: on disk
*.mp4
*.mov
*.mkv
*.wav
*.mp3
*.m4a
*.zip
*.7z
*.db
*.sqlite
*.bak
# junk: it all regenerates
node_modules/
dist/
build/
.wrangler/
__pycache__/
.venv/
Thumbs.db
desktop.ini
.DS_Store
*.log
*.tmp
The secret scan, and the hook that runs it
Scan a folder before it's a repo. PowerShell or Command Prompt, from inside the folder. The report lands one folder up, outside the repo:
gitleaks dir . --redact --verbose --report-path ..\leaks.json
Scan a repo's whole history, which is what GitHub holds:
gitleaks git . --redact --verbose
The hook. In PowerShell, from the repo's top folder, after git init:
$hook = "#!/bin/sh`nexec gitleaks git --pre-commit --staged --redact --verbose`n"
[IO.File]::WriteAllText("$PWD\.git\hooks\pre-commit", $hook)
Tested before posting, with gitleaks 8.30 and a fake Slack token: the commit was refused and nothing landed. Older guides use gitleaks detect and gitleaks protect. Those still run but are on their way out. Heads-up: the gitleaks README now says future releases will be security fixes only. It still works. Read the README before you install.
If it finds something, get a new key from the provider first. A key that showed up anywhere is burned, even after you delete the line.
The encrypted backup
7-Zip, in PowerShell. Point it at the folder of originals, and swap E: for your backup drive. Not the drive the originals are on.
$zip = "C:\Program Files\7-Zip\7z.exe"
& $zip a -t7z -mhe=on -p E:\Backups\originals.7z .\originals\
& $zip t -p E:\Backups\originals.7z
Get-FileHash E:\Backups\originals.7z -Algorithm SHA256
-mhe=on encrypts the file names too. Without it, anyone can read the list of what's inside. -p with nothing after it makes 7-Zip ask for the passphrase, so it never lands in your command history. t tests the archive. A backup you've never opened is a guess. The hash proves the second copy matches the first.
Skip old-style zip passwords, anything that says ZipCrypto. Free tools break those with as little as 12 bytes of known content.
After day one it's boring, which is the point. Small commits, push often. Branches become the rule the day a second person, or an automatic deploy, touches main. GitHub lists protecting main in a private repo as a paid Pro feature. On any plan, a local hook that refuses commits on main does most of that job.
Two notes for the AI part. Claude Code reads a project's settings file from the folder the session starts in. Start it one folder up and the repo's own deny list never loads, so keep the rules that matter in the settings file in your user folder too. And if you let the AI write your commit messages, make it a command you run on purpose, not a hook. A hook fires on every commit and spends quota every time. A command runs when you ask.
Scripts
Preview first. Receipts after.
A word about PowerShell. Every script I've ever run has exactly two speeds: does nothing, and does it to every file on the drive. There is no third speed.
A script on my machine once had a helper function named Ssh, and inside it, it called ssh. PowerShell doesn't care about capital letters, and a function beats a real program to the same name. So the function called itself. Which called itself. Which called itself, until PowerShell quit with "The script failed due to call depth overflow." It never connected to anything. The old Windows PowerShell takes minutes to reach that point. I didn't write that function. I approved it, which is worse.
Ctrl+C is the only part of PowerShell I've truly mastered.
So every script that changes files gets a preview, and the preview is the default. It lists what it would do and stops. The real run needs a word you have to type on purpose. And when it says "removed", it checks the thing is actually gone. A script will happily print "removed" over a file that's still sitting there. It isn't lying. It just believes in itself.
Then there's robocopy, the copy tool built into Windows, which has more switches than my studio. I spent hours learning it. /E for every subfolder. /XJ so it doesn't follow a folder link around in a circle. /R:1 /W:1, because out of the box it retries a locked file a million times, thirty seconds apart. That's almost a year. It would wait. /XF to skip the Google Drive shortcut files it can't read anyway. I built the perfect command. I ran it. Thousands of lines flew up the screen. It finished.
Did everything copy? No idea. I forgot /LOG. lol.
Hours learning flags. Zero receipts. Iconic.
/LOG:file writes the whole report to a file, and /TEE keeps the light show on screen too. That's the rule for this whole half of the page. The secret scan writes a report. The backup gets tested and hashed. The copy gets a log. If you can't open the receipt later, it didn't happen. You just watched it scroll.
The other mess
A database is clean for about a day
A new database is the cleanest it will ever be. Then the writing starts. Mine, my scripts', and now the AI's, which can write a thousand rows before I've finished reading the first one.
DoublesA script dies halfway, you run it again, and the first half lands twice. Or the AI's tool times out, the AI tries again, and both tries went through.
Test rows"asdf". "test test". A customer named Test McTest. Still there in March, sitting next to the real ones.
Two clocksOne script logs in local time, another logs the same thing in UTC. Now everything happened twice, hours apart, like an echo.
GhostsRows for the file you moved, the account you closed, the job that finished in August. Nobody owns them, so nobody deletes them.
None of it shows on day one. It shows the day you add something up, the total's wrong, and you can't tell which rows to trust. By then it isn't cleaning. It's archaeology.
I've done it. One of my own logs held nearly five thousand rows twice, each copy five or six hours late, because a script read UTC timestamps as local time. And the first day my site had a view counter, its most loyal visitor was the AI that built it. Every test run counted as a view.
So the rules go in before the first write. Same idea as the .gitignore before the first git add. Tick what yours already has.
- of 6. Zero. That's not a database yet. That's a pile with a search bar. A start. The doubles are already in there. You just haven't counted them. Halfway. Write down the rest before the next script writes something for you. That's a database. Now make the AI's write tool follow the same rules.
And the AI gets the smallest key here too. Read-only unless the job is writing. When it does write, it goes through a tool that follows these rules, not straight into the tables.
What I did
What I changed that night
I turned off every connector on my account that I wasn't using. It took two minutes. It should have been done on day one.
There's more on my list. I'll post what works and what doesn't once it's done, not before.
I'm not quitting the tool. I wrote this page with it. That's the joke, and it's also the point. You don't have to cut the hands off. You have to know what they're holding when they leave the room.
Using it wasn't the mistake. Not reading the page was. I read it so you'd only have to read this one.
Receipts
Sources
- Model Context Protocol docs, architecture overview: modelcontextprotocol.io · stdio transport: modelcontextprotocol.io
- Claude Code docs, opened 4 Nov 2026. Prompt caching: code.claude.com · Sandboxing: code.claude.com · Data usage: code.claude.com · Zero data retention: code.claude.com
- Claude Code docs, opened 4 Nov 2026. Permissions: code.claude.com · Settings: code.claude.com · MCP: code.claude.com · Hooks: code.claude.com
- Claude Help Center, custom connectors using remote MCP: support.claude.com
- Anthropic Privacy Policy, effective 10 Sep 2026: anthropic.com · Privacy Center, model training: privacy.claude.com · retention: privacy.claude.com · the setting: privacy.claude.com
- Invariant Labs, tool poisoning, 1 Apr 2025: invariantlabs.ai · GitHub MCP, 26 May 2025: invariantlabs.ai
- BleepingComputer on Asana, 18 Jun 2025: bleepingcomputer.com
- General Analysis, Supabase MCP, 8 Jul 2025: generalanalysis.com
- JFrog, CVE-2025-6514 in mcp-remote: jfrog.com · Oligo, CVE-2025-49596 in MCP Inspector: oligo.security
- Postmark, 25 Sep 2025: postmarkapp.com
- Socket, SANDWORM_MODE, 20 Feb 2026: socket.dev · Check Point Research, Claude Code project files, 25 Feb 2026: research.checkpoint.com
- Trend Micro, exposed MCP servers, 28 Apr 2026: trendaisecurity.com · Backslash Security, 25 Jun 2025: backslash.security
- OX Security, 9 Aug 2026: ox.security
- Simon Willison, "The lethal trifecta", 16 Jun 2025: simonwillison.net · Meta, Agents Rule of Two: ai.meta.com · OWASP LLM01:2025: genai.owasp.org
- MCP specification, security best practices: modelcontextprotocol.io · MCP Registry, about (still in preview): modelcontextprotocol.io · OWASP cheat sheet for third-party MCP servers, 23 Oct 2025: genai.owasp.org
- Anthropic, "Code execution with MCP": anthropic.com · Presidio, the "no guarantee" warning on its front page and in its code repository (the project moved from Microsoft to the Data Privacy Stack): presidio.dataprivacystack.org · github.com · AWS, agent access patterns with MCP, Apr 2026: aws.amazon.com
- GitHub Docs. Large files (50 MiB warning, 100 MiB block, repo size): docs.github.com · Repository limits (push size): docs.github.com · Commit email address: docs.github.com · Email addresses reference (the noreply format): docs.github.com · GitHub's plans (protected branches in private repos under Pro): docs.github.com
- gitleaks README, including the
gitanddircommands and the security-fixes-only notice: github.com. The hook and both scans were run on 6 Nov 2026 with gitleaks 8.30.1 against a throwaway repo. - 7-Zip, the 7z format and AES-256: 7-zip.org · the
-mheswitch, in 7-Zip's command-line help: 7-zip.opensource.jp · the passphrase prompt, how-to in Japanese: 7-zip.opensource.jp · bkcrack, the known-plaintext attack on ZipCrypto: github.com - Microsoft Learn. robocopy (retry and wait defaults, /log, /tee, /xj): learn.microsoft.com · PowerShell command precedence: learn.microsoft.com · PowerShell case-sensitivity: learn.microsoft.com. The call depth error was reproduced on 6 Nov 2026 in PowerShell 7 and Windows PowerShell 5.1.
The connector half was checked on 4 Nov 2026, the repo, script and database half on 6 Nov 2026. Docs change, and settings get renamed. If a line here has gone stale, the docs win.