← Back to Feed CACHED · 2026-06-29 20:56:59 · CACHE_KEY CVE-2026-49860
CVE-2026-49860 · CWE-918 · Disclosed 2026-06-23

Deno is a JavaScript

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

A fence with a working gate guard who forgets to check IDs once you're past the turnstile

Deno's runtime permission model lets operators run untrusted JavaScript/TypeScript with --deny-net=10.0.0.0/8,169.254.169.254,... to keep scripts off internal networks and cloud metadata endpoints. CVE-2026-49860 is a classic *time-of-check / time-of-use* gap in the WebSocket API: Deno validates the hostname string against the deny list, then performs DNS resolution and connects to whatever IP comes back. An attacker hosting a domain that resolves to 169.254.169.254 (or 127.0.0.1, or an RFC1918 address) passes the string check, then connects to the forbidden IP. All Deno versions before 2.8.1 are affected; fixed in 2.8.1.

Vendor pegged this at MEDIUM 5.2 (AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N) — that's appropriate. The vector correctly captures that the attacker must already be executing code inside the Deno sandbox; the bug only *defeats a defense-in-depth control*, it does not grant RCE, code execution, or remote reachability on its own. The Scope:Changed flag is what bumps it out of LOW territory in CVSS math — escaping the permission boundary touches a different trust zone. In practice, the population of orgs running untrusted Deno scripts behind --deny-net is a small subset of Deno's already modest enterprise footprint, and most of them are pairing it with network egress filtering anyway. We're calling this LOW.

"Sandbox-escape DNS rebind in Deno's WebSocket allowlist. Requires you to already be running attacker code under --deny-net. Backlog hygiene."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attacker code lands in a sandboxed Deno runtime

The target is running Deno with explicit permission flags (e.g., --allow-net --deny-net=169.254.169.254,10.0.0.0/8) and is willing to execute attacker-controlled or attacker-influenced JS/TS — a serverless function, an edge-worker script, a plugin, a CI job evaluating a third-party module, or a multi-tenant Deno Deploy-style platform. Without this preexisting code-execution position, the bug is unreachable.
Conditions required:
  • Target invokes Deno with --deny-net restrictions
  • Attacker can submit or influence the JS/TS that Deno executes
Where this breaks in practice:
  • Most Deno deployments run trusted first-party code with --allow-all or no permission flags at all
  • Multi-tenant Deno script execution is a niche pattern outside Deno Deploy and a handful of edge platforms
Detection/coverage: No CVE-specific signature; AppSec review of how untrusted scripts reach Deno is the only meaningful coverage.
STEP 02

Attacker controls DNS for a domain

The attacker registers evil.example (or compromises a domain) and sets an A record pointing at a denied internal IP — 169.254.169.254 for AWS/Azure/GCP metadata, an RFC1918 address for lateral movement, or 127.0.0.1 for loopback services. Standard dnsmasq, BIND, or any DNS-as-code provider (Route53, Cloudflare) does the job.
Conditions required:
  • Outbound DNS from the Deno host to public resolvers
  • Ability to publish an A record
Where this breaks in practice:
  • Split-horizon DNS that refuses to return RFC1918 / link-local answers will break this step
  • Cloud workloads using IMDSv2 on AWS reject unauthenticated metadata fetches even if reached
Detection/coverage: Passive DNS logging (Zeek, Corelight, Cloudflare 1.1.1.1 for Families) will show the resolution-to-private-IP pattern.
STEP 03

Open WebSocket to the crafted hostname

Inside the Deno script: new WebSocket("ws://evil.example/"). The runtime checks the literal string evil.example against the --deny-net deny list, finds no match (the deny list contains IPs/CIDRs, not the attacker's domain), then resolves and connects to the returned 169.254.169.254. No public PoC is published as of disclosure; the technique is trivial — standard *DNS rebinding against post-DNS-unaware allow/deny logic*.
Conditions required:
  • Deno < 2.8.1
  • Use of the WebSocket API specifically (not fetch — fetch has its own path)
Where this breaks in practice:
  • Many internal services do not speak WebSocket and will reject the upgrade handshake immediately
  • Cloud metadata services do not speak WebSocket — you get a TCP connect and an HTTP 4xx, not a usable channel
Detection/coverage: EDR / sidecar netflow showing the Deno process opening sockets to denied ranges is the cleanest tell.
STEP 04

Exfiltrate or pivot

Because WebSocket is bidirectional, the attacker can send arbitrary bytes to and from the denied IP — useful for talking to internal HTTP/WS services that *do* accept WS upgrades (some Kubernetes API endpoints, internal admin panels, dev tools). The realistic blast radius is whatever those reachable services expose to an unauthenticated TCP peer.
Conditions required:
  • A denied-range service actually speaks WebSocket and accepts the upgrade
  • That service has data or actions worth reaching
Where this breaks in practice:
  • Cloud metadata (the most valuable target) is HTTP-only; WS path yields nothing
  • Most internal services require auth; an SSRF without credential-theft primitive is limited
Detection/coverage: Egress proxy logs showing WS CONNECT to internal CIDRs; runtime allowlists at the L7 proxy.
03 · Intelligence Metadata

The supporting signals.

In the wildNo known exploitation as of 2026-06-30. Not on CISA KEV.
Public PoCNo standalone PoC repo published; technique is trivial (DNS A-record → denied IP) and reproducible from the advisory text in GHSA-83pc-3rw9-qpwj.
EPSS0.00106 (~0.1%) — bottom-of-percentile likelihood of exploitation in the next 30 days.
KEV statusNot listed.
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N — local AV, low privileges, Scope:Changed is the only thing keeping this above LOW in vendor math. Confidentiality/Integrity *Low*, no availability impact.
Affected versionsAll Deno releases < 2.8.1.
Fixed versionsDeno 2.8.1 and later. No distro backports relevant — Deno is a single static binary.
Exposure / scanningDeno is not a long-running listener; Shodan / Censys / GreyNoise have no signal for this CVE. No reachable internet-exposed surface for the bug itself.
Disclosed2026-06-23 via GitHub Security Advisory GHSA-83pc-3rw9-qpwj.
ReporterReported by the Deno security team / external researcher via the GHSA channel (see vendor advisory).
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to LOW (3.4/10)

The single decisive factor is that exploitation requires the attacker to already be executing code inside a Deno sandbox that the defender explicitly hardened with --deny-net — a niche configuration with a small installed base, where success only defeats a *defense-in-depth* control rather than yielding new code execution. Confidentiality and integrity impact are *Low* per the vendor vector, and there is no role-multiplier path: Deno is not canonically deployed as an identity, hypervisor, CA, or kernel-mode component.

HIGH Technical accuracy of the bug class (post-DNS check missing — classic CWE-918 pattern)
HIGH Fixed-version remediation path (Deno 2.8.1)
MEDIUM Real-world exposure population — no public Deno-multi-tenant census exists

Why this verdict

  • Preconditions are heavy. Attacker must (a) get code into a Deno runtime, (b) that runtime must use --deny-net, and (c) the targeted internal service must speak WebSocket. Each prerequisite narrows the population.
  • No remote reachability. AV:L is correct — the bug is unreachable from the network without an existing code-execution channel. EPSS 0.00106 reflects this.
  • Blast radius is sandbox-escape only. Successful exploitation reveals the internal-IP attack surface to an already-running attacker script; it does not create one.
  • Role multiplier: low-value role (typical). Deno runs scripts, CLI tools, build steps, edge functions. Even in the most common configuration the chain ends at *one reachable internal HTTP/WS endpoint per request*, not fleet compromise.
  • Role multiplier: typical role. Deno used as a serverless/edge function platform — chain still ends at SSRF against whatever the worker pod can reach; cloud metadata (the canonical SSRF prize) is HTTP-only and IMDSv2 defeats it.
  • Role multiplier: high-value role. Deno is not deployed as a domain controller, IdP, hypervisor, PAM, CA, backup server, EDR/AV, or network-edge appliance. There is no canonical high-value role for the Deno binary, so the deployment-role floor does not push above HIGH and is not engaged here.
  • No KEV, no active exploitation, no public PoC repo. Removes the override that would force immediate mitigation regardless of bucket.

Why not higher?

Promoting to MEDIUM would require either a credible path to remote unauthenticated reach (there is none — AV:L), a high-value-role component (Deno is not), or KEV/active-exploitation evidence (none). The CVSS 5.2 MEDIUM rating is driven almost entirely by Scope:Changed; in operator reality, sandbox-bypass-only with no chained impact does not warrant a 30-day mitigation SLA.

Why not lower?

Calling this IGNORE would be wrong because the bug is a real, exploitable control bypass with a published fix and a non-zero population of multi-tenant Deno operators (Deno Deploy, edge-worker shops) for whom --deny-net is a load-bearing boundary. LOW correctly flags it as backlog hygiene without forcing emergency change-windows.

05 · Compensating Control

What to do — in priority order.

  1. Upgrade Deno to 2.8.1+ — The only complete fix. Deno is a single static binary; replace it in your container images, GitHub Actions runners, and edge-worker bases. No mitigation SLA applies (LOW verdict per noisgate SLA); fold into the standard 365-day remediation track and roll it during the next normal base-image refresh.
  2. Block egress to RFC1918, link-local (169.254/16), and loopback at the network layer for Deno workloads — A network-layer egress policy on the host/pod (NetworkPolicy, iptables, security group, service-mesh egress rules) makes the post-DNS bypass moot because the *socket* never establishes. This is the right control for multi-tenant Deno operators and should be in place regardless of this CVE. Apply during the same 365-day window.
  3. Enforce IMDSv2 on all AWS instances running Deno workloads — Even if WS-over-SSRF reaches 169.254.169.254, IMDSv2 requires a session token obtained via PUT — which a one-shot SSRF cannot mint. This is general SSRF hardening, not CVE-specific.
  4. Inventory where Deno actually runs --deny-net — Most Deno installs in your fleet are probably trusted first-party scripts running with broad permissions; for those the CVE is informational. Confirm where the deny list is load-bearing and prioritize those hosts within the LOW remediation window.
What doesn't work
  • WAF rules — the WebSocket is initiated *from inside* the Deno process to an internal IP; a perimeter WAF never sees the traffic.
  • Hostname allowlisting at DNS alone — the bug exploits the gap *after* DNS resolution; restricting which hostnames the script may resolve is essentially the same flawed check that failed in Deno.
  • EDR on the Deno host — opening a WebSocket from a permitted process to a routable IP is not a behavioral anomaly any EDR will flag.
  • --allow-net=<hostlist> instead of --deny-net — the allowlist path has the same TOCTOU class historically; verify your Deno version's behavior, do not assume allowlist is safe.
06 · Verification

Crowdsourced verification payload.

Run on each Linux/macOS host or container image that ships a Deno binary. Invoke as bash check-cve-2026-49860.sh (no privileges required — it just inspects the on-disk Deno version). Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check-cve-2026-49860.sh — Deno WebSocket post-DNS check bypass
# PATCHED: Deno >= 2.8.1
set -u

FIXED_MAJOR=2
FIXED_MINOR=8
FIXED_PATCH=1

DENO_BIN="${DENO_BIN:-$(command -v deno || true)}"
if [ -z "$DENO_BIN" ] || [ ! -x "$DENO_BIN" ]; then
  echo "UNKNOWN: deno not found on PATH (set DENO_BIN=/path/to/deno to override)"
  exit 2
fi

RAW="$("$DENO_BIN" --version 2>/dev/null | head -n1)"
# Expected: 'deno 2.8.1 (release, x86_64-unknown-linux-gnu)'
VER="$(printf '%s' "$RAW" | awk '{print $2}')"
if [ -z "$VER" ]; then
  echo "UNKNOWN: could not parse 'deno --version' output: $RAW"
  exit 2
fi

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

cmp() {
  if   [ "$MAJ" -gt "$FIXED_MAJOR" ]; then return 0
  elif [ "$MAJ" -lt "$FIXED_MAJOR" ]; then return 1
  elif [ "$MIN" -gt "$FIXED_MINOR" ]; then return 0
  elif [ "$MIN" -lt "$FIXED_MINOR" ]; then return 1
  elif [ "$PAT" -ge "$FIXED_PATCH" ]; then return 0
  else return 1
  fi
}

if cmp; then
  echo "PATCHED: deno $VER (>= 2.8.1) at $DENO_BIN"
  exit 0
else
  echo "VULNERABLE: deno $VER (< 2.8.1) at $DENO_BIN — upgrade to 2.8.1+"
  exit 1
fi
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: do not interrupt change-windows for this. The noisgate verdict is LOW, which per the noisgate mitigation SLA carries *no mitigation deadline* — go straight to the noisgate remediation SLA of ≤ 365 days and fold the Deno 2.8.1+ upgrade into your next normal base-image / runner-image refresh. The only audience that should treat it as urgent is teams operating multi-tenant Deno execution (Deno Deploy-style platforms, edge-worker shops) where --deny-net is a real security boundary — those teams should add a network-layer egress block to RFC1918/link-local/loopback for Deno workloads in the same refresh and verify IMDSv2 is enforced on AWS. No KEV, no active exploitation, no public PoC — there is no override forcing a faster track.

Sources

  1. GitLab Advisories — CVE-2026-49860
  2. GitHub Security Advisory GHSA-83pc-3rw9-qpwj
  3. Deno release notes
  4. Deno permissions documentation (--deny-net)
  5. CWE-918: Server-Side Request Forgery
  6. FIRST EPSS model
  7. CISA KEV catalog
  8. AWS IMDSv2 (mitigates SSRF→metadata)
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.