A defensive security tool to detect potential compromise from the Nx "s1ngularity" supply chain attack.
The Nx s1ngularity attack was a sophisticated supply chain attack that compromised JavaScript developers through malicious packages in the Nx ecosystem. The attack targeted valuable credentials including:
- GitHub tokens and SSH keys
- npm authentication tokens
- Environment variables containing API keys
- Cryptocurrency wallet files
- LLM client configurations (Claude, Gemini, ChatGPT)
Stolen credentials were exfiltrated to public GitHub repositories, resulting in over 2,300 distinct secrets being leaked from more than 1,000 compromised systems.
This project relies on HasMySecretLeaked. To learn more about how this works, visit our What happens under the hood page.
- Python ≥3.9 (enforced at runtime)
- ggshield - GitGuardian's CLI tool for secret scanning
- GitHub CLI (optional) - for extracting GitHub tokens
macOS:
# Using Homebrew (recommended)
brew install ggshield
# Or download standalone .pkg from GitHub releases (no Python required)
📦 Download .pkg from GitHub releases
Linux:
# Using pipx (recommended)
pipx install ggshield
# Or use distribution packages (deb/rpm) from Cloudsmith
📦 Download packages from Cloudsmith
Windows:
# Using Chocolatey
choco install ggshield
# Or download standalone .zip from GitHub releases (no Python required)
📦 Download .zip from GitHub releases
For more installation options, see the ggshield documentation.
macOS/Linux:
git clone https://github.com/GitGuardian/s1ngularity-scanner
cd s1ngularity-scanner
# With uv (if available)
./leak_scanner.py
# Or with Python directly
python3 leak_scanner.py
Windows:
git clone https://github.com/GitGuardian/s1ngularity-scanner
cd s1ngularity-scanner
python leak_scanner.py
--min-chars <number>
- Minimum character length for values to consider (default: 5)--keep-found-values
- Keep the temporary file containing gathered values instead of deleting it--timeout <seconds>
- Maximum time to spend scanning filesystem for .env files (default: 30, use 0 for unlimited)--verbose, -v
- Show detailed scanning progress and debug information
Singularity Scanner uses environment variables and files, including files known to be used by the original exploit. We don't reuse the prompt as our analysis showed the AI didn't actually provide many files. Instead, the scanner directly targets known file locations and scans them for secrets. These secrets are hashed and compared against what GitGuardian found on GitHub.
The scanner collects potential secrets from these sources:
- All environment variables from the current shell session
- GitHub token from
gh auth token
command (if GitHub CLI is installed) - NPM configuration from
$HOME/.npmrc
- Environment files - all
.env*
files recursively found in your home directory- Skips hidden directories (starting with
.
) andnode_modules
for performance - Has a configurable timeout to prevent long scans on large filesystems
- Skips hidden directories (starting with
- No data transmission: Secrets are never sent to GitGuardian or any external service
- Local processing: All scanning happens locally on your machine
- Hash comparison: Only SHA-256 hashes of potential secrets are compared against GitGuardian's database
- Temporary files: A temporary file
gg_gathered_values
is created during scanning and automatically deleted (unless--keep-found-values
is used)
Basic scan with default settings:
./leak_scanner.py
# or: python3 leak_scanner.py
Scan with longer timeout for large filesystems:
./leak_scanner.py --timeout 120
Keep the temporary file for inspection:
./leak_scanner.py --keep-found-values
Only consider longer values (reduces noise):
./leak_scanner.py --min-chars 10
Show detailed scanning progress:
./leak_scanner.py --verbose
Complete filesystem scan (no timeout):
./leak_scanner.py --timeout 0
- No AI queries: Unlike the original exploit, we don't ask Claude, Gemini or Q for files that may contain secrets
- Filesystem timeout: Large filesystems may not be fully scanned within the default 30-second timeout
- Directory exclusions: Hidden directories (
.git
,.cache
, etc.) andnode_modules
are skipped for performance - Pattern matching: Only detects key-value assignments in standard formats (may miss unconventional secret storage)
- False positives: May flag legitimate non-secret values that happen to match secret patterns