Skip to content

dbt-mcp exposes dbt platform OAuth access and refresh tokens to local processes

Moderate
taylor-brudos published GHSA-qgp5-9hvj-2pr7 Aug 21, 2026

Package

pip dbt-mcp (pip)

Affected versions

>= 1.15.0, < 2.1.2

Patched versions

2.1.2

Description

Summary

The dbt-mcp OAuth helper exposed dbt platform OAuth tokens to local processes through two related defects:

  1. POST /selected_projects returned the full DbtPlatformContext, including decoded_access_token with the plaintext access and refresh tokens, with no authentication and no redaction. The bundled frontend never read the response body, so the credential served no purpose there.
  2. The persisted context file (~/.dbt/mcp.yml) was created with permissions following the process umask, typically 0644, leaving the same tokens readable by other local users.

This is an incomplete fix of CVE-2026-55837. That patch (b8bdb40, v1.20.0) removed GET /dbt_platform_context and added TrustedHostMiddleware, closing the remote DNS-rebinding vector, but did not address the sibling endpoint returning the same token-bearing object.

Root cause

POST /selected_projects constructed a new DbtPlatformContext with decoded_access_token set to the live token captured by the OAuth callback, then returned that context unmodified. DbtPlatformContext.decoded_access_token is a plain Pydantic field, and DecodedAccessToken.access_token_response held access_token and refresh_token as plain str with no SecretStr and no Field(exclude=...), so FastAPI serialized both into the response body. The route's only guard confirmed that the OAuth flow had completed, not that the caller was authorized: there was no Depends(), session, or origin check.

Separately, the context file was created via Path.touch() with no mode argument, so its permissions followed the process umask.

Maintainer assessment

Exploitation requires the ability to execute code on the developer's machine and is limited to an in-progress OAuth login session. The helper binds 127.0.0.1 and TrustedHostMiddleware rejects non-loopback Host headers, so the endpoint is not reachable from the network and the DNS-rebinding vector described in CVE-2026-55837 remains closed. The listener exists only while a login is in flight: it is started by the login flow, is not used during token refresh, and returns HTTP 500 with no token before the OAuth callback populates it. An attacker's request also overwrites the victim's stored project selection, so exploitation is not silent.

In affected versions the persisted context file was readable by other local users, so for most local attackers the endpoint offered no capability they did not already have. The case the endpoint uniquely enables is a sandboxed attacker that can reach loopback but cannot read that file, such as a malicious browser extension holding http://127.0.0.1:*/* permissions.

Assessed as CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N (5.0, Moderate).

Reporter's assessment

Persistent dbt platform account takeover. The leaked refresh_token (offline_access grant) lets the attacker mint fresh access tokens indefinitely. Full read/write over the victim's dbt platform account: source code, warehouse credentials/connections, production job runs, environment settings. Exploit requires no victim interaction and no credentials — only the ability to make loopback HTTP requests (any process co-located on the host). The original remote (DNS-rebinding) vector is correctly closed by TrustedHostMiddleware; this is the residual local vector the fix never addressed.

Proof of concept

Running the live create_app() with a seeded token and Host 127.0.0.1 (stubbing only the external dbt platform account fetch):

POST /selected_projects  Host: 127.0.0.1  body {"account_id": 42, "project_ids": [1, 2]}
  -> 200 OK
  -> response JSON contains:
     decoded_access_token.access_token_response.access_token   = eyJhbG...TURE
     decoded_access_token.access_token_response.refresh_token = dbt-platform-offline-refresh-SUPERSECRET-abc123

Two-request exfil chain for a co-located attacker (helper on 127.0.0.1:6785):

1. curl -s http://127.0.0.1:6785/projects -H 'Host: localhost'          # enumerate account ids
2. curl -s http://127.0.0.1:6785/selected_projects -H 'Host: localhost' \
   -H 'Content-Type: application/json' -d '{"account_id": ACCOUNT_ID, "project_ids": [1, 2]}'
   # two arbitrary project ids skip the single-project environment-fetch branch

Affected versions

Affected: >= 1.15.0, < 2.1.2. Introduced in 1.15.0 (commit 296cd48, #717), the first release containing /selected_projects. Patched in 2.1.2.

Remediation

Upgrade to dbt-mcp 2.1.2. That release:

  1. Returns only a status object from /selected_projects, so no token can appear in the response body.
  2. Creates and maintains ~/.dbt/mcp.yml with 0600 permissions.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
Low
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N

CVE ID

No known CVE

Weaknesses

Insertion of Sensitive Information Into Sent Data

The code transmits data to another actor, but a portion of the data includes sensitive information that should not be accessible to that actor. Learn more on MITRE.

Incorrect Default Permissions

During installation, installed file permissions are set to allow anyone to modify those files. Learn more on MITRE.

Insufficiently Protected Credentials

The product transmits or stores authentication credentials, but it uses an insecure method that is susceptible to unauthorized interception and/or retrieval. Learn more on MITRE.

Credits