← Back to Feed CACHED · 2026-10-14 14:44:30 · CACHE_KEY CVE-2026-108976
CVE-2026-108976 · CWE-184 · Disclosed 2026-10-11

GNU Emacs TRAMP OS Command Injection via Incomplete tramp-user-regexp Blocklist

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

Someone slipped a razor blade into a library book, but only Emacs readers can cut themselves

CVE-2026-108976 is an OS command injection in GNU Emacs's TRAMP subsystem (Transparent Remote Access, Multiple Protocols). TRAMP parses specially formatted file paths like /ssh:user@host:/path to handle remote files. The tramp-user-regexp regex is supposed to block shell metacharacters in the username field, but the blocklist is incomplete — leftover characters from an earlier incomplete fix (CVE-2026-79992) still pass through. When Emacs performs routine operations like file-exists-p on a crafted path, the unsanitized username field gets concatenated into a shell command and executed locally. Affected: GNU Emacs < 31.2 and TRAMP ≤ 2.8.2. The flaw was found by Wonyoung Jung of 78ResearchLab using the PatchHawk automated patch-analysis tool; TRAMP maintainer Michael Albinus shipped the second repair.

The vendor rates this HIGH at CVSS 7.8, and on paper the vector (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) looks dangerous — low-privilege local attacker, no interaction needed, full CIA impact. But the score ignores three compounding realities: (1) the attacker already has a local shell to plant the crafted filename, so they can already execute commands at the same privilege level; (2) Emacs is installed on a single-digit percentage of developer workstations and virtually zero servers in most enterprise fleets; (3) code execution happens in the user-space Emacs process — there is no privilege escalation. The 7.8 treats this like a reliable local privesc on a ubiquitous OS component. It is neither.

"Local Emacs command injection is scary-sounding but the attacker already has a shell"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Establish local presence

The attacker needs a local account on the target host or write access to a filesystem the victim's Emacs will browse (e.g., a shared NFS/CIFS mount, a Git repo checkout). The CVSS vector is AV:L — this is not remotely exploitable. The attacker must already be past initial access.
Conditions required:
  • Local account on target host OR write access to a shared/mounted filesystem visible to the victim
Where this breaks in practice:
  • Attacker must already have compromised the host or an adjacent system with shared storage
  • Enterprise workstations with Emacs installed are a small fraction of the fleet
STEP 02

Plant a crafted filename

The attacker creates a file whose name is a valid TRAMP path with shell metacharacters in the user field — e.g., a filename like /ssh:$(id)@evil:/x. The characters that bypass the tramp-user-regexp filter are precisely the ones omitted from the CVE-2026-79992 fix. No special tooling required; touch or mkdir suffices.
Conditions required:
  • Write access to a directory the victim will browse in Emacs
  • Knowledge of which characters bypass the updated regex (trivially derivable from the patch diff)
Where this breaks in practice:
  • The filename must look plausible enough to appear in a browsed directory — obvious /ssh:$(cmd)@... paths stand out
  • On single-user workstations the attacker IS the user — there is no cross-user pivot
Detection/coverage: File integrity monitoring (AIDE, OSSEC, Wazurity) on shared directories would flag anomalous filenames
STEP 03

Victim's Emacs processes the path

When the victim opens a directory in dired, uses projectile, find-file completion, or any Lisp that calls file-exists-p / file-name-handler-alist on the crafted path, TRAMP's filename handler activates. The handler parses the path, extracts the user field, and — because the regex check is incomplete — passes the shell metacharacters through to a local shell invocation. No explicit user click on the malicious file is needed; directory listing can trigger it (hence UI:N).
Conditions required:
  • Emacs is running and loads TRAMP (loaded on demand when a TRAMP-style path is encountered)
  • Victim browses or programmatically lists a directory containing the crafted filename
Where this breaks in practice:
  • TRAMP lazy-loads; if the user never touches a TRAMP-format path, the handler never fires
  • Many Emacs users disable or restrict TRAMP via tramp-mode or file-name-handler-alist customizations
  • Org-mode / Doom / Spacemacs configs vary widely in auto-loading behavior
Detection/coverage: Process execution monitoring (auditd, EDR) would see an unexpected child process spawned by Emacs
STEP 04

Command execution as the Emacs user

The injected shell command runs with the UID of the Emacs process — typically the logged-in developer. There is no privilege escalation. The attacker gains the same access level the user already has. Impact is code execution, data access, and persistence within that user's session.
Conditions required:
  • Steps 1-3 completed successfully
Where this breaks in practice:
  • Execution is same-privilege — on a single-user dev workstation where the attacker planted the file, they already HAVE this access
  • EDR agents (CrowdStrike, SentinelOne, Defender for Endpoint) will flag anomalous child-process trees from Emacs
  • No SUID/capability escalation path documented
Detection/coverage: EDR behavioral detection for shell spawned by Emacs (e.g., emacs → /bin/sh -c $(cmd)); Sysmon on Linux (process creation with ParentImage containing emacs)
03 · Compensating Control

1
MEDIUM 5.5→IGNORE 0.0
SEVERITY REDUCED
Disable TRAMP globally in your Emacs config — Add (setq tramp-mode nil) to init.el or site-wide site-start.el. This completely prevents TRAMP's filename handler from activating, neutralizing the entire attack surface. No mitigation SLA applies for MEDIUM — go straight to the 365-day remediation window, but if you can push this config change via dotfile management (chezmoi, GNU Stow, Ansible) it's a 5-minute fix.
2
MEDIUM 5.5→LOW 2.5
SEVERITY REDUCED
Restrict file-name-handler-alist to known-safe handlers — Customize file-name-handler-alist to remove TRAMP entries or restrict TRAMP to explicitly allowlisted connection methods. This narrows the attack surface without fully disabling remote file access. Apply within the 365-day remediation window.
3
MEDIUM 5.5→MEDIUM 4.5
Monitor for anomalous Emacs child processes — Deploy an EDR rule or auditd/Sysmon-for-Linux rule that alerts on shell processes (/bin/sh, /bin/bash) spawned as children of Emacs. This doesn't prevent exploitation but ensures rapid detection. Any SOC-mature environment should already have parent-child anomaly detection.
4
MEDIUM 5.5→IGNORE 0.0
SEVERITY REDUCED
Patch Emacs to 31.2 or TRAMP to 2.8.3 — The definitive fix. Upgrade Emacs to 31.2+ or install TRAMP 2.8.3 from ELPA. For RHEL/Fedora, confirm the distro backport covers CVE-2026-108976 (not just 79992). Apply within the noisgate 365-day remediation SLA for MEDIUM.
What doesn't work
  • WAF / network IDS — This is a local vulnerability with no network component. Network-layer controls have zero visibility into local filename processing.
  • MFA / authentication hardening — The attacker already has a local account. Authentication controls don't gate file creation or Emacs filename handling.
  • Sandboxing Emacs in a container — Emacs in a container still executes the injected command *inside the container*. If the attacker's goal is to access data the Emacs user can see (source code, credentials in dotfiles), containerization doesn't help unless you also restrict filesystem mounts.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Not listed in CISA KEV. No GreyNoise or Shadowserver scanner activity. Local-only vector makes mass exploitation implausible.
Proof-of-concept availabilityNo public PoC found. Checked pocindex.io, GitHub (no repos named CVE-2026-108976), 78ResearchLab's PatchHawk-PoC repo. Wonyoung Jung disclosed the bypass to the maintainer responsibly; PatchHawk can *generate* PoCs but none published for this CVE. Trivial to reproduce from the patch diff — the regex change is a one-liner.
EPSS0.00505 (0.51%) — bottom quartile. The 30-day exploitation probability is near baseline noise, consistent with a local-only vuln in niche software.
CISA KEVNot listed as of 2026-10-14.
CVSS vector interpretationCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — Local access, low complexity, low privileges, no interaction, unchanged scope, full CIA. The 7.8 score is structurally correct *if* you ignore population and same-privilege context.
Affected versionsGNU Emacs < 31.2; TRAMP (standalone ELPA package) ≤ 2.8.2. Distro packages shipping Emacs 28.x, 29.x, 30.x are all affected unless backport patches are applied.
Fixed versionsEmacs 31.2. TRAMP 2.8.3 (ELPA). Red Hat issued RHSA-2026:80507 for RHEL 9 (covers CVE-2026-79992; CVE-2026-108976 backport status TBD). Fedora 43/44 updates shipped September 2026 for the first CVE; follow-up patch pending. Debian/Ubuntu backport status: not yet confirmed for the 108976 follow-up.
Scanning / exposureEmacs is a desktop application — there is no network listening surface. Shodan/Censys/FOFA are irrelevant. Fleet-level detection requires software inventory (SCCM, osquery, Tanium) checking emacs --version or dpkg -l emacs.
Disclosure timeline2026-08-21: CVE-2026-79992 disclosed (original TRAMP command injection). 2026-09-xx: Distro patches ship. 2026-10-11: CVE-2026-108976 disclosed as incomplete fix bypass. 2026-10-13: NVD published.
Researcher / creditWonyoung Jung, AI Research Team Lead at 78ResearchLab, discovered the bypass using PatchHawk. Fix supplied by Michael Albinus, TRAMP maintainer.

Sources.

  1. VulDB — CVE-2026-108976 Detail
  2. LinuxSecurity — Emacs Incomplete Fix Allows File Commands Execution Risk
  3. Strix.ai — CVE-2026-108976 Analysis
  4. Red Hat Advisory RHSA-2026:80507 (CVE-2026-79992)
  5. 78ResearchLab PatchHawk-PoC Repository
  6. PoC Index — CVE Search
  7. Fedora 43 Emacs Update (2026-6b1a89dfe6)
  8. The Hacker Wire — CVE-2026-108976 Command Injection
05 · The Call

Final Verdict
↓ DOWNGRADED to MEDIUM (5.5/10)

Why this verdict

  • Local-only, same-privilege execution. AV:L with PR:L means the attacker already has a shell on the box. The injected command runs as the Emacs user — the same privilege level the attacker already holds. This isn't privilege escalation; it's an alternative execution path the attacker doesn't need.
  • Tiny installed-base footprint. Emacs usage is ~8-9% among developers per Stack Overflow 2025 data, and far lower across full enterprise fleets. In a 10,000-host environment, expect 50-200 workstations with Emacs installed. Servers almost never have it. The blast radius denominator is minuscule.
  • Role multiplier: Emacs is not a high-value-role component. Emacs is a user-space text editor. It is not an identity provider, hypervisor, CI/CD engine, backup agent, network appliance, or database. In the rare case where Emacs batch mode runs in CI/CD (processing untrusted Org-mode or Lisp files from PRs), there is a theoretical supply-chain pivot — but this pattern exists in <0.1% of CI/CD pipelines, well below the 1% floor threshold. No high-value-role floor applies.
  • No PoC, no exploitation, rock-bottom EPSS. EPSS is 0.51%. No public PoC exists. No in-the-wild exploitation observed. Not on KEV. The bypass is trivially derivable from the patch diff, but nobody is building campaigns around local Emacs command injection.
  • UI:N is technically correct but practically misleading. While TRAMP's handler can fire from directory listing without an explicit file-open click, the user must still navigate Emacs into the directory containing the crafted filename. This is not zero-click in the way a network service auto-processing input is zero-click.

Why not higher?

The vendor's 7.8 HIGH would be appropriate if this were a privilege escalation in a ubiquitous OS component (think polkit, sudo, or a kernel module). It is none of those things. Emacs is an optional developer tool with single-digit adoption. The code execution lands at the same privilege level the attacker already holds. There is no scope change, no network reachability, and no documented path to domain or fleet compromise. Bumping to HIGH requires either a high-value-role floor (which doesn't apply) or active exploitation (which doesn't exist).

Why not lower?

Dropping below MEDIUM would undercount the legitimate cross-user scenario: on shared multi-user systems (university clusters, shared dev boxes, NFS-mounted home directories), one user can plant a filename that triggers code execution in another user's Emacs session — a real, if narrow, lateral-movement vector. The attack complexity is genuinely low once you have filesystem write access, and no user interaction beyond directory browsing is needed. That keeps it solidly in MEDIUM territory rather than LOW.

06 · Verification

Crowdsourced verification payload.

Run this on each target host where Emacs may be installed. Requires no special privileges — any user account works. Invoke: bash check_cve_2026_108976.sh

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_108976.sh
# Checks whether the local Emacs installation is vulnerable to CVE-2026-108976
# (TRAMP tramp-user-regexp incomplete input blocklist)
# Exit codes: 0 = PATCHED/NOT AFFECTED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

# Check if Emacs is installed
if ! command -v emacs &>/dev/null; then
  echo "PATCHED — Emacs is not installed on this host."
  exit 0
fi

EMACS_VERSION=$(emacs --version 2>/dev/null | head -1 | grep -oP '[0-9]+\.[0-9]+(\.[0-9]+)?')

if [ -z "$EMACS_VERSION" ]; then
  echo "UNKNOWN — Could not determine Emacs version."
  exit 2
fi

echo "Detected Emacs version: $EMACS_VERSION"

# Parse major.minor
MAJOR=$(echo "$EMACS_VERSION" | cut -d. -f1)
MINOR=$(echo "$EMACS_VERSION" | cut -d. -f2)

# Check if Emacs >= 31.2 (patched upstream)
if [ "$MAJOR" -gt 31 ]; then
  echo "PATCHED — Emacs $EMACS_VERSION is >= 31.2."
  exit 0
elif [ "$MAJOR" -eq 31 ] && [ "$MINOR" -ge 2 ]; then
  echo "PATCHED — Emacs $EMACS_VERSION is >= 31.2."
  exit 0
fi

# For versions < 31.2, check TRAMP version via Emacs Lisp
TRAMP_VERSION=$(emacs --batch --eval '(progn (require (quote tramp) nil t) (if (boundp (quote tramp-version)) (princ tramp-version) (princ "unknown")))' 2>/dev/null || echo "unknown")

echo "Detected TRAMP version: $TRAMP_VERSION"

if [ "$TRAMP_VERSION" = "unknown" ]; then
  # TRAMP not loadable or version not detectable — check distro patch
  # Attempt to check if distro backported the fix via changelog
  if command -v dpkg &>/dev/null; then
    if dpkg -s emacs-common 2>/dev/null | grep -qi 'CVE-2026-108976'; then
      echo "PATCHED — Distro package contains CVE-2026-108976 fix."
      exit 0
    fi
  elif command -v rpm &>/dev/null; then
    if rpm -q --changelog emacs-common 2>/dev/null | grep -qi 'CVE-2026-108976'; then
      echo "PATCHED — Distro package contains CVE-2026-108976 fix."
      exit 0
    fi
  fi
  echo "VULNERABLE — Emacs $EMACS_VERSION (< 31.2) with indeterminate TRAMP version. Assume vulnerable."
  exit 1
fi

# Compare TRAMP version — fixed in 2.8.3
TRAMP_MAJOR=$(echo "$TRAMP_VERSION" | cut -d. -f1)
TRAMP_MINOR=$(echo "$TRAMP_VERSION" | cut -d. -f2)
TRAMP_PATCH=$(echo "$TRAMP_VERSION" | cut -d. -f3)
TRAMP_PATCH=${TRAMP_PATCH:-0}

if [ "$TRAMP_MAJOR" -gt 2 ]; then
  echo "PATCHED — TRAMP $TRAMP_VERSION is >= 2.8.3."
  exit 0
elif [ "$TRAMP_MAJOR" -eq 2 ] && [ "$TRAMP_MINOR" -gt 8 ]; then
  echo "PATCHED — TRAMP $TRAMP_VERSION is >= 2.8.3."
  exit 0
elif [ "$TRAMP_MAJOR" -eq 2 ] && [ "$TRAMP_MINOR" -eq 8 ] && [ "$TRAMP_PATCH" -ge 3 ]; then
  echo "PATCHED — TRAMP $TRAMP_VERSION is >= 2.8.3."
  exit 0
fi

# Check distro-level backport
if command -v dpkg &>/dev/null; then
  if dpkg -s emacs-common 2>/dev/null | grep -qi 'CVE-2026-108976'; then
    echo "PATCHED — Distro package contains CVE-2026-108976 backport."
    exit 0
  fi
elif command -v rpm &>/dev/null; then
  if rpm -q --changelog emacs-common 2>/dev/null | grep -qi 'CVE-2026-108976'; then
    echo "PATCHED — Distro package contains CVE-2026-108976 backport."
    exit 0
  fi
fi

echo "VULNERABLE — Emacs $EMACS_VERSION with TRAMP $TRAMP_VERSION (< 2.8.3). CVE-2026-108976 applies."
exit 1