This is a loaded nail gun left on a workbench, not an artillery shell landing on your internet edge
CVE-2026-28792 is a browser-driven attack chain in @tinacms/cli where Tina's local dev server exposes permissive CORS and can be paired with path traversal to let a malicious website read, write, and delete files on the developer machine while tinacms dev is running. Authoritative descriptions from NVD and the GitHub advisory say the issue affects versions *before* 2.1.8, with the default dev server running on http://localhost:4001; the advisory page's affected-version widget also shows <= 2.1.15, which conflicts with its own narrative and patched-version field, so treat 2.1.8 as the vendor-intended fix unless Tina corrects the record.
paragraphs
5 steps from start to impact.
Land on a developer browser
fetch() from the malicious page, exactly as shown in the vendor advisory PoC.- A user is actively using a browser
- The user can be induced to visit attacker-controlled content
- The affected user is a TinaCMS developer
- This is *user-interaction driven* rather than self-propagating
- Only the subset of endpoints used by Tina developers are relevant
- Email security, web filtering, browser isolation, and ad blocking cut the reachable population
Catch tinacms dev while it is running
localhost:4001, the documented default port. The advisory explicitly states the attack does *not* require internet exposure, 0.0.0.0 binding, or external reachability.- The developer is running
tinacms devat that moment - The host has a vulnerable
@tinacms/cliversion installed
- The vulnerable service is usually ephemeral, not always-on
- Most enterprise endpoints will never run Tina at all
- Even among developers, many will not be in an active Tina session when they browse
4001, but external ASM/attack-surface tools will under-report exposure because localhost-only instances are invisible to internet scans.Use browser fetch() plus permissive CORS
- The vulnerable routes return permissive CORS headers
- The victim browser permits the script to run
- Browser security still blocks some follow-on actions unless the local app explicitly allows them
- Corporate browser controls, script blocking, or remote browser isolation can break the chain
localhost:4001, but signature coverage is immature and noisy.Traverse out of the media path
/media/list/, /media/upload/, and delete operations under /media/. The advisory PoC shows file-system enumeration via traversal, then exfiltration of the results back to the attacker.- Traversal payloads are accepted by the vulnerable endpoints
- The local file system contains accessible targets of value
- Impact depends heavily on what is present on that one workstation
- Some developer images keep secrets in external vaults instead of local files
- OS file permissions still constrain what the Node process can access
Steal secrets or poison source
.env files, tokens, SSH material, or modify project files and build scripts for later supply-chain abuse. The most serious real-world outcome is compromise of developer credentials or source integrity, not mass wormable takeover of the enterprise.- Sensitive files or writable source trees are present on the endpoint
- The victim has privileges to read or modify them
- Blast radius is usually one developer workstation or one repo checkout
- Follow-on organization-wide impact requires additional steps such as credential replay, code commit, or CI abuse
- EDR, Git protections, code review, and secrets rotation can contain downstream damage
The supporting signals.
| In-the-wild status | No evidence found of *active exploitation* as of 2026-06-02; the CVE is not present in CISA KEV. |
|---|---|
| Proof of concept | A working browser PoC is published in the GitHub advisory using fetch('http://localhost:4001/../../../etc/passwd') plus exfiltration from an attacker page. |
| EPSS | User-supplied EPSS is 0.00484 (~0.484%), which is low and consistent with a niche, user-driven exploit chain rather than high-volume opportunistic exploitation. |
| KEV status | Not KEV-listed; the public CISA catalog did not show this CVE when checked on 2026-06-02. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H — technically severe because the browser becomes a cross-scope bridge into localhost, but the decisive limiter is UI:R plus the need for an active dev session. |
| Affected versions | NVD CPE and advisory narrative indicate @tinacms/cli versions < 2.1.8 are affected. The advisory widget showing <= 2.1.15 appears inconsistent with the same page's patched version field. |
| Fixed version | Vendor-fixed version is 2.1.8. I found no distro backport advisories; this looks like a straight npm package upgrade case. |
| Exposure and scanning reality | This is *not an internet-exposure story*. The vendor explicitly says exploitation works even when the dev server is localhost-only, which means Shodan/Censys-style ASM is the wrong lens and will understate risk on developer workstations. |
| Disclosure date | Published 2026-03-12 by GitHub CNA; NVD enrichment followed on 2026-03-13. |
| Reporter | The GitHub advisory credits @alaeddine03. |
noisgate verdict.
The single biggest downward pressure is that exploitation only exists while a *developer workstation* is running tinacms dev and the user is lured into attacker-controlled web content. That sharply limits exposed population and turns a scary file-write primitive into a niche, workstation-scoped chain rather than a remotely reachable enterprise-wide critical event.
Why this verdict
- Downgraded for attacker position: the attacker is nominally remote, but the exploit still *implies successful delivery to a developer browser* and an active local dev session. That is materially narrower than a pre-auth internet-facing service flaw.
- Downgraded for exposed population: most enterprises do not run TinaCMS at scale across endpoints, and even in shops that do, only a fraction of developers will have
tinacms devlistening onlocalhost:4001at any given time. - Held above LOW for blast radius on the wrong endpoint: compromise of a developer workstation can expose secrets, poison source, and create supply-chain follow-on risk. The impact per victim is real even if the reachable population is small.
Why not higher?
This is not a wormable edge-service bug and not a broadly reachable production-server issue. The chain needs multiple compounding prerequisites: a Tina developer, a vulnerable CLI version, an active dev session, and successful user interaction with malicious browser content.
Why not lower?
If you *do* have Tina developers, the affected endpoint class is high-value: source trees, package tokens, cloud creds, and CI paths often live there. Arbitrary file read/write/delete on a developer box is too potent to dismiss as hygiene-only backlog.
What to do — in priority order.
- Inventory Tina developer endpoints — Find repos and workstations with
@tinacms/cliinstalled so you are acting on the real population, not your whole fleet. For a MEDIUM verdict there is no mitigation SLA — go straight to the 365-day remediation window, but do this discovery now so the package does not disappear into dev-tool blind spots. - Move secrets off developer disks — Use short-lived cloud credentials, secret managers, and minimal local
.envstorage so that a localhost file-read becomes less valuable. There is no mitigation SLA for this severity, but it is the best structural control to reduce impact before the patch lands within the remediation window. - Use isolated dev environments — Run Tina in disposable dev containers, VMs, or tightly managed browser/dev profiles where practical, so a malicious webpage hits a sandbox instead of a privileged daily-driver workstation. Treat this as a compensating control while you remediate within 365 days.
- Harden developer browsing — Apply ad/script filtering, remote browser isolation for risky browsing, and EDR coverage on developer endpoints to reduce the user-interaction leg of the chain. Again, no mitigation SLA applies here, but this lowers exploitability immediately without pretending perimeter controls solve a localhost issue.
- Shut dev servers when idle — Train teams and wrapper scripts to stop
tinacms devwhen they are not actively editing. That directly removes the vulnerable listening window and is worth doing until the package is upgraded inside the 365-day remediation window.
- A perimeter WAF does not help because the exploit targets
localhoston the developer machine, not your public edge. - Binding Tina to
127.0.0.1only does not fix it; the advisory explicitly says localhost-only is still exploitable from the browser. - External ASM/Shodan/Censys visibility is the wrong control because this issue can be fully exploitable with zero internet exposure.
- VPN, NGFW, or inbound ACL thinking does little here; the traffic path is the user's browser reaching its own local service.
Crowdsourced verification payload.
Run this on the developer workstation or in a repo checkout that may use Tina. Invoke it with python3 check_tina_cve_2026_28792.py /path/to/project or omit the path to scan the current directory; no admin privileges are required. It checks local node_modules and common lock/package files for @tinacms/cli and reports VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# check_tina_cve_2026_28792.py
# Detect vulnerable @tinacms/cli versions for CVE-2026-28792.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN, 3=SCRIPT_ERROR
import json
import os
import re
import sys
from pathlib import Path
FIXED = (2, 1, 8)
PKG = '@tinacms/cli'
def parse_semver(version):
if not version:
return None
v = version.strip()
v = v.lstrip('^~<>=v ')
m = re.match(r'^(\d+)\.(\d+)\.(\d+)', v)
if not m:
return None
return tuple(int(x) for x in m.groups())
def cmp_ver(a, b):
return (a > b) - (a < b)
def load_json(path):
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return None
def find_in_package_json(root):
path = root / 'package.json'
data = load_json(path)
if not data:
return None, None
for section in ('dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'):
deps = data.get(section, {})
if isinstance(deps, dict) and PKG in deps:
return str(path), deps[PKG]
return None, None
def find_in_package_lock(root):
path = root / 'package-lock.json'
data = load_json(path)
if not data:
return None, None
packages = data.get('packages', {})
if isinstance(packages, dict):
node = packages.get('node_modules/@tinacms/cli')
if isinstance(node, dict) and node.get('version'):
return str(path), node.get('version')
deps = data.get('dependencies', {})
node = deps.get('@tinacms/cli') if isinstance(deps, dict) else None
if isinstance(node, dict) and node.get('version'):
return str(path), node.get('version')
return None, None
def find_in_node_modules(root):
path = root / 'node_modules' / '@tinacms' / 'cli' / 'package.json'
data = load_json(path)
if data and data.get('version'):
return str(path), data.get('version')
return None, None
def walk_roots(start):
roots = [start]
if (start / 'packages').is_dir():
for p in (start / 'packages').iterdir():
if p.is_dir():
roots.append(p)
return roots
def evaluate(found):
parsed = []
for source, version in found:
sv = parse_semver(version)
parsed.append((source, version, sv))
installed = [x for x in parsed if x[2] is not None]
if not installed:
return 'UNKNOWN', 'No parseable @tinacms/cli version found.'
vuln = [x for x in installed if cmp_ver(x[2], FIXED) < 0]
if vuln:
details = '; '.join([f'{src} -> {ver}' for src, ver, _ in vuln])
return 'VULNERABLE', details
patched = [x for x in installed if cmp_ver(x[2], FIXED) >= 0]
details = '; '.join([f'{src} -> {ver}' for src, ver, _ in patched])
return 'PATCHED', details
def main():
try:
start = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path.cwd()
if not start.exists():
print('UNKNOWN - path does not exist')
sys.exit(2)
found = []
seen = set()
for root in walk_roots(start):
for finder in (find_in_node_modules, find_in_package_lock, find_in_package_json):
src, ver = finder(root)
if src and (src, ver) not in seen:
found.append((src, ver))
seen.add((src, ver))
status, details = evaluate(found)
print(f'{status} - CVE-2026-28792 check for {PKG}; fixed version is 2.1.8; {details}')
if status == 'PATCHED':
sys.exit(0)
elif status == 'VULNERABLE':
sys.exit(1)
else:
sys.exit(2)
except Exception as e:
print(f'UNKNOWN - script error: {e}')
sys.exit(3)
if __name__ == '__main__':
main()
If you remember one thing.
@tinacms/cli 2.1.8 or later within the noisgate remediation SLA of ≤ 365 days, and in the meantime reduce exposure by limiting secret storage on dev boxes, stopping tinacms dev when idle, and isolating risky browsing on affected developer systems.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.