Skip to content

OAuth consent page hard-codes one connector's product name — every other connector's consent screen shows the wrong name #18

Description

@thorwhalen

_consent_page renders a hard-coded product name into the approve/deny form:

enlace_auth/auth/oauth_server.py:461 (name elided here — read the line):

<p><strong>{client}</strong> is requesting access to your «HARD-CODED PRODUCT NAME» data as
<strong>{email}</strong>.</p>

The authorization server is shared by every MCP connector on a platform — one router
serves every resource. So that single string is what a user reads while authorizing any
connector. It is correct for exactly one of them and wrong for all the others.

git log -L461,461:enlace_auth/auth/oauth_server.py dates it to 77b6881 ("OAuth 2.1
authorization server for MCP connectors", #12), when the platform had exactly one
connector. It was never parameterised. The deployment now runs three, and every new
connector inherits the first one's name.

This is live, not latent

  • The consent page renders whenever require_consent is true — the default in both
    enlace_auth/config.py:63 and enlace_auth/auth/oauth_server.py:145 — and the platform
    config that consumes it does not override it.
  • Path: GET /auth/oauth/authorize (oauth_server.py:319) → login redirect if no session →
    _resource_allowed (:342) → _consent_page (:351) → the string above.

It was reported from real use: a user authorizing one connector saw a different connector's
product name.

Why this is more than cosmetic

resource_allowlist exists so that a connector can be restricted to a specific set of users
(oauth_server.py:146; docstring :155-163). A connector gated that way has a deliberately
narrow audience — yet the shared consent page announces its product name to everyone who
authorizes any other connector on the same platform
, including users who are not on its
allowlist. Per-connector access control at the token layer, undone by a global string at the
consent layer.

Structural fix — key the display name on resource

The connector's identity is already in hand at render time: _Authorized.resource
(oauth_server.py:129, populated at :302). It is the same key resource_allowlist is
keyed by, and it is the right key, because a connector only accepts tokens whose aud is
that exact resource.

  1. Add resource_display_names: dict[str, str] to the OAuth server config
    (enlace_auth/config.py, alongside resource_allowlist at :64); thread it through
    make_oauth_server_router (oauth_server.py:146) and enlace_auth/plugin.py:302,
    normalising keys with the same .rstrip("/") the allowlist already uses
    (oauth_server.py:165-169).
  2. _consent_page renders display_names.get(resource).
  3. The fallback must be generic — never a product name. e.g. "… is requesting access to
    your data on this platform."
    Any default that names a specific connector reintroduces
    this bug for the next one. _denied_page (oauth_server.py:443) is already generic and
    is the right precedent.

enlace_auth/appmeta.py:55 already carries a per-app display_name overlay, but it is keyed
by app name, not by OAuth resource URL — useful as a source for the value, not as the key.

The upstream half (making the connector's declared title the single source of this string,
rather than a second hand-maintained copy in platform config) is i2mint/enlace_connector#3.

Test it so it cannot come back

tests/test_oauth_server.py already drives a full consent flow (_build / _register). Add:

  • a mapped resource → the rendered HTML contains its display name and does not contain
    any other configured connector's;
  • an unmapped resource → the generic fallback, and no product name at all.

A test that merely asserts the consent page renders, or that a form field is present, passes
with this bug in place — which is how it survived three connectors.

One note while you are rewriting that template

pages._page escapes only the page title; this module does not import html.escape, and
_consent_page interpolates several request-supplied values straight into HTML attributes.
Escape everything you interpolate while you are in there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions