This is someone scribbling fake entries into your guestbook, not picking the lock on the building
In morgan, the :remote-user token is populated from the HTTP Authorization: Basic ... header via the basic-auth package, and the current upstream source returns the decoded username directly into the log line without neutralizing control characters. Because the standard default, combined, common, and short formats all include :remote-user, an attacker who can send a request can base64-encode a username containing newline or other control characters and poison line-oriented logs. *Inference from public source code:* the latest public release is 1.10.1, and the current public source still shows the unsafe pattern, so the safest external assessment is that public releases through 1.10.1 are affected unless the operator has locally sanitized or removed :remote-user.
The vendor's MEDIUM 5.3 is defensible in a vacuum because the bug is remotely reachable and changes integrity, but it still overshoots enterprise patch urgency. This is not initial access, not auth bypass, and not code execution; it's audit-trail corruption whose real damage depends on whether you use a vulnerable log format, trust those logs operationally, and have downstream systems that treat a forged line as truth. For a 10,000-host program, that's *backlog hygiene with some SOC-facing compensations*, not a front-of-queue fire.
4 steps from start to impact.
Send a crafted Basic auth header
curl, Burp Repeater, or any HTTP client to send an Authorization: Basic ... header where the decoded username contains control characters such as \n, \r, or terminal escapes. The application does not need to use Basic auth for access control; the header only needs to reach the Node/Express process.- Network reachability to the Node/Express app
- Ability to set arbitrary HTTP headers
- Morgan enabled on the request path
- Some proxies, API gateways, or edge services strip or normalize
Authorizationheaders - If the app never logs the request path in question, the attack dies here
Decode attacker-controlled username
basic-auth(req), and basic-auth base64-decodes the credentials then splits on the first colon. The resulting credentials.name can contain non-printable characters because the parser does not neutralize them before returning the username.- The request includes syntactically valid Basic credentials
- Morgan's
:remote-usertoken is evaluated
- Malformed Basic headers are ignored
- A username cannot contain the delimiter colon, so the payload space is slightly constrained
Inject control characters into the log line
default, combined, common, and short formats all interpolate :remote-user into a single line. Because the value is not escaped, control characters can terminate the current line, create fake follow-on entries, or contaminate terminal output and naive log shippers.- Deployment uses a format containing
:remote-useror a custom format that includes it - Logs are written to stdout, files, or a collector that preserves the injected characters
- Teams using
devortinyare not hit by this specific token path - Structured logging pipelines that re-encode fields can blunt the effect
Authorization-driven usernames, malformed timestamps, or impossible client/user combinations.Mislead investigators or downstream automation
- Operators rely on raw Morgan access logs for forensic truth or alerting
- Downstream consumers do not validate record boundaries
- Most mature SIEM pipelines enrich from multiple sources, limiting blast radius
- The attacker still cannot alter application state beyond what a normal request already could
The supporting signals.
| In-the-wild status | No public exploitation evidence found in the sources reviewed, and it is not listed in the CISA KEV catalog. |
|---|---|
| Proof-of-concept availability | No dedicated public PoC repo located. Practical exploitation is trivial with curl/Burp by sending a crafted Basic username, so the *absence of a repo is not meaningful friction*. |
| EPSS | No public EPSS value was located in the reviewed sources for this CVE at time of assessment; treat that as missing data, not proof of safety. |
| KEV status | Not KEV-listed; no CISA due date applies. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N — the vendor score captures remote reachability and low integrity impact, but it does not capture the real-world limiter that this is only log integrity, not system compromise. |
| Affected versions | *Inference from public code and release history:* public upstream releases through 1.10.1 appear affected because the latest release and current public source still return credentials.name unsanitized. |
| Fixed versions | No public patched release or vendor advisory was found in the reviewed sources. No distro backport guidance located. |
| Exposure / scanning reality | This is an application-library issue, not a bannered network appliance bug. Shodan/Censys/FOFA-style external counting is largely *not applicable*; exposure must be measured from SBOMs, package-lock.json, and application inventories. |
| Disclosure / attribution | NVD had no record per the prompt, and no public upstream advisory with researcher credit was found. Public attribution is therefore unclear from the sources reviewed. |
noisgate verdict.
The decisive downgrade is that exploitation stops at log integrity manipulation; it does not create code execution, privilege gain, or authentication bypass. Even though the attacker position is unauthenticated remote, the blast radius is narrowed by a second-order dependency on logging format, log trust, and downstream parsing behavior.
Why this verdict
- Dropped from the 5.3 vendor baseline because the effect is post-request log poisoning, not attacker progression — no foothold expansion, no session theft, no code execution.
- Real deployment exposure is narrower than AV:N suggests — only apps running Morgan *and* using a format containing
:remote-userare affected;devandtinyare not hit by this exact path. - No active exploitation signal — no KEV listing, no public campaign reporting, and no public exploit ecosystem beyond trivial manual reproduction.
Why not higher?
An unauthenticated remote attacker can reach the vulnerable code path, but the consequence is forged log content, not direct system compromise. To get from 'poisoned access log' to 'material enterprise damage,' you need additional assumptions about analyst trust, downstream parser weakness, or brittle automation.
Why not lower?
This still matters because the unsafe token is present in several standard Morgan formats, and attackers do not need the app to actually authenticate them with Basic auth to influence :remote-user. If your access logs feed investigations, detections, or compliance evidence, forged lines are a real integrity problem.
What to do — in priority order.
- Remove
:remote-userfrom Morgan formats — If you cannot patch or upstream has not shipped a fix, switchdefault/combined/common/shortto a custom format that omits:remote-user. For aLOWverdict there is no SLA; treat this as backlog hygiene and make the change during normal application maintenance. - Sanitize log fields before write — Neutralize CR, LF, tabs, and ANSI/control bytes in any user-derived token before writing to stdout or files. For this
LOWissue, implement during normal logger hardening rather than emergency change windows. - Prefer structured logging — Move access telemetry into JSON with proper field encoding, then let the shipper serialize records instead of concatenating line-oriented strings. This is the best long-term control for CWE-117 style bugs and fits backlog-hygiene timing.
- Alert on control characters in access logs — Add a SIEM or log-pipeline rule to flag usernames or request fields containing non-printable bytes, line breaks, or escape sequences. There is no emergency deadline here, but it is a cheap detective control for shared Express logging patterns.
MFAdoes not help because the issue is not account takeover; the attacker only needs to send a crafted header.- A generic
WAFrarely helps much because the payload is hidden inside valid base64 Basic credentials and the dangerous behavior happens after decoding in the app. - Simply disabling Basic auth in the application does not reliably help if Morgan still parses a supplied
Authorizationheader and logs:remote-user.
Crowdsourced verification payload.
Run this on the application host, CI workspace, or source checkout that contains the Node project. Invoke it with python3 check_morgan_cve_2026_5078.py /path/to/app using any account that can read the project files; no admin rights are required.
#!/usr/bin/env python3
"""
check_morgan_cve_2026_5078.py
Detect likely exposure to CVE-2026-5078 in the Node.js `morgan` package.
Usage:
python3 check_morgan_cve_2026_5078.py /path/to/app
Exit codes:
0 = PATCHED / not affected
1 = VULNERABLE
2 = UNKNOWN / insufficient evidence
"""
import json
import os
import re
import sys
from typing import List, Tuple
FIXED_VERSION = None # No public fixed version was located during assessment
def parse_semver(v: str):
m = re.match(r'^(\d+)\.(\d+)\.(\d+)', v or '')
if not m:
return None
return tuple(int(x) for x in m.groups())
def find_morgan_paths(root: str) -> List[str]:
hits = []
for dirpath, dirnames, filenames in os.walk(root):
if os.path.basename(dirpath) == 'morgan':
pkg = os.path.join(dirpath, 'package.json')
idx = os.path.join(dirpath, 'index.js')
if os.path.isfile(pkg) and os.path.isfile(idx):
hits.append(dirpath)
return sorted(set(hits))
def load_pkg_version(pkg_json: str):
try:
with open(pkg_json, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('version')
except Exception:
return None
def source_looks_vulnerable(index_js: str) -> bool:
try:
with open(index_js, 'r', encoding='utf-8', errors='replace') as f:
src = f.read()
except Exception:
return False
token_present = "morgan.token('remote-user'" in src or 'morgan.token("remote-user"' in src
unsafe_return = 'return credentials ? credentials.name : undefined' in src
sanitize_markers = [
'replace(/\\r/g',
'replace(/\\n/g',
'replace(/[\\r\\n]/g',
'replace(/[\\x00-\\x1f\\x7f]/g',
'sanitize',
'escape'
]
sanitization_obvious = any(marker in src for marker in sanitize_markers)
return token_present and unsafe_return and not sanitization_obvious
def assess_one(path: str) -> Tuple[str, str]:
pkg = os.path.join(path, 'package.json')
idx = os.path.join(path, 'index.js')
version = load_pkg_version(pkg)
looks_vuln = source_looks_vulnerable(idx)
if looks_vuln:
return ('VULNERABLE', f'{path} version={version or "unknown"} source pattern matches unsanitized :remote-user token')
if version and FIXED_VERSION:
pv = parse_semver(version)
fv = parse_semver(FIXED_VERSION)
if pv and fv and pv >= fv:
return ('PATCHED', f'{path} version={version} >= fixed version {FIXED_VERSION}')
if version and not looks_vuln:
return ('UNKNOWN', f'{path} version={version} found, but source did not clearly match the known vulnerable pattern and no public fixed version is available')
return ('UNKNOWN', f'{path} found, but version/source inspection was inconclusive')
def main():
if len(sys.argv) != 2:
print('UNKNOWN - usage: python3 check_morgan_cve_2026_5078.py /path/to/app')
sys.exit(2)
root = os.path.abspath(sys.argv[1])
if not os.path.exists(root):
print(f'UNKNOWN - path does not exist: {root}')
sys.exit(2)
paths = find_morgan_paths(root)
if not paths:
print('PATCHED - morgan package not found under the supplied path')
sys.exit(0)
results = [assess_one(p) for p in paths]
for status, detail in results:
print(f'{status} - {detail}')
statuses = [s for s, _ in results]
if 'VULNERABLE' in statuses:
print('VULNERABLE')
sys.exit(1)
if all(s == 'PATCHED' for s in statuses):
print('PATCHED')
sys.exit(0)
print('UNKNOWN')
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
LOW noisgate rating there is no noisgate mitigation SLA and no noisgate remediation SLA — treat it as backlog hygiene: identify apps using morgan with :remote-user, strip or sanitize that token in normal change windows, and then fold any eventual upstream fix into your standard dependency-update cadence rather than emergency patching.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.