← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2026-28792 · CWE-22 · Disclosed 2026-03-12

Tina is a headless content management system

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

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

"Dangerous on a developer laptop, but too narrow and user-driven to deserve a fleet-wide critical label"
02 · The Attack Path

5 steps from start to impact.

STEP 01

Land on a developer browser

The attacker does not hit a public TinaCMS server first; they need a developer to browse to an attacker-controlled page, ad, or compromised site while working. The weapon here is ordinary browser JavaScript using fetch() from the malicious page, exactly as shown in the vendor advisory PoC.
Conditions required:
  • A user is actively using a browser
  • The user can be induced to visit attacker-controlled content
  • The affected user is a TinaCMS developer
Where this breaks in practice:
  • 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
Detection/coverage: Network vulnerability scanners will usually miss this because the trigger is client-side browsing behavior, not a remotely exposed internet service.
STEP 02

Catch tinacms dev while it is running

The malicious page then targets the local Tina dev server on 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.
Conditions required:
  • The developer is running tinacms dev at that moment
  • The host has a vulnerable @tinacms/cli version installed
Where this breaks in practice:
  • 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
Detection/coverage: EDR or local telemetry may see a listening Node process on 4001, but external ASM/attack-surface tools will under-report exposure because localhost-only instances are invisible to internet scans.
STEP 03

Use browser fetch() plus permissive CORS

Because the dev server returns permissive CORS headers, browser JavaScript from an untrusted origin can read responses from the local service. That turns what would otherwise be a same-origin blocked localhost probe into a readable cross-origin channel.
Conditions required:
  • The vulnerable routes return permissive CORS headers
  • The victim browser permits the script to run
Where this breaks in practice:
  • 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
Detection/coverage: Browser telemetry and proxy logs may show suspicious cross-origin requests to localhost:4001, but signature coverage is immature and noisy.
STEP 04

Traverse out of the media path

The attacker weaponizes the existing traversal flaw against Tina media endpoints such as /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.
Conditions required:
  • Traversal payloads are accepted by the vulnerable endpoints
  • The local file system contains accessible targets of value
Where this breaks in practice:
  • 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
Detection/coverage: SAST/SCA can flag the vulnerable package version; runtime NDR coverage is poor because traffic stays local until exfiltration.
STEP 05

Steal secrets or poison source

From there the attacker can enumerate directories, locate .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.
Conditions required:
  • Sensitive files or writable source trees are present on the endpoint
  • The victim has privileges to read or modify them
Where this breaks in practice:
  • 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
Detection/coverage: Look for unusual reads of developer secrets, new or modified build scripts, suspicious local-to-remote exfil, and anomalous Git activity after browser events.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo evidence found of *active exploitation* as of 2026-06-02; the CVE is not present in CISA KEV.
Proof of conceptA working browser PoC is published in the GitHub advisory using fetch('http://localhost:4001/../../../etc/passwd') plus exfiltration from an attacker page.
EPSSUser-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 statusNot KEV-listed; the public CISA catalog did not show this CVE when checked on 2026-06-02.
CVSS vectorCVSS: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 versionsNVD 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 versionVendor-fixed version is 2.1.8. I found no distro backport advisories; this looks like a straight npm package upgrade case.
Exposure and scanning realityThis 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 datePublished 2026-03-12 by GitHub CNA; NVD enrichment followed on 2026-03-13.
ReporterThe GitHub advisory credits @alaeddine03.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (6.4/10)

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.

HIGH Exploit prerequisites and attack-chain friction
MEDIUM Vendor version-range consistency, because the advisory page contains conflicting affected-version cues

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 dev listening on localhost:4001 at 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.

05 · Compensating Control

What to do — in priority order.

  1. Inventory Tina developer endpoints — Find repos and workstations with @tinacms/cli installed 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.
  2. Move secrets off developer disks — Use short-lived cloud credentials, secret managers, and minimal local .env storage 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.
  3. 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.
  4. 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.
  5. Shut dev servers when idle — Train teams and wrapper scripts to stop tinacms dev when 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.
What doesn't work
  • A perimeter WAF does not help because the exploit targets localhost on the developer machine, not your public edge.
  • Binding Tina to 127.0.0.1 only 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.
06 · Verification

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.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/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()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, do not treat this like a fleet-wide internet critical; treat it like a *developer-workstation exposure* and first identify whether you even run Tina locally anywhere. Because the reassessed verdict is MEDIUM, there is noisgate mitigation SLA here — no mitigation SLA — go straight to the 365-day remediation window — but if Tina is present on developer endpoints, apply the package upgrade to @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

  1. NVD CVE-2026-28792
  2. GitHub advisory GHSA-8pw3-9m7f-q734
  3. TinaCMS CLI documentation
  4. CISA Known Exploited Vulnerabilities Catalog
  5. FIRST EPSS API documentation
  6. FIRST EPSS overview
  7. TinaCMS GitHub repository
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.