New GlassWorm Vector: Force-Pushing Malware via Stolen GitHub Tokens
Just saw the StepSecurity report on GlassWorm shifting gears. It seems the campaign has evolved beyond just the Open VSX marketplace abuse and is now actively using stolen GitHub Personal Access Tokens (PATs) to force-push malware directly into Python repositories.
The attackers are specifically targeting Python projects—everything from Django apps to ML research code and Streamlit dashboards. Their method involves appending obfuscated code to core files like setup.py, main.py, and app.py. This is particularly insidious because a simple pip install from a compromised repo could wreck your environment.
Since the code is obfuscated, standard linters might miss it during the pull request review if the repo is poorly monitored. We need to be watching for anomalous force-push events, as this is a primary indicator of this attack vector.
I've whipped up a quick script to scan for recent modifications to critical files in our internal repos:
import os
import subprocess
def check_force_push(target_dir):
os.chdir(target_dir)
# Check reflog for force-push indicators (rewritten history)
log_output = subprocess.check_output(['git', 'reflog', 'show', '--format=%gs'], stderr=subprocess.STDOUT).decode()
if 'force-update' in log_output or 'forced-update' in log_output:
print("[!] Potential force-push detected.")
check_force_push('.')
Given that they are going after ML research and dev environments, how is everyone handling token hygiene? Are we finally ready to ditch static PATs in favor of OIDC for CI/CD pipelines?
We've actually been rolling out GitHub Actions OIDC for our internal CI/CD specifically to prevent this. Storing long-lived PATs in organization secrets is a disaster waiting to happen. With OIDC, the token is short-lived and scoped only to the specific repo. It doesn't stop a compromised dev laptop entirely, but it significantly reduces the blast radius if a repo token is leaked via a supply chain attack.
From a SOC perspective, this is a nightmare to detect post-incident. If the attacker force-pushes, they wipe the commit history. We're setting up alerts for any git push --force events via GitHub Webhooks sent to our SIEM. For detection on the endpoint, you might want to look for base64 encoded strings appended to the end of Python imports, as that's how the obfuscation usually starts.
I manage a few Django repos for clients, and this is exactly why I enforce branch protection rules. Require pull requests for main, disable direct pushes, and—crucially—require status checks to pass before merging. Even if they steal a token, they can't bypass the PR reviews unless they have admin rights to change the rules. Security in depth is key here.
Solid advice so far. To add a layer of defense-in-depth specifically for the payload, consider implementing a pre-commit hook or CI check that scans for common obfuscation patterns in Python files. Since GlassWorm targets setup.py and main.py, detecting large base64 blobs or unsafe exec calls can stop the infection even if the token is compromised.
Here’s a quick bash check you can drop into your pipeline:
# Check for suspicious imports or encoded strings
if git diff --cached --name-only | grep -E '^(setup|main|app)\.py$' | xargs grep -lE "(import exec|base64|marshal\.loads)"; then
echo "Malware signature detected. Push blocked."
exit 1
fi
To build on the OIDC advice, enforce Fine-Grained Personal Access Tokens immediately. Unlike classic tokens, these allow you to restrict write access to specific repositories, drastically limiting the blast radius if a token is stolen.
You can proactively identify risky usage by scanning your audit log for classic tokens creating pushes:
gh api /orgs/YOUR_ORG/audit-log --jq '.[] | select(.action == "git.push") | {actor, token_type}'
This quickly highlights users who need to rotate to the more secure token type.
Building on the branch protection discussion, enabling Commit Signing Verification is a critical step. Even with a stolen PAT, the attacker can't push if the rule requires a valid GPG signature. Configure your repo to reject unsigned commits:
git config commit.gpgsign true
If the attacker force-pushes without the key, the protection rule blocks it. It effectively turns the code push into a two-factor problem: something you know (the PAT) and something you have (the signing key).
Verified Access Required
To maintain the integrity of our intelligence feeds, only verified partners and security professionals can post replies.
Request Access