My MCP Server's GitHub Token Can Write. The Code That Promises It Never Will Had No Test. A developer's MCP server for GitHub and DEV.to has a read-only guard in its `_gh()` helper that blocks non-GET requests, but the `--selftest` suite lacks a regression test for this guard, meaning a future edit could silently enable write access with a token scoped for full repo control. The developer reproduced the issue in isolation and plans to add a test. My MCP server developer-presence , the one that lets Claude check my GitHub profile and manage my DEV.to posts has exactly three GitHub tools: get github profile , list repos , get repo stats . All three read data. None of them create, update, or delete anything. The GITHUB TOKEN behind them isn't scoped that way. My own key facts.md says it plainly: Token scopes needed: repo , user — add delete repo if repo deletion via API is required, add workflow if you'll ever push a branch that pulls in upstream .github/workflows/ .yml changes repo is GitHub's full-control scope — it can push commits, edit files, change repo settings, everything short of an outright delete. I gave it that scope because other parts of this project the git-writing side, not the MCP server need it. The MCP server just inherits the same .env file and, with it, the same token. So every call this server's GitHub helper makes is running with write credentials it never intends to use. The only thing standing between "never intends to" and "actually can't" is one function. python def gh path, method="GET", data=None : No GitHub tool in this server writes anything — GITHUB TOKEN is scoped repo, user full write access, see key facts.md , so a stray method="POST"/"DELETE" here would be a real write, not a hypothetical one. Enforced, not just true by convention. See bugs.md 2026-07-30. if method = "GET": raise ValueError f" gh is read-only — got method={method r}" if data is not None: raise ValueError " gh is read-only — got a data payload on a GET call" token = os.environ.get "GITHUB TOKEN" if not token: raise RuntimeError "GITHUB TOKEN not set — add it to .env next to server.py" req = urllib.request.Request f"https://api.github.com{path}", method=method req.add header "Authorization", f"token {token}" ... Every one of the three GitHub tools routes through gh , and none of them ever pass method= or data= . That's the entire enforcement: a single if that turns "this file happens to only call GET" into "this file cannot call anything but GET." I added that guard back on 2026-07-30 specifically so a future tool — create repo , star repo , whatever gets bolted on next — can't silently start using write access this token has but this server was never supposed to touch. The comment even calls it out: "Enforced, not just true by convention." I wrote that line myself, seven days ago, and I believed it. server.py has a --selftest block that's grown steadily since late July — every bug fixed in this file gets a regression case in the same run, so it can't quietly come back. It currently covers: the attribution-stripping regex, list repos 's negative-limit handling, create article 's pagination walk, and both gh / dev 's missing-credential path. It does not cover the read-only guard. I went looking for it assuming it'd be there — it's the security-relevant one, the one whose whole job is standing between a scoped-for-write token and an actual write — and it wasn't. Nothing in this file ever calls gh path, method="POST" and asserts it blows up. That means the promise in the comment was never actually checked by anything except me reading the four lines below it. Delete the if method = "GET": check in a future edit — a merge conflict resolved wrong, a "just add a quick write tool" PR that forgets the guard exists — and --selftest would still print selftest ok . The regression would only surface the first time some caller passed a non-GET method and it actually went through to GitHub. I checked this wasn't hypothetical by reproducing it in isolation, without hitting the network: try: gh "/users/x", method="POST" print "no exception raised" this is what a missing guard looks like except ValueError as e: print "guard fired:", e With the guard in place: guard fired: gh is read-only — got method='POST' . Comment that one if out locally and rerun it, and you get no exception raised — the request would have gone out with Authorization: token