Skip to content

client_secret.json without project_id: silent fallback to ambient gcloud ADC quota_project_id escapes CONFIG_DIR isolation #878

Description

@alexey-pelykh

Description

When client_secret.json has no project_id field, gws does not omit the x-goog-user-project quota project — it silently falls back to the ambient gcloud ADC quota_project_id read from ~/.config/gcloud/application_default_credentials.json.

Two problems:

  1. It escapes GOOGLE_WORKSPACE_CLI_CONFIG_DIR isolation. The whole point of pointing CONFIG_DIR at a self-contained directory is that the binding is defined by that directory. Instead gws reaches outside it into machine-global gcloud state that the user never associated with this binding.
  2. It fails confusingly. The resulting 403 names a project the user has no relationship with, giving no hint that the value came from ADC. This appears to be the same symptom reported in 403 permission error referencing unknown project ID (not user's project) #291 ("403 permission error referencing unknown project ID (not user's project)", closed) but via a different trigger, so it may be worth a regression check there.

Environment

Detail Value
gws 0.22.5
OS macOS (Darwin 25.5.0, arm64)
Auth OAuth desktop-app flow, file keyring backend
gcloud ADC present, with quota_project_id set to an unrelated project

Steps to Reproduce

  1. Have gcloud ADC present with a quota project set — i.e. ~/.config/gcloud/application_default_credentials.json contains "quota_project_id": "<unrelated-project-from-adc>". This is the normal state on any machine where gcloud auth application-default login has been run.
  2. Set up a gws binding in an isolated dir: GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/path/to/binding, with its own client_secret.json, and authenticate.
  3. Remove the project_id key from /path/to/binding/client_secret.json (leave client_id / client_secret intact — OAuth itself does not need project_id).
  4. Make any raw Discovery call, e.g.:
    GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/path/to/binding \
      gws gmail users messages get --params '{"userId":"me","id":"<MSG_ID>","format":"minimal"}'
    

Expected Behavior

With no project_id in the binding's client_secret.json, gws should either:

— but not silently adopt a project from machine-global gcloud state that lives outside the configured CONFIG_DIR.

Actual Behavior

The quota project silently becomes the ADC quota_project_id, and the call 403s against a project the binding never referenced:

{
  "error": {
    "code": 403,
    "message": "Caller does not have required permission to use project <unrelated-project-from-adc>. Grant the caller the roles/serviceusage.serviceUsageConsumer role, or a custom role with the serviceusage.services.use permission, by visiting https://console.developers.google.com/iam-admin/iam?project=<unrelated-project-from-adc> and then retry.",
    "reason": "forbidden"
  }
}

Restoring project_id flips the named project back to the client's own, confirming the fallback chain is:

client_secret.json project_id  →  (if absent)  ADC quota_project_id  →  (no "none" option)

Additional Context

Honest scoping — two caveats so this is prioritised correctly:

Possibly related: #857 (GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE ignored when credentials.enc exists) looks like the same genre — ambient/co-located state overriding an explicitly configured binding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions