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.
4 steps from start to impact.
Attacker code lands in a sandboxed Deno runtime
--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.- Target invokes Deno with
--deny-netrestrictions - Attacker can submit or influence the JS/TS that Deno executes
- Most Deno deployments run trusted first-party code with
--allow-allor no permission flags at all - Multi-tenant Deno script execution is a niche pattern outside Deno Deploy and a handful of edge platforms
Attacker controls DNS for a domain
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.- Outbound DNS from the Deno host to public resolvers
- Ability to publish an A record
- 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
Open WebSocket to the crafted hostname
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*.- Deno < 2.8.1
- Use of the WebSocket API specifically (not fetch — fetch has its own path)
- 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
Exfiltrate or pivot
- A denied-range service actually speaks WebSocket and accepts the upgrade
- That service has data or actions worth reaching
- 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
The supporting signals.
| In the wild | No known exploitation as of 2026-06-30. Not on CISA KEV. |
|---|---|
| Public PoC | No standalone PoC repo published; technique is trivial (DNS A-record → denied IP) and reproducible from the advisory text in GHSA-83pc-3rw9-qpwj. |
| EPSS | 0.00106 (~0.1%) — bottom-of-percentile likelihood of exploitation in the next 30 days. |
| KEV status | Not listed. |
| CVSS vector | CVSS: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 versions | All Deno releases < 2.8.1. |
| Fixed versions | Deno 2.8.1 and later. No distro backports relevant — Deno is a single static binary. |
| Exposure / scanning | Deno is not a long-running listener; Shodan / Censys / GreyNoise have no signal for this CVE. No reachable internet-exposed surface for the bug itself. |
| Disclosed | 2026-06-23 via GitHub Security Advisory GHSA-83pc-3rw9-qpwj. |
| Reporter | Reported by the Deno security team / external researcher via the GHSA channel (see vendor advisory). |
noisgate verdict.
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.
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.
What to do — in priority order.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
#!/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
If you remember one thing.
--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
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.