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
- uv (optional, recommended) - Modern Python package manager for better dependency isolation
- 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.
Preferred approach with uv (recommended):
First, install uv for modern Python dependency management.
git clone https://github.com/GitGuardian/s1ngularity-scanner
cd s1ngularity-scanner
./leak_scanner.py
Alternative with Python directly:
macOS/Linux:
python3 leak_scanner.py
Windows:
python leak_scanner.py
--min-chars <number>
- Minimum character length for values to consider (default: 5)--keep-temp-file
- Keep the temporary file containing gathered values instead of deleting it--timeout <seconds>
- Maximum time to spend scanning filesystem for .env files (default: 0 for unlimited, set a number for time limit)--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-temp-file
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-temp-file
Only consider longer values (reduces noise):
./leak_scanner.py --min-chars 10
Show detailed scanning progress:
./leak_scanner.py --verbose
Limited time scan (30 seconds):
./leak_scanner.py --timeout 30
- No AI queries: Unlike the original exploit, we don't ask Claude, Gemini or Q for files that may contain secrets
- Filesystem scanning: Large filesystems may take time to scan completely. Use the timeout option e.g.
--timeout 30
to limit the time spent scanning if needed - 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