
Cloud Vulnerability DB
A community-led vulnerabilities database
Code read at bf7eb2c (v1.12.7). Found in an external security review (Oct 2026).
The hosted Semantic Layer client picks the host and the token independently, then sends the token to that host:
connect.py:2204: host = env.get("DBT_SL_HOST") or getattr(semantic, "host", None), so semantic.host from .dex/config.yml is used whenever DBT_SL_HOST is unset.connect.py:2213: token = env.get("DBT_SL_TOKEN") or _dbt_cloud_service_token(env) (the latter reads ~/.dbt/dbt_cloud.yml).explore/semantic/hosted.py:157: self._url = f"https://{host}/api/graphql".explore/semantic/hosted_transport.py:16-29: post_graphql POSTs with Authorization: Bearer {token}.
Nothing checks that the host belongs to dbt Cloud (config.py SemanticConfig.host takes any value)..dex/config.yml lives in the repository. A repository that sets semantic.host to a server it controls receives the user's dbt Cloud service token the first time anyone runs explore semantic --api in it. Typical cases: a cloned third-party project, a fork, or a PR an agent is reviewing. The token comes from the user's environment or home directory, not from the repository. The ~/.dbt/dbt_cloud.yml fallback may hold a full dbt Cloud API token rather than a Semantic-Layer-only one (see the _dbt_cloud_service_token docstring), which widens the blast radius. The source= path (host-supplied config and token) is not affected.
*.getdbt.com (suffix match on the parsed hostname, so regional and multi-tenant hosts such as sl.cloud.getdbt.com keep working; also localhost for tests).post_graphql, so no other caller can send the Bearer elsewhere..dex/config.yml.DBT_SL_TOKEN_FILE (refused unless mode 0600) as an alternative to the env var.semantic.host: attacker.example and a token present, no outbound request is made, and the refusal names the host.sl.cloud.getdbt.com (the existing test fixture) and regional *.getdbt.com hosts still work.6f94352 (main)Still present on main. Three additions:
dbt.com, not only getdbt.com. references/semantic-layer.md:397 documents host: <account>.semantic-layer.<region>.dbt.com, so a *.getdbt.com-only check would break the documented form. Compare host == s or host.endswith("." + s) (a bare endswith("dbt.com") admits evildbt.com) on a lowercased hostname with any trailing dot stripped, and refuse /, @, : and whitespace, since hosted.py:157 interpolates the host into the URL as-is (evil.example/x? carries a path)._dbt_cloud_service_token returns the token-value of the first project in ~/.dbt/dbt_cloud.yml, possibly for a dbt Cloud account the user never pointed dex at. Consider refusing instead of falling back.source= path is safe only if the embedding host controls the config. With a SemanticSource, the host still comes from the passed config's semantic.host. Apply the same host check there, with the escape hatch for private-link hosts settable only by the embedding host (a DexEngine parameter), never by .dex/config.yml.
The project already treats .dex/config.yml as possibly agent-written (values-free mode cannot be selected from it, "since an agent may have written that file"), which puts a hostile config inside the threat model.Reported by Sthenos Security. Reported by Sthenos Security, found with Reachable. Fixed in exmergo-dex-core 1.16.1.
Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."