What happens
CARLOS_INSTANCE_TOKEN is bound to (account, app), never to an instance, and it is delivered through the app's default config bundle — handleAPIInstanceToken says so itself (internal/console/apiinstances.go:1368):
> Delivery to the app is the config-env layer, like any other app secret — > carlos secrets set -app <app> CARLOS_INSTANCE_TOKEN=....
So every instance of a multi-tenant app renders the same token, and a kind='app' row resolves to Role: svcrole.Instance (internal/console/svcprincipal.go:124). The Instance role reaches create, GET, PATCH, DELETE and the four move doors (internal/console/authcli.go:612-628), and apiAccountAppBy scopes a service principal to its app, not to a host (internal/console/apicli.go:70).
For an app that runs one instance per customer — titogo does, which is what discussion #148 is about — that means code running in customer A's instance can DELETE /api/cli/apps/{account}/{app}/instances/{hostB}, repoint B's upstreams with PATCH, or start a residence move on B. The byte-identical 404 that protects a stranger does not apply, because every host of the app belongs to the caller's app.
This is already known, in part
The comment above the move doors (authcli.go:614-619) says it:
> requested by the tenant's ROUTER with the app-held instance token > (titogo's residence design — never by an instance about itself... > which the role system cannot yet express; the Instance role is the > same authority the create and delete doors already grant this > credential).
So the gap is recorded for the move doors. What is not recorded is that the same reasoning makes delete and PATCH reachable from any instance of the app, which for a per-customer app is cross-tenant.
What would fix it
Tokens bound to (account, app, instance): a new cli_tokens kind so a rolled-back console refuses them rather than widening them to app scope, an Instance field on Principal, and a host comparison in instanceGate rather than in apiAccountAppBy, whose {host} is an alias hostname on the domains doors and not an instance at all. The delivery path has to change too, since a token delivered through the app-wide bundle is app-wide whatever it says on it — the per-instance config layer proposed for #148 is what would carry it.
Context
Found while designing #147 and #148. The tenant-email design deliberately does not depend on this: a central hub does the onboarding there, so the email doors take a narrow role and customer instances never call them. This is filed as its own work, sequenced after #148.