Read-only token policy — what it costs, what it buys¶
One setting, Require a read-only token for read access
(muppy_git.require_read_only_token), decides three things. It is off by default, and
off is the behaviour of a platform that has not opted into anything.
What it decides¶
Three things, all in one direction — a credential that can write is never used where reading is enough.
| Off (default) | On | |
|---|---|---|
| Dev boxes | a read may be served by a read-write token, behind a prompt | refused |
| Deploys | run with whatever token is configured, with a warning | blocked — by the wizard before it starts, and on the webhook path by an explicit failure in the IMQ log |
| Credentials on the box | any token may be written at provisioning | only a read-only, repository-scoped one |
Why the third row is stricter than the second¶
A deploy only reads, so a read-only token — repository-scoped or wide — is enough for it. Embedding is held to a higher bar, and the reason is duration, not privilege:
The more durable the exposure, the narrower the token.
A credential Muppy writes for a box sits in .git/config, in clear, until somebody
removes it — in the remote URL on most providers, in an http.extraheader on Azure DevOps.
Same file, same duration, same readers. A read-only wide token is acceptable for the length of a deploy; deposited on
a disk it opens every repository of the account to whoever reads that file.
What Muppy refuses, and what it does not prevent¶
With the policy on, Muppy will not write a broad credential into a repository's
.git/config — not at provisioning, and not when you remove the git helper from a box. Remove git
helper refuses in that state rather than removing the helper and leaving a box that
can no longer do git.
It does not stop you. A developer with a shell can point a remote at their own token whenever they like, and no product setting can prevent that.
The rule governs what Muppy writes, in its own name, at the click of a button, with a token it holds. That is a promise it can keep indefinitely — which is why it is worth making.
"Read-only" is measured, not declared¶
Muppy reads a token's permissions from the provider. A token whose permissions cannot be read is treated as write-capable.
The policy therefore fails closed: a provider outage tightens access, it never loosens it. If introspection has never run on a token, it does not qualify.
GitLab fine-grained tokens
The permission that grants cloning is download_code ("Code: Download"). It does not
start with read_, which is a trap for anything that classifies by prefix — Muppy
matches it explicitly.
Before you turn it on¶
Check that every App Server and every non-dev server has a read-only token for its repository.
A repository-scoped one pays twice. Beyond satisfying this policy, it earns:
- free reads — served with no session and no approval at all;
- sliding windows on its read-write counterpart, if you keep
muppy_git.repo_scoped_rw_slidingon.
So the fluid path and the strict path are the same investment. There is no version of this where doing the work at your git provider makes life harder.
On Azure DevOps no token can satisfy this policy — check before you turn it on
A repository-scoped token does not exist on Azure DevOps: its finest grain is an organisation plus a scope. So every Azure token is account-wide, and with this policy on no Azure App Server can be provisioned, and none can have its git helper removed — whatever token you create.
The way out is the Muppy git helper, which writes no credential to the box at all. Switch your Azure App Servers to it before turning this on; a box already running is not touched, but the refusal meets it at its next provisioning.
List what is affected:
SELECT id, name FROM mpy_dev_server
WHERE repository_provider = 'azuredevops'
AND COALESCE(is_template,false) = false
AND COALESCE(use_muppy_git_helper,false) = false;
One thing a repository-scoped read-only token does not contain
At worst it grants a full read of that one repository — every branch, all history,
all tags. A box cloned with --depth=1 on one branch holds far less than the token
opens. On a monorepo, that depth is breadth.
If that is too much, turn off muppy_git.repo_scoped_ro_free_read as well: the token
then goes through a prompt like any other, and you keep the write-side guarantees.
What turning it on will break, and how it shows¶
- A dev box whose only credential is read-write can no longer read. Its owner creates a read-only token; nothing else changes.
- A deploy without a read-only token is refused. The wizard says so before starting; the webhook path fails with an explicit IMQ message and notifies the box owner — an auto-deploy dead for three days because a token is missing is not discovered by reading journals.
Remove git helperrefuses on a box whose token is not read-only and repository-scoped. It names the remedy — the one its provider actually offers, which on Azure DevOps is the helper rather than a narrower token.
Every refusal states what to do. None of them is silent.
Related¶
- Git Credential Settings — every knob, with its default.
- Git Tokens (Dev & Non-Dev) — how tokens are resolved.
- Managing GitLab Tokens — making a repository-scoped read-only token.