← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2025-21620 · CWE-200 · Disclosed 2025-01-06

Deno is a JavaScript

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

This is a valet handing your API key to the next parking lot because the first one pointed across the street

CVE-2025-21620 is a cross-origin redirect credential leak in Deno fetch(). If a Deno app sends an Authorization header to one origin and that origin responds with a redirect to a different origin, vulnerable Deno versions can carry the same Authorization header into the follow-up request. GitHub lists affected Deno versions as <1.46.4 and <2.1.2; the clearly documented fix line is 2.1.2, and the advisory also states the lower-level deno_fetch crate is fixed in 0.204.0.

The vendor's 7.5/HIGH score overstates enterprise urgency. This is not a direct pre-auth exploit against an exposed listener; it is a conditional secret-leak bug that only matters when your application performs authenticated outbound fetch() calls, follows redirects, and can be steered into an attacker-controlled or attacker-compromised redirector. That's meaningful, but it is not the same operational class as internet-reachable RCE.

"High on paper, but real attacks need a very specific outbound fetch-with-token redirect chain."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Get into the outbound request path

The attacker first needs influence over where the Deno application sends an authenticated fetch() request. In practice that means controlling the initial URL, compromising the upstream service, or abusing an application feature that proxies or forwards requests. The weaponized primitive is Deno's own fetch() redirect handling as described in the GitHub advisory.
Conditions required:
  • The target app uses Deno fetch() for outbound HTTP(S) requests
  • The request includes an Authorization header
  • The attacker can control the target URL or the first-hop server response
Where this breaks in practice:
  • Many Deno workloads never send bearer tokens to attacker-influenced URLs
  • Well-designed integrations hardcode upstream hosts instead of taking user-supplied URLs
  • Egress filtering or --allow-net host restrictions can block arbitrary destinations
Detection/coverage: Network scanners will miss this. SCA/SBOM tooling and host-side version inventory are the primary detection paths.
STEP 02

Force a cross-origin redirect

The attacker-controlled first hop returns a 30x redirect to a different origin, such as a second server they control. Vulnerable Deno follows the redirect and incorrectly preserves the original Authorization header. The advisory's reproduction uses Deno.serve() on two local ports to demonstrate the leak.
Conditions required:
  • The application allows redirects or does not set redirect: "manual"
  • The first-hop origin can emit a cross-origin redirect
Where this breaks in practice:
  • Some applications disable redirects for authenticated calls
  • Some upstream APIs never redirect, so the bug is dead code in those paths
  • Middleware or custom wrappers may already strip sensitive headers on redirect
Detection/coverage: Proxy logs can show outbound 30x chains followed by requests to a different host with authentication headers, but most enterprises do not log headers for obvious security reasons.
STEP 03

Capture the leaked secret

The second origin receives the redirected request and can read the leaked bearer token or API credential from the Authorization header. This is confidentiality impact only; the bug itself does not modify application state or crash the runtime. The practical blast radius equals the value and scope of the leaked credential.
Conditions required:
  • The redirected destination is attacker-controlled or monitored by the attacker
  • The leaked credential is still valid and useful
Where this breaks in practice:
  • Short-lived tokens, audience-bound tokens, or mTLS-backed auth reduce reuse value
  • Some tokens only authorize narrow API actions
  • Credential rotation and anomaly detection may contain follow-on abuse
Detection/coverage: Downstream API telemetry may reveal token reuse from anomalous source IPs after the leak, but that is a second-stage signal.
STEP 04

Attempt downstream abuse

With a captured token, the attacker pivots into the API or service that trusted that credential. This is where business impact happens: data access, tenant access, or service-to-service impersonation. The exploit chain ends here because the CVE itself is only the leak, not the post-leak account compromise.
Conditions required:
  • The leaked token grants access to a reachable downstream service
  • The downstream service does not enforce stronger binding than possession of the token
Where this breaks in practice:
  • Scoped OAuth tokens, IP allowlists, or proof-of-possession designs can blunt reuse
  • Mature cloud/API monitoring can catch unusual token use quickly
  • Many service tokens are low-value or environment-specific
Detection/coverage: Cloud audit logs, API gateway logs, and IAM telemetry are more useful than perimeter IDS for this stage.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo evidence surfaced in reviewed sources that CVE-2025-21620 is being exploited in the wild. It is not in CISA KEV.
Proof-of-concept availabilityPublic reproduction is available in the GitHub advisory itself using Deno.serve() plus fetch() redirect behavior. I did not find a separate weaponized exploit repo in reviewed primary sources.
EPSSUser-supplied EPSS is 0.00263, which is very low and consistent with a bug that needs a narrow application workflow rather than broad internet exposure.
KEV statusNot listed in the CISA KEV catalog.
CVSS vector reality checkCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N is technically defensible for the leak event, but it hides the real prerequisite: the attacker must get into a token-bearing outbound request path first.
Affected versionsGitHub marks Deno as affected in versions <1.46.4 and <2.1.2; the advisory also lists cargo deno_fetch as affected until 0.204.0.
Fixed versionsClearly documented fix: Deno 2.1.2. The GHSA page also references a 1.46.4 boundary, but I did not find a public v1.46.4 release page in reviewed sources, so operationally anchor on 2.1.2+ or vendor-confirmed downstream backports.
Scanning / exposure dataThere is no meaningful internet-wide banner/signature story here. This is a runtime behavior issue, so Shodan/Censys-style exposure counts are not a useful prioritization input; SCA, SBOM, and endpoint inventory are.
DisclosurePublished 2025-01-06 by GitHub CNA; CISA included it in the weekly bulletin released 2025-01-13.
ReporterCredited reporter in the GitHub advisory: rexxars.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.6/10)

The decisive downgrade factor is attacker position: this is not a straight shot from the internet into a listening Deno service, but a leak that requires the attacker to land inside a very specific authenticated outbound request flow. That sharply reduces exposed population compared with the vendor's CVSS baseline, even though leaked bearer tokens can still hurt when the application pattern exists.

HIGH Technical root cause and affected/fixed version understanding
MEDIUM Real-world exposure prevalence across enterprise Deno deployments

Why this verdict

  • Downgrade for attacker position: exploitation requires influence over an authenticated outbound fetch() path, not merely network reachability to a Deno host
  • Downgrade for exposure population: only apps that both send sensitive Authorization headers and follow cross-origin redirects are actually exposed
  • Downgrade for security friction: --allow-net scoping, fixed upstream hostnames, egress controls, and manual redirect handling break many real deployments
  • Severity floor stays at MEDIUM: if the leaked credential is a real bearer/API token, downstream compromise can be immediate even though the CVE itself is only a confidentiality bug

Why not higher?

There is no code execution, no integrity impact, and no availability impact in the vulnerability itself. More importantly, the exploit chain assumes a prior foothold into the application's request-routing logic or a compromised/attacker-controlled upstream redirector, which is a major real-world narrowing compared with classic pre-auth remote bugs.

Why not lower?

A leaked Authorization header is not cosmetic. In service-to-service environments, those headers often carry bearer tokens, PATs, or API keys that can immediately unlock downstream data or tenant actions, so this is more than backlog trivia when the vulnerable call pattern exists.

05 · Compensating Control

What to do — in priority order.

  1. Disable redirect following on authenticated fetches — For code paths that attach Authorization, set redirect handling to manual or explicitly re-issue redirected requests only after stripping sensitive headers. There is no mitigation SLA for a MEDIUM verdict, so use this where patching will be delayed and otherwise go straight to the normal remediation window.
  2. Constrain outbound destinations — Use Deno --allow-net host scoping plus network egress policy so the runtime cannot talk to arbitrary attacker-chosen hosts. This reduces the reachable population of the bug and is worth enforcing as a hardening baseline even though there is no separate noisgate mitigation deadline for MEDIUM.
  3. Inventory token-bearing integrations — Find Deno services that send bearer tokens or API keys with fetch() to third-party APIs, especially any path influenced by user-supplied URLs or webhook targets. Those are the only systems that deserve priority inside the 365-day remediation window.
  4. Rotate exposed high-value tokens if suspicious redirects are found — If proxy, API gateway, or app telemetry shows unexpected cross-origin 30x behavior on authenticated calls, treat the credential as exposed and rotate it immediately. This is incident handling, not routine patch prioritization.
What doesn't work
  • MFA does not help because the leaked artifact is typically a bearer/API token used non-interactively
  • WAF rules do not meaningfully solve this because the vulnerable behavior happens in the application's outbound HTTP client, not at the inbound web edge
  • TLS everywhere is not sufficient because the problem is header forwarding across origins, not transport interception
06 · Verification

Crowdsourced verification payload.

Run this on the target host that has Deno installed. Invoke it as python3 verify_cve_2025_21620.py /path/to/deno or just python3 verify_cve_2025_21620.py if deno is on PATH; no elevated privileges are required, and the script only opens loopback ports 3001 and 3002 for a local functional test.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# verify_cve_2025_21620.py
# Functional verifier for CVE-2025-21620 (Deno fetch Authorization leak on cross-origin redirect)
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

import os
import re
import shutil
import subprocess
import sys
import tempfile

EXIT_PATCHED = 0
EXIT_VULNERABLE = 1
EXIT_UNKNOWN = 2


def out(msg):
    print(msg)


def find_deno(arg_path=None):
    if arg_path:
        return arg_path
    return shutil.which("deno")


def parse_version(text):
    m = re.search(r"deno\s+(\d+)\.(\d+)\.(\d+)", text)
    if not m:
        return None
    return tuple(int(x) for x in m.groups())


def semver_lt(a, b):
    return a < b


def version_based_fallback(ver):
    if ver is None:
        return None
    major, minor, patch = ver
    if major == 1:
        return "VULNERABLE" if semver_lt(ver, (1, 46, 4)) else "PATCHED"
    if major == 2:
        return "VULNERABLE" if semver_lt(ver, (2, 1, 2)) else "PATCHED"
    if major > 2:
        return "PATCHED"
    return None


def get_version(deno):
    try:
        cp = subprocess.run([deno, "--version"], capture_output=True, text=True, timeout=10)
        text = (cp.stdout or "") + "\n" + (cp.stderr or "")
        return parse_version(text), text.strip()
    except Exception:
        return None, ""


def functional_test(deno):
    js = r'''
const ac = new AbortController();

const server1 = Deno.serve({ hostname: "127.0.0.1", port: 3001, signal: ac.signal }, (_req) => {
  return new Response(null, {
    status: 302,
    headers: { "location": "http://127.0.0.1:3002/redirected" },
  });
});

const server2 = Deno.serve({ hostname: "127.0.0.1", port: 3002, signal: ac.signal }, (req) => {
  const body = JSON.stringify({
    url: req.url,
    hasAuth: req.headers.has("authorization"),
    authValue: req.headers.get("authorization") || "",
  });
  return new Response(body, {
    status: 200,
    headers: { "content-type": "application/json" },
  });
});

async function main() {
  await new Promise((r) => setTimeout(r, 300));
  try {
    const response = await fetch("http://127.0.0.1:3001/", {
      headers: { authorization: "Bearer noisgate-test-token" },
    });
    const body = await response.json();
    console.log(JSON.stringify(body));
  } finally {
    ac.abort();
    setTimeout(() => Deno.exit(0), 50);
  }
}

main();
'''

    with tempfile.TemporaryDirectory() as td:
        path = os.path.join(td, "cve_2025_21620_check.ts")
        with open(path, "w", encoding="utf-8") as f:
            f.write(js)
        try:
            cp = subprocess.run(
                [deno, "run", "--quiet", "--allow-net=127.0.0.1:3001,127.0.0.1:3002", path],
                capture_output=True,
                text=True,
                timeout=20,
            )
        except Exception as e:
            return None, f"functional test execution failed: {e}"

        stdout = (cp.stdout or "").strip()
        stderr = (cp.stderr or "").strip()
        if cp.returncode != 0 and not stdout:
            return None, f"functional test failed rc={cp.returncode} stderr={stderr}"

        m = re.search(r'\{.*"hasAuth"\s*:\s*(true|false).*\}', stdout, re.S)
        if not m:
            return None, f"unable to parse functional test output: {stdout or stderr}"

        leaked = m.group(1) == "true"
        return ("VULNERABLE" if leaked else "PATCHED"), stdout


def main():
    deno = find_deno(sys.argv[1] if len(sys.argv) > 1 else None)
    if not deno:
        out("UNKNOWN: deno binary not found")
        sys.exit(EXIT_UNKNOWN)

    ver, raw_ver = get_version(deno)
    func_result, detail = functional_test(deno)

    if func_result == "VULNERABLE":
        out(f"VULNERABLE: functional test reproduced Authorization header leak ({raw_ver})")
        sys.exit(EXIT_VULNERABLE)
    if func_result == "PATCHED":
        out(f"PATCHED: functional test did not leak Authorization header ({raw_ver})")
        sys.exit(EXIT_PATCHED)

    fallback = version_based_fallback(ver)
    if fallback == "VULNERABLE":
        out(f"VULNERABLE: functional test inconclusive, but installed version appears vulnerable ({raw_ver})")
        sys.exit(EXIT_VULNERABLE)
    if fallback == "PATCHED":
        out(f"PATCHED: functional test inconclusive, but installed version is at/above known fixed baseline ({raw_ver})")
        sys.exit(EXIT_PATCHED)

    out(f"UNKNOWN: could not complete functional verification; detail={detail}; version={raw_ver}")
    sys.exit(EXIT_UNKNOWN)


if __name__ == "__main__":
    main()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, do not panic-patch every host that happens to have Deno. First, inventory where Deno is actually used for authenticated outbound HTTP and flag the subset that can follow redirects to attacker-influenced destinations; those are your real candidates. Because this is a MEDIUM reassessment with no KEV or active exploitation evidence, there is noisgate mitigation SLA for this issue — no mitigation SLA — go straight to the 365-day remediation window — and your noisgate remediation SLA is to get affected runtimes onto the vendor-fixed line within 365 days as part of normal runtime/library maintenance, prioritizing internet-facing apps and token-brokering services first.

Sources

  1. GitHub Security Advisory GHSA-f27p-cmv8-xhm6
  2. NVD CVE-2025-21620
  3. OSV entry for CVE-2025-21620
  4. Deno security and permissions documentation
  5. Deno release v2.1.2
  6. CISA Known Exploited Vulnerabilities catalog
  7. CISA Vulnerability Summary for the Week of January 6, 2025
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.