Detect GlassWorm supply chain attack payloads - technique detection, not just blocklists
Project description
glassworm-hunter
Detect GlassWorm supply chain attack payloads on your machine. Scans VS Code extensions, npm packages, Python packages, and git repositories.
This scanner detects the attack technique itself - invisible Unicode variation selector payloads, GlassWorm decoder patterns, C2 indicators, and credential harvesting code.
Install
pip install glassworm-hunter
Or with pipx:
pipx install glassworm-hunter
Quick start
Scan your current directory without scanning VS Code/Cursor extensions:
glassworm-hunter scan --no-extensions
Scan a specific project:
glassworm-hunter scan /path/to/project
What it detects
Technique detection (catches unknown variants)
- Invisible Unicode payloads - variation selector characters (U+FE00-FE0F, U+E0100-E01EF) used to encode hidden code. Legitimate uses are 1-2 characters for emoji. GlassWorm uses thousands.
- GlassWorm decoder patterns - the codePointAt + variation selector range arithmetic that decodes invisible payloads
- Bidirectional override characters - Trojan Source attack (CVE-2021-42574)
- Hangul filler - invisible valid JavaScript identifiers (U+3164)
- eval/Function with dynamic content - execution sinks fed by decoded strings
- Credential access - code reading .npmrc, .gitcredentials, SSH keys, token env vars
- C2 communication patterns - Solana RPC calls, Google Calendar URLs, WebRTC data channels in unexpected contexts
Known IOC matching (supplementary)
- 21 known malicious VS Code/OpenVSX extension IDs (all 5 waves)
- 4 known malicious npm packages
- 14 known C2 IP addresses
- 3 known Solana C2 wallet addresses
- Attacker email and build path artifacts
Severity levels
| Level | Meaning |
|---|---|
| CRITICAL | Active GlassWorm payload detected (invisible Unicode cluster in code, decoder pattern) |
| HIGH | Known malicious IOC matched (C2 IP, wallet, extension dependency on known malware) |
| MEDIUM | Suspicious pattern worth reviewing (eval + dynamic content, credential access, Trojan Source) |
| LOW | Informational (unusual zero-width character density, suspicious install scripts) |
CLI options
glassworm-hunter scan [OPTIONS] [PATHS...]
| Option | Default | Description |
|---|---|---|
--extensions / --no-extensions |
on | Scan VS Code/Cursor/Codium extensions |
--npm-scan / --no-npm-scan |
on | Scan node_modules |
--pip-scan / --no-pip-scan |
off | Scan Python site-packages |
--git / --no-git |
on | Scan git repositories |
--format [console|json|sarif] |
console | Output format |
--output FILE |
stdout | Write report to file |
--severity [critical|high|medium|low] |
low | Minimum severity to report |
--max-file-size SIZE |
10MB | Skip files larger than this |
--include-hidden |
off | Scan hidden files/directories |
--quiet |
off | Suppress progress output |
--verbose |
off | Show every file being scanned |
--exclude PATTERN |
— | Glob patterns to exclude (repeatable) |
--disable-rule RULE_ID |
— | Suppress specific detection rules (repeatable) |
--ioc-file FILE |
— | Load additional IoC indicators from a JSON file |
A Rich progress bar is shown on stderr during scanning. It is automatically suppressed with --quiet or --verbose. A summary line (files scanned, findings, output path) is always printed to stderr after the scan completes.
Other commands
# List all detection rule IDs (for use with --disable-rule)
glassworm-hunter rules
# Update local IoC database from GitHub (default source:
# https://raw.githubusercontent.com/afine-com/glassworm-hunter/main/data/ioc.json)
glassworm-hunter update
# Update from a custom source (e.g. internal threat intelligence server)
glassworm-hunter update --source https://internal.corp/ioc.json
# Force overwrite existing local IoC database
glassworm-hunter update --force
# Print version
glassworm-hunter version
Exit codes
| Code | Meaning |
|---|---|
| 0 | No findings |
| 1 | Findings detected |
| 2 | Scanner error |
If you find something
- Don't panic. The scanner found an indicator, not a confirmed breach.
- Don't execute the flagged code. Don't run, build, or test the affected project until resolved.
- CRITICAL findings: Uninstall the extension/package immediately. Rotate your NPM tokens, GitHub tokens, SSH keys, and any other credentials on the machine.
- HIGH findings: Investigate the flagged file. If it's in a dependency you didn't explicitly install, remove it.
- MEDIUM findings: Review the code. These patterns are suspicious but may be legitimate in some contexts.
CI/CD integration
JSON output for automated processing:
glassworm-hunter scan . --format json --output report.json --no-extensions
SARIF for GitHub Code Scanning:
glassworm-hunter scan . --format sarif --output results.sarif --no-extensions
Upload SARIF to GitHub:
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Use exit codes in CI:
glassworm-hunter scan . --severity critical --no-extensions || exit 1
Exclude build artifacts and vendor code:
glassworm-hunter scan . --exclude "dist/**" --exclude "*.min.js" --no-extensions
No network access
This scanner is fully offline by default. It reads local files only. No telemetry, no phone-home, no automatic update checks. IoC databases are bundled in the package.
The only command that makes a network request is glassworm-hunter update, which fetches the latest IoC database from GitHub (or a custom --source URL). It is never called automatically.
Configuration file
Place a .glassworm.yml in your project root to set defaults:
exclude:
- "*.min.js"
- "vendor/**"
- "dist/**"
disable_rules:
- zero-width-chars
severity: medium
CLI flags override config file values.
Custom IoC files
Use --ioc-file to load additional indicators from a JSON file. This is useful for team-specific or internal threat intelligence:
glassworm-hunter scan /path --ioc-file /path/to/team_ioc.json
The file must follow the same schema as data/ioc.json:
{
"schema_version": "1.0",
"extensions": [{"id": "publisher.name"}],
"npm_packages": [{"name": "pkg", "malicious_versions": "1.0.0"}],
"c2_ips": [{"ip": "1.2.3.4"}],
"c2_wallets": [{"address": "..."}],
"attacker_artifacts": [{"type": "email", "value": "attacker@example.com"}]
}
The custom file is merged on top of all other IoC layers (hardcoded → bundled → user ~/.glassworm/ioc.json → --ioc-file).
IoC database layers
The scanner uses a 3+1 layer IoC system (each layer merges on top of the previous, never removes):
- Hardcoded — built into the Python source, always available
- Bundled —
data/ioc.jsonshipped with the package - User —
~/.glassworm/ioc.json, updated viaglassworm-hunter update - Custom —
--ioc-fileflag, highest priority
Credits
- Koi Security - original discovery of GlassWorm (October 2025) and ongoing tracking across multiple waves, including the OpenVSX/VSCode campaign, Rust binary pivot, and macOS pivot. IoC data in this scanner builds on their published research.
- Aikido Security - analysis of the March 2026 GlassWorm wave targeting GitHub repositories and npm packages, alongside Socket, Step Security, and the OpenSourceMalware community.
- AFINE - this scanner
Windows
The scanner works on Windows. 4 tests fail unless you run an elevated (Administrator) command prompt.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file glassworm_hunter-1.0.0.tar.gz.
File metadata
- Download URL: glassworm_hunter-1.0.0.tar.gz
- Upload date:
- Size: 130.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5234f8f79138efaf012e8339819c99211b1be1d49b36a1ce0133afefab59f4c8
|
|
| MD5 |
42c6ab5005aac345f67ee0295163f837
|
|
| BLAKE2b-256 |
256cc1f0aafb5d63fc21f9f15e033bbdb6e8c89cf67bceb04218f8ccafcd5c6d
|
Provenance
The following attestation bundles were made for glassworm_hunter-1.0.0.tar.gz:
Publisher:
publish.yml on afine-com/glassworm-hunter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
glassworm_hunter-1.0.0.tar.gz -
Subject digest:
5234f8f79138efaf012e8339819c99211b1be1d49b36a1ce0133afefab59f4c8 - Sigstore transparency entry: 1147627016
- Sigstore integration time:
-
Permalink:
afine-com/glassworm-hunter@0d835ebd2e313f8f195bbd2972b8360872fe3acd -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/afine-com
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0d835ebd2e313f8f195bbd2972b8360872fe3acd -
Trigger Event:
release
-
Statement type:
File details
Details for the file glassworm_hunter-1.0.0-py3-none-any.whl.
File metadata
- Download URL: glassworm_hunter-1.0.0-py3-none-any.whl
- Upload date:
- Size: 43.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3914db8101a3c1d0bf86ea79b47d95825250b71fd875a55a9625441365827c3
|
|
| MD5 |
5ccb8b1f2cc5dcf93ead2e0e6dea9bf5
|
|
| BLAKE2b-256 |
920f7302fa0150e338bc76ab640386594e6bfa6b438107a08bf78613e85d4ffd
|
Provenance
The following attestation bundles were made for glassworm_hunter-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on afine-com/glassworm-hunter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
glassworm_hunter-1.0.0-py3-none-any.whl -
Subject digest:
a3914db8101a3c1d0bf86ea79b47d95825250b71fd875a55a9625441365827c3 - Sigstore transparency entry: 1147627131
- Sigstore integration time:
-
Permalink:
afine-com/glassworm-hunter@0d835ebd2e313f8f195bbd2972b8360872fe3acd -
Branch / Tag:
refs/tags/v1.0.2 - Owner: https://github.com/afine-com
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0d835ebd2e313f8f195bbd2972b8360872fe3acd -
Trigger Event:
release
-
Statement type: