Skip to content

Published GCP project missing .readonly scopes requested by default read feature groups #323

@allenhutchison

Description

@allenhutchison

Problem

The default feature configuration in workspace-server/src/features/feature-config.ts requests six .readonly OAuth scopes that are not listed in scripts/setup-gcp.sh (and therefore not configured on the published GCP project's OAuth consent screen). Any user authenticating against the published project with default settings hits Google's "This app is blocked — This app tried to access sensitive info in your Google Account" screen, because requesting an unregistered sensitive/restricted scope on an unverified app is a hard block with no click-through.

Missing scopes

Feature group Scope missing from published project / setup-gcp.sh
drive.read https://www.googleapis.com/auth/drive.readonly
calendar.read https://www.googleapis.com/auth/calendar.readonly
chat.read https://www.googleapis.com/auth/chat.spaces.readonly
chat.read https://www.googleapis.com/auth/chat.messages.readonly
chat.read https://www.googleapis.com/auth/chat.memberships.readonly
gmail.read https://www.googleapis.com/auth/gmail.readonly

The corresponding write-group scopes (drive, calendar, chat.spaces, chat.messages, chat.memberships, gmail.modify) are in the published project and grant read access at the API level, so this is purely a consent-screen registration gap introduced when read/write feature groups were split out.

Root cause: two sources of truth

The scope list in scripts/setup-gcp.sh (lines 94–106) is a hardcoded array that must be manually kept in sync with workspace-server/src/features/feature-config.ts. When the read/write feature split added the six .readonly scopes to feature-config.ts, neither setup-gcp.sh nor the published GCP consent screen was updated, and nothing caught the drift.

Any fix for this issue should also close the drift gap, so this can't silently recur the next time scopes change. Options:

  1. Generate setup-gcp.sh's scope list from feature-config.ts at build/run time (e.g. a small Node script the shell script shells out to, or a generated file checked in with a "do not edit" header).
  2. Add a CI check that parses setup-gcp.sh's SCOPES=(...) block and diffs it against resolveFeatures().requiredScopes, failing the build on mismatch.
  3. Make setup-gcp.sh query the server at runtime — e.g. node -e "console.log(require('./workspace-server/dist/auth/scopes').SCOPES.join('\n'))" — so it always prints whatever the code actually requests.

Option 3 is the smallest diff; option 2 catches the problem at PR time, which is ideal.

Proper fix for the scope mismatch itself

Restructure resolveFeatures in workspace-server/src/features/feature-resolver.ts so read groups contribute their .readonly scope only when the paired write group is disabled — falling back to the broader write scope when both groups are active. This avoids asking users to consent to both broad and narrow scopes (e.g. drive and drive.readonly), and it keeps read tools functional when users disable a write group via WORKSPACE_FEATURE_OVERRIDES (e.g. gmail.write:off).

Once that's in, the published GCP consent screen and scripts/setup-gcp.sh would still need to gain the six .readonly scopes, since they'd be requested whenever a user disables the corresponding write group. That update should land via whichever consistency mechanism is chosen above.

Workaround for users hitting the block today

Until the published project is updated, users can match the currently-registered scope set via:

export WORKSPACE_FEATURE_OVERRIDES="drive.read:off,calendar.read:off,chat.read:off,gmail.read:off"

Tradeoff: the read-only tools in those four services (e.g. gmail.search, drive.search, calendar.list, chat.listSpaces) won't be registered with the MCP server, even though the write-group scopes would cover their API calls. Users who need those tools should instead run ./scripts/setup-gcp.sh against their own GCP project and manually add the six missing .readonly scopes to their consent screen.

Repro

  1. Unset WORKSPACE_CLIENT_ID and WORKSPACE_CLOUD_FUNCTION_URL so the client uses DEFAULT_CONFIG (pointing at the published geminicli.com cloud function).
  2. Trigger the OAuth flow on an account that isn't a test user on some project that covers every requested scope.
  3. Observe "This app is blocked" instead of the consent screen.

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