Skip to content

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_sliding on.

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 helper refuses 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.