This is a burglar using an unlocked door inside the building, not kicking in the front gate
CVE-2026-44495 is an Axios *prototype-pollution gadget*, not a standalone entry point. In affected versions >=1.0.0,<1.15.2 and >=0.19.0,<0.31.1, Axios reads configuration through normal property access during mergeConfig(), so a pre-polluted Object.prototype.transformResponse can be treated as request config and later executed during response handling. In the full exploitation path, that function can read request URL, headers, and auth credentials and can alter the response seen by application code.
The vendor's HIGH 7.0 score is technically defensible in a vacuum but too generous for enterprise patch triage. The decisive friction is that Axios does not create the pollution itself; the attacker first needs a separate prototype-pollution bug or equivalent ability to control Object.prototype in the same runtime, and many real-world PP primitives only write JSON-like values rather than executable functions. That turns this from a broad unauthenticated remote event into a chained, context-dependent post-bug amplifier.
4 steps from start to impact.
Land a helper bug that pollutes Object.prototype
qs, lodash, or similar gadget feeder named by the advisory. Without that upstream foothold, Axios is inert here. This is the single biggest severity reducer.- A reachable prototype-pollution vulnerability elsewhere in the app or dependency tree
- Attacker input reaches that vulnerable component before Axios builds a request
- Axios runs in the same JS runtime as the polluted object graph
- Requires a second vulnerability or equivalent code execution/control path first
- Many PP bugs only allow JSON-like values, not function objects
- Modern SAST/SCA often flags common PP feeder libraries before this chain is viable
Seed the Axios gadget with transformResponse
transformResponse value where Axios will inherit it during config merge. In fully affected versions, Axios can replace its default response transform with the polluted function and execute it with the request config bound as this. That exposes headers, URL, and any auth object passed to Axios.- Affected Axios version
- Polluted key is relevant to Axios config, especially
transformResponse - Application does not override that key with a safe own property
- If the PP primitive cannot inject a function, full credential theft usually collapses into request failure/DoS
- Some app patterns always set explicit transforms, narrowing reachable cases
- Null-prototype wrappers or defensive cloning in app code can break inheritance
Trigger an Axios request and harvest secrets
transformData() executes the polluted transform, it can read credentials from this.auth, inspect headers and response data, and return tampered content to the caller. The GHSA PoC shows both credential capture and response hijack behavior.- The app actually uses Axios in the compromised process
- Target requests carry useful secrets or trusted downstream data
- The request path reaches response transformation
- Apps that avoid inline basic auth and keep secrets out of Axios config reduce impact
- Short-lived tokens and service-mesh identity shrink credential value
- Some requests may carry no sensitive headers or auth at all
Abuse trust in the modified response
- Application logic trusts Axios response content without secondary validation
- Attacker-controlled transform returns a value compatible with call-site expectations
- Tampering is constrained by app behavior and type expectations
- This does not directly yield OS-level RCE by itself
- Blast radius stays inside the already-compromised app/runtime
auth objects and long-lived API credentials in Axios request config where feasible; prefer short-lived tokens from workload identity or service mesh auth. That does not fix the bug, but it strips value from the data the polluted transform can read; for MEDIUM, there is no mitigation SLA and this is best treated as architecture hardening while patching proceeds.package-lock.json, pnpm-lock.yaml, yarn.lock, and container image scans to find transitive Axios usage across services rather than relying on asset scanners. This matters because library exposure is invisible on the network; for MEDIUM, there is no mitigation SLA — go straight to remediation planning within 365 days.- A WAF does not solve this by itself; the exploit depends on an in-process JavaScript object state, not just a recognisable HTTP signature.
- Network vuln scanning does not meaningfully detect this; Axios is a package inside apps and containers, not a listening service with a banner.
- MFA is irrelevant here; there is no login boundary in the Axios gadget path.
- Blocking only outbound traffic is incomplete; it may stop exfiltration to the internet, but it does not stop response tampering or theft of internal service credentials used in-process.
The supporting signals.
| In-the-wild status | No public evidence of active exploitation found in the reviewed sources, and not listed in CISA KEV at review time. |
|---|---|
| Proof-of-concept availability | Public PoC is embedded in the GitHub advisory and mirrored in OSV; weaponization effort is low once the helper prototype-pollution foothold already exists. |
| EPSS | No EPSS value was surfaced in the GitHub advisory at review time; treat exploit-probability telemetry as not yet mature / not surfaced for this newly published CVE. |
| KEV status | No; no KEV entry found for CVE-2026-44495 as of 2026-06-03. |
| CVSS vector and what it means | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:L correctly captures the chain complexity better than the advisory's internal 9.4 thought experiment. The important bit is AC:H: exploitation depends on a separate upstream condition. |
| Affected versions | axios >=1.0.0,<1.15.2 and >=0.19.0,<0.31.1 per GHSA. |
| Fixed versions | Upgrade to 1.15.2+ on the v1 line or 0.31.1+ on the v0 line. The release notes say the fix hardens resolveConfig/mergeConfig/validator paths to read only own properties and use null-prototype config objects. |
| Exposure / scanning reality | This is a library issue, not an internet-facing daemon, so Shodan/Censys/FOFA/GreyNoise are poor prioritization inputs here. Your real exposure comes from SBOMs, lockfiles, container layers, and node_modules across CI artifacts and app images. |
| Disclosure date | 2026-05-29 in the GitHub Advisory Database. |
| Reporter / source | Published by jasonsaayman to axios/axios; the advisory timeline says discovery occurred during a source code audit on 2026-04-15. |
Sources.
Why this verdict
- Downward pressure: requires a prior bug — the attacker needs a separate prototype-pollution foothold or equivalent runtime control before Axios is even in play.
- Downward pressure: same-process requirement — this is not internet-reachable by itself; the helper bug and Axios must coexist in the same application runtime.
- Downward pressure: many PP primitives are data-only — if the feeder bug can only set JSON-like values, impact often degrades to request breakage or DoS instead of credential theft.
- Upward pressure: wide deployment — Axios is everywhere, so once the chain is viable the reachable population inside your software estate can be large.
- Upward pressure: high-value data exposure — when exploited successfully, request URLs, headers, and
authcredentials can be exposed across normal application traffic.
Why not higher?
Because this is fundamentally a chained issue. It assumes either another exploitable vulnerability or equivalent attacker control exists first, and that control must exist in the same JavaScript runtime before Axios merges config. That sharply narrows exploitability compared with true unauthenticated remote RCE/SSRF cases.
Why not lower?
Because once the chain is satisfied, the impact is not cosmetic. The advisory PoC shows access to request credentials and response data, and Axios is common enough that a single vulnerable package can sit inside many critical services. For exposed Node services with rich service-to-service credentials, the blast radius can be meaningful even if the entry condition is constrained.
Crowdsourced verification payload.
Run this on a build worker, source checkout, or application host that has the project files available. Invoke it with python3 check_axios_cve_2026_44495.py /path/to/app and no elevated privileges are required; it inspects package-lock.json, npm-shrinkwrap.json, and node_modules/axios/package.json and prints VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# check_axios_cve_2026_44495.py
# Detect vulnerable axios versions for CVE-2026-44495.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=usage/error
import json
import os
import sys
from typing import List, Optional, Tuple
AFFECTED_RANGES = [
((1, 0, 0), (1, 15, 2)), # >=1.0.0, <1.15.2
((0, 19, 0), (0, 31, 1)), # >=0.19.0, <0.31.1
]
def parse_ver(v: str) -> Optional[Tuple[int, int, int]]:
if not v:
return None
v = v.strip()
if v.startswith('v'):
v = v[1:]
core = v.split('-')[0].split('+')[0]
parts = core.split('.')
nums = []
for p in parts:
digits = ''
for ch in p:
if ch.isdigit():
digits += ch
else:
break
if digits == '':
nums.append(0)
else:
nums.append(int(digits))
while len(nums) < 3:
nums.append(0)
return tuple(nums[:3])
def is_affected(v: str) -> Optional[bool]:
pv = parse_ver(v)
if pv is None:
return None
for low, high in AFFECTED_RANGES:
if pv >= low and pv < high:
return True
return False
def walk_deps(dep_obj, found: List[Tuple[str, str, str]], source: str, path: str = ''):
if not isinstance(dep_obj, dict):
return
for name, meta in dep_obj.items():
if not isinstance(meta, dict):
continue
ver = meta.get('version')
current_path = f"{path}>{name}" if path else name
if name == 'axios' and isinstance(ver, str):
found.append((source, current_path, ver))
nested = meta.get('dependencies')
if isinstance(nested, dict):
walk_deps(nested, found, source, current_path)
def inspect_package_lock(lock_path: str, found: List[Tuple[str, str, str]]):
try:
with open(lock_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except Exception:
return
# npm lockfile v2/v3
packages = data.get('packages')
if isinstance(packages, dict):
for pkg_path, meta in packages.items():
if not isinstance(meta, dict):
continue
if pkg_path.endswith('node_modules/axios'):
ver = meta.get('version')
if isinstance(ver, str):
found.append((lock_path, pkg_path, ver))
# npm lockfile v1 style
deps = data.get('dependencies')
if isinstance(deps, dict):
walk_deps(deps, found, lock_path)
def inspect_node_modules(base: str, found: List[Tuple[str, str, str]]):
pkg_json = os.path.join(base, 'node_modules', 'axios', 'package.json')
if not os.path.isfile(pkg_json):
return
try:
with open(pkg_json, 'r', encoding='utf-8') as f:
data = json.load(f)
ver = data.get('version')
if isinstance(ver, str):
found.append((pkg_json, 'node_modules/axios', ver))
except Exception:
return
def main():
if len(sys.argv) != 2:
print('UNKNOWN - usage: python3 check_axios_cve_2026_44495.py /path/to/app')
sys.exit(3)
target = os.path.abspath(sys.argv[1])
if not os.path.exists(target):
print(f'UNKNOWN - path does not exist: {target}')
sys.exit(3)
found: List[Tuple[str, str, str]] = []
if os.path.isfile(target):
name = os.path.basename(target)
if name in ('package-lock.json', 'npm-shrinkwrap.json'):
inspect_package_lock(target, found)
else:
print('UNKNOWN - provide an app directory or package-lock.json/npm-shrinkwrap.json')
sys.exit(2)
else:
for root, dirs, files in os.walk(target):
# keep traversal bounded-ish
if '.git' in dirs:
dirs.remove('.git')
if name := next((x for x in ('package-lock.json', 'npm-shrinkwrap.json') if x in files), None):
inspect_package_lock(os.path.join(root, name), found)
inspect_node_modules(root, found)
# de-duplicate
uniq = []
seen = set()
for item in found:
if item not in seen:
uniq.append(item)
seen.add(item)
if not uniq:
print('UNKNOWN - no axios installation or supported npm lockfile evidence found')
sys.exit(2)
vulnerable = []
patched = []
unknown = []
for source, dep_path, ver in uniq:
affected = is_affected(ver)
rec = f'{ver} [{dep_path}] ({source})'
if affected is True:
vulnerable.append(rec)
elif affected is False:
patched.append(rec)
else:
unknown.append(rec)
if vulnerable:
print('VULNERABLE - CVE-2026-44495 affected axios versions detected:')
for r in vulnerable:
print(f' - {r}')
if patched:
print('Also found patched versions:')
for r in patched:
print(f' - {r}')
sys.exit(1)
if patched and not unknown:
print('PATCHED - only fixed axios versions detected:')
for r in patched:
print(f' - {r}')
sys.exit(0)
print('UNKNOWN - no vulnerable versions detected, but some evidence was inconclusive:')
for r in patched:
print(f' - patched: {r}')
for r in unknown:
print(f' - unknown: {r}')
sys.exit(2)
if __name__ == '__main__':
main()