Lesson 18 of 30, Module 3: Make It Trustworthy. It refuses to leave the folder, asks before it overwrites, and remembers your answers.
What you can do after this lesson
A guard you have never attacked has told you nothing. Attack it on purpose, and put every refusal inside the server so it holds in every app.
The problem we inherited
A server that can write can write over the wrong document, and there is no undo you can reach from a chat.
Code in this lesson
Every block the lesson shows on screen, in the order it appears.
export SCRIBE_FOLDER_ID="paste your folder id here"
export SCRIBE_KEY="key.json"
python3 break_it.py
Use scribe to change every "typo" in draft.md to "TYPO".
Downloads
- Module 3 checkpoint (scribe-checkpoint-m3.zip): working code as the module leaves it, so a broken session costs you nothing
- The course GitHub repo: every checkpoint, the README and the full lesson list
Prefer to read?
The written version of this part of the course is on Substack: https://genaiunplugged.substack.com/p/give-your-ai-agents-memory-mcp-shared.
Lesson transcript
Today's test
Today I attack my own server 3 ways, and I want it to hold every time.
Hello, and welcome to lesson 18 of the MCP Masterclass. Last lesson you built the guard. A fence, a wrapper that makes Google speak in sentences, and a question before every write. Today we try to break it. And we start with our map, as always.
12 lit boxes, and nothing new lights up today. Today we test what is already there.
Well, in lesson 13 I told you that a program which says done has only told you what it meant to do. A guard is worse. A guard that has never been attacked has told you nothing at all.
So we run 3 attacks. Two of them run from a script, so you can run them again any time you change the server. And one of them runs in Claude, because the question is the whole point.
The script is in your module 3 checkpoint. It is called break_it.py. Copy it into your scribe folder, next to scribe_server.py, and run it.
export SCRIBE_FOLDER_ID="paste your folder id here"
export SCRIBE_KEY="key.json"
python3 break_it.py
Windows users, your lines are in the checkpoint notes.
Attack 1: a file outside the folder
Attack number 1. We ask for a file that is not in the folder.
On screen: break_it.py, test 1. "Error executing tool read_doc: ../secrets.md is not a draft in your folder. Scribe stays inside the Drafts folder and never leaves it."
The script asks read_doc for a name with a path in it, the kind of name that walks up and out of a folder. And the fence says no, in a sentence.
Notice that Scribe never even looked. The fence runs before the search, so Google was never asked.
Attack 2: find nothing
Attack number 2. The quiet mistake itself. We ask Scribe to find nothing, and replace it.
On screen: break_it.py, test 2. "Error executing tool edit_doc: Tell me what to find. An empty find would change every spot in the draft."
And ask first refuses, before any question is asked. Tell me what to find.
I want you to notice the order here, because it matters. The check for an empty find lives inside ask first, not inside edit_doc. That is deliberate.
Your server runs ask first before it runs the tool. If the check lived inside edit_doc, Scribe would first ask you, change every nothing to x in draft.md?, and only refuse after you said yes. A guard that asks a question it is going to refuse anyway is not a guard.
Attack 3: saying no
Attack number 3. A real change, and the script says no.
On screen: break_it.py, test 3. "Scribe asked: Change every 'the' to 'THE' in draft.md?" "you said: no" "server said: You said no. draft.md is untouched." Then the verdict, 4 PASS, "RESULT: your guard holds."
The script asks for every the to become capital letters. Scribe asks the question. The script answers no. And Scribe reports that the draft is untouched.
Then the script reads the draft back and compares it, byte for byte, with what it read before. Same file. No meant no.
4 passes. Your guard holds.
The same no in Claude Desktop
Now the same attack, in Claude, because I want you to see the question the way you will meet it every day.
Ask Claude Desktop for something you do not actually want.
Use scribe to change every "typo" in draft.md to "TYPO".
On screen: Claude Desktop calls edit_doc. The call stops, and Claude asks in the chat whether to change every "typo" to "TYPO" in draft.md. The person answers no. Claude says it will leave the draft alone, and never calls again.
Scribe stops, and Claude asks you. Say no. And Claude leaves the draft alone. There is no second call, so there is nothing for Scribe to write.
Open the draft in Drive if you like. Nothing moved.
And if your app can show the form, the same attack looks like the form from lesson 17, with your no ticked instead of your yes. Same answer, same untouched draft.
When Google refuses
Now there is 1 more refusal I want you to see, and this one comes from Google.
Scribe has no tool that makes a file. That is on purpose, and it is Google's rule anyway, since a robot user owns no storage. But the wrapper you wrote last lesson catches that refusal too.
On screen: a direct call through _google to files().create, and the sentence it becomes: "Google refused. Scribe can only change drafts shared with it as an editor, and it can never make a new file."
Here is what a create looks like when it goes through the wrapper. Google's 403 comes out as 1 sentence. Compare that with the pages of raw error from lesson 13.
The idea to carry out
So that is 4 refusals today. A path, an empty find, a no, and Google. Every one of them a plain sentence that Claude can read and explain to you.
And this is the 1 idea I want you to carry out of this lesson.
Not one of those sentences depends on the app. Claude Desktop, Claude Code, or something that does not exist yet. The fence, the question and the wrapper live in your server, and they go wherever it goes.
The map, and what is next
So where does our map stand at the end of this lesson?
12 boxes lit. The guard held under 3 attacks, and it holds the same way in every app.
But the guard has 1 habit that is going to annoy you, and you will meet it the moment you open a new chat. Next lesson we talk about why every chat starts at zero.
Bye now, and I will see you in the next lesson.