A developer-tool parameter parser that trusts the dev a little too much
CVE-2026-13752 is an *improper input neutralization* (CWE-89) flaw in the Snowflake CLI (snow) prior to 3.19. When the CLI processes certain user-supplied parameters — likely in YAML spec files, --variable substitutions, or identifier handling consistent with other 2026 Snowflake CLI fixes — the values are interpolated into the SQL sent to Snowflake without proper escaping. A crafted parameter value (e.g. embedding $$, single quotes, or identifier separators) can break out of the intended literal/identifier context and append attacker-chosen SQL that executes under the invoking user's Snowflake role.
Vendor scored this MEDIUM (6.0) with vector AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:N. That vector is honest: local attack, high complexity, requires the victim to run the CLI on attacker-influenced input. In practice this is a *trojaned project file* or *malicious teammate* threat model, not a remote pwn. For a defender managing 10,000 hosts, this is a developer-laptop / CI-runner concern with a narrow blast radius — Snowflake's MEDIUM is slightly hot for the typical deployment, hence the downgrade to LOW.
3 steps from start to impact.
Attacker stages a malicious project artifact
snowflake.yml, deployment manifest, or parameter value (e.g., in a PR, a shared repo, a Slack snippet, or a downloaded sample app). The payload embeds SQL-breaking sequences inside a field the CLI will later interpolate into a query. No tooling required beyond a text editor.- Write access to a file/PR/artifact the victim will consume
- Knowledge of which CLI parameter is unsafely interpolated
- Code review on the PR catches odd
$$or quote sequences - Repo CODEOWNERS / branch protection blocks untrusted contributors
snowflake.yml)Developer runs snow against the poisoned input
snow sql -f ..., snow spcs service create, snow app deploy, or similar against the trojaned manifest. The CLI builds the SQL string client-side and sends it to Snowflake over an authenticated session.- Victim has a valid Snowflake session / configured connection
- Victim executes the CLI with the attacker's input as an argument or file
- UI:R requirement — needs a human to run the command
- Many shops gate Snowflake credentials behind SSO + short-lived tokens
QUERY_HISTORY shows the malformed/extra statementsInjected SQL executes under the victim's Snowflake role
GRANT, CREATE USER, COPY INTO @stage for exfil, or DROP for destruction. Scope is bounded by the role's privileges; a SYSADMIN/ACCOUNTADMIN victim is catastrophic, an ANALYST victim is contained.- Victim's role has the privileges the payload needs
- No row/object-level masking blocking the target data
- Least-privilege role design limits blast radius
- Network policies / IP allowlists do not help here — traffic is from the legit user
ACCESS_HISTORY and QUERY_HISTORY anomaly rules; Datadog/Panther Snowflake monitorsThe supporting signals.
| In-the-wild exploitation | None observed. No public reports, no incident disclosures as of 2026-06-30. |
|---|---|
| Public PoC | No public PoC repo identified. Pattern is consistent with prior Snowflake CLI 2026 fixes (YAML $$ break-out, SHOW … LIKE literal handling, FQN.sql_identifier escaping). |
| EPSS | Not yet scored at disclosure (typical for sub-week-old developer-tool CVEs); expect <1st percentile. |
| KEV | Not listed. Unlikely to qualify — no remote vector. |
| CVSS vector | AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:N — local, high complexity, low priv, user interaction; high C/I, no A impact. |
| Affected versions | Snowflake CLI (snowflake-cli) < 3.19 |
| Fixed version | 3.19.0 (PyPI: snowflake-cli==3.19.0) |
| Exposure population | Per PyPI download stats Snowflake CLI sees ~hundreds of thousands of monthly installs — but the attack surface is *developer endpoints and CI runners*, not internet-facing infra. Shodan/Censys: not applicable (no listener). |
| Disclosed | 2026-06-29 |
| Reporter | Snowflake PSIRT (researcher attribution not yet published) |
noisgate verdict.
The single most decisive factor is the local attack vector combined with required user interaction on attacker-supplied input — this is a trojaned-project-file threat model, not a remote exploit. Blast radius is further bounded by the invoking user's Snowflake role, which in a least-privilege account is narrow.
Why this verdict
- Local + UI:R + AC:H — three friction multipliers stacked. Attacker must place a file, get the victim to run a specific CLI command against it, and have the payload survive any review.
- Blast radius is role-scoped. Damage equals what the victim's Snowflake role can already do. Shops following Snowflake's documented least-privilege model (functional roles, no default
ACCOUNTADMIN) contain this to a single dataset. - Role multiplier: if a CI/CD service account running
snowholdsACCOUNTADMINorSECURITYADMIN, the chain ends in tenant-wide takeover of the Snowflake account. However, the affected component (CLI binary) is *not canonically* a high-value-role component the way a hypervisor or DC is — the high-value role share is well under 10% of installs (mostsnowinvocations are interactive developer sessions with scoped roles), so the CRITICAL floor does not trip. HIGH floor also does not trip because the high-privilege-CI-account population is itself a minority of installs AND requires the trojaned-input prerequisite. - No remote vector, no KEV, no PoC, no listener for scanners to find. EPSS will sit near zero.
Why not higher?
MEDIUM would be defensible if the CLI were commonly run by ACCOUNTADMIN-class automation against untrusted inputs — but the documented and best-practice pattern is scoped functional roles. HIGH is off the table because there is no remote vector, no unauthenticated path, and no service-listener exposure.
What to do — in priority order.
- Upgrade
snowflake-clito 3.19.0+ on developer laptops and CI runners — This is a code-fix bug; the real remediation is the package bump. Pin inrequirements.txt/pyproject.toml, rebuild base CI images. Per noisgate remediation SLA for LOW: backlog hygiene — fold into the normal dependency-refresh cycle, target within ~365 days but ideally next CI image rebuild. - Audit Snowflake roles used by CI service accounts and developer SSO — Confirm no automation runs
snowunderACCOUNTADMIN/SECURITYADMIN. Move to functional roles (APP_DEPLOYER,SCHEMA_OWNER) with explicit grants. This is the single biggest blast-radius reducer for this CVE and the next one. - Enable Snowflake
ACCESS_HISTORY+ alerting on anomalous CLI sessions — PipeQUERY_HISTORYandACCESS_HISTORYinto your SIEM. Alert on unexpectedGRANT,CREATE USER,COPY INTO @stagefrom CLI client sessions. Catches both this and any successor injection bug. - Treat
snowflake.ymland Snowflake project manifests as code — Require PR review, CODEOWNERS, and a semgrep/regex rule flagging suspicious$$, unbalanced quotes, or identifier separators in those files.
- Network ACLs / IP allowlists on Snowflake — the injected SQL rides the legitimate user's authenticated session from a permitted IP.
- WAFs / NGFWs — there is no inbound network attack surface; the CLI is the attacker tool, not a victim service.
- MFA on the Snowflake login — the victim has already authenticated; MFA is satisfied before the malicious CLI invocation.
Crowdsourced verification payload.
Run on each developer laptop or CI runner that has Python/Snowflake CLI installed. Invoke as python3 check_cve_2026_13752.py — no privileges required beyond reading the local Python environment. For CI, wire it into your image-build pipeline.
#!/usr/bin/env python3
# CVE-2026-13752 — Snowflake CLI SQL injection (<3.19)
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN / not installed
import sys, shutil, subprocess, re
FIXED = (3, 19, 0)
def parse(v):
m = re.match(r'(\d+)\.(\d+)\.(\d+)', v.strip())
return tuple(int(x) for x in m.groups()) if m else None
def get_version():
# 1) Try the `snow` binary
snow = shutil.which('snow')
if snow:
try:
out = subprocess.check_output([snow, '--version'], text=True, stderr=subprocess.STDOUT, timeout=10)
m = re.search(r'(\d+\.\d+\.\d+)', out)
if m: return m.group(1), 'snow --version'
except Exception:
pass
# 2) Fall back to pip metadata
try:
import importlib.metadata as md
return md.version('snowflake-cli'), 'pip metadata'
except Exception:
try:
import importlib.metadata as md
return md.version('snowflake-cli-labs'), 'pip metadata (labs)'
except Exception:
return None, None
ver, src = get_version()
if ver is None:
print('UNKNOWN: snowflake-cli not installed on this host')
sys.exit(2)
parsed = parse(ver)
if parsed is None:
print(f'UNKNOWN: could not parse version string {ver!r} ({src})')
sys.exit(2)
if parsed < FIXED:
print(f'VULNERABLE: snowflake-cli {ver} < 3.19.0 ({src})')
sys.exit(1)
print(f'PATCHED: snowflake-cli {ver} ({src})')
sys.exit(0)
If you remember one thing.
snowflake-cli >= 3.19.0 bump into the normal dependency-refresh cycle (target within 365 days, but realistically the next CI image rebuild). Monday morning: pin snowflake-cli==3.19.0 in your CI base images, push a Slack/email note to data-engineering teams to pipx upgrade snowflake-cli, and — far more valuable than the patch itself — audit whether any CI service accounts run snow under ACCOUNTADMIN and demote them to scoped functional roles. That role-hygiene work pays down risk on this CVE and the next half-dozen Snowflake CLI bugs.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.