LeakLens – Credential Exposure Scanner for File Shares
LeakLens scans SMB/UNC shares and local paths for passwords, keys, tokens, and secrets —
with confidence scoring, live browser streaming, and zero mounting required.
Built for pentesters • Blue teams • Security auditors • DFIR
Tools like truffleHog and gitleaks excel at scanning Git repositories and CI artifacts.
LeakLens targets a different, often neglected attack surface: shared folders and internal file servers. It scans \\server\share directly without mounting or drive letters, and streams confidence-scored findings to a browser in real time.
Born from a pentest finding: a domain admin password sitting in a plaintext .ps1 file on an open file share, readable by every user in the domain. LeakLens exists to find those before an attacker does.
LeakLens complements tools like truffleHog and gitleaks by focusing on a different attack surface: file shares and internal paths.
| Tool | Primary focus | Native SMB (no mount) | Share enumeration | Git history | Live UI streaming | Resume scans |
|---|---|---|---|---|---|---|
| LeakLens | File shares, local paths | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes |
| truffleHog | Git, CI, cloud, filesystem | ❌ No | ✅ Yes | ❌ No | ❌ No | |
| gitleaks | Git repos & commits | ❌ No | ❌ No | ✅ Yes | ❌ No | ❌ No |
LeakLens is not a replacement for repository secret scanners, it fills the gap where credentials leak into shared folders, scripts, and internal file servers. The biggest difference being Native SMB/UNC (and ease of use - personal taste)
Scans Windows file shares (and local paths) for exposed credentials:
- Connects directly to SMB/UNC paths — no manual mount, no drive letter required
- Enumerates shares on a server with one click
- Scans text files for passwords, hashes, keys, tokens, and connection strings
- Flags sensitive file types (
.pfx,.ppk,.kdbx,.pem, ...) and risky filenames (id_rsa,credentials,.env, ...) - Scores every finding 1–10 so you spend time on what matters
- Shows the exact matched line and line number for every content finding — no need to open the file
- Streams results to a browser UI in real time using Server-Sent Events
- Scans files in parallel using a configurable number of worker threads
- Saves findings to a SQLite database per scan for fast historical querying
- Can resume interrupted scans from a checkpoint without rescanning files already processed
- Saves a timestamped JSON report of every scan, browsable in the Reports tab
- Python 3.11+
- Dependencies (pinned):
flask==3.1.3smbprotocol==1.16.0
Share enumeration — no extra packages required:
| Platform | Method used automatically |
|---|---|
| Windows | Built-in net use + net view — supports credentials, works on all Windows versions |
| Linux | smbclient binary — apt install smbclient |
| macOS | smbclient binary — brew install samba |
Optional:
impacket>=0.12.0is used first if installed (cross-platform, slightly faster). It is never required.
Windows
start.bat
Linux / macOS
chmod +x start.sh && ./start.shOr run directly:
python3 -m pip install -r requirements.txt
python3 leaklens.pyOpens at http://localhost:3000.
The server binds to 127.0.0.1:3000 by default.
Override with environment variables: LEAKLENS_HOST and LEAKLENS_PORT.
For a full walkthrough of every feature — SMB share browsing, confidence scoring, custom patterns, suppression, webhooks, and CLI usage — see USAGE.md.
Type a UNC path such as \\server\share into the Path field and click Start Scan.
Credentials are read from the SMB: Browse Shares & Credentials modal if you need them.
- Click ⬡ SMB: Browse Shares & Credentials in the Scan Configuration panel
- Enter the server address in the Server / Host field
- Standard port:
192.168.1.10 - Non-standard port:
192.168.1.10:4445(orhostname:port)
- Standard port:
- Optionally enter Username, Password, and Domain — leave blank for guest/anonymous access
- Click ⬡ Discover Shares — all visible shares appear in a list (admin shares are labelled separately)
- Click any share to auto-populate the Path field and close the modal
- Back in the main panel:
- Set Max Size (default 10 MB) and Worker Threads (default 8)
- Enable Resume to continue an interrupted scan from its checkpoint
- Click Start Scan — findings stream in as they are found
- Click any row to see the exact matched line, file metadata, and remediation advice
- Open the Reports tab at any time to reload a previous scan
| Pattern | Examples | Confidence |
|---|---|---|
| Plaintext Password | password=, "password": "...", DB_PASSWORD= |
8/10 |
| Connection String | Embedded passwords in connection strings | 8/10 |
| NTLM / LM Hash | lm_hash:ntlm_hash pairs |
8/10 |
| Bcrypt Hash | $2a$, $2b$, $2y$ |
8/10 |
| Base64 Credential | Base64 values next to credential keywords | 5/10 |
| AWS Access Key | AKIA… |
9/10 |
| GitHub / GitLab PAT | ghp_…, gho_…, glpat-… |
10/10 |
| Stripe API Key | sk_live_…, sk_test_… |
10/10 |
| Slack Token | xoxb-…, xoxp-… |
10/10 |
| SendGrid API Key | SG.xxxx.xxxx |
10/10 |
| Azure Client Secret / Storage Key | Azure credential formats | 8/10 |
| DPAPI Encrypted Blob | DPAPI blob headers (base64 or hex) | 8/10 |
| API Key / Bearer Token | Key assignments and Bearer tokens |
6/10 |
| Private Key Header | -----BEGIN … PRIVATE KEY----- |
10/10 |
| Net Use Credential | net use /user: commands |
8/10 |
| PowerShell SecureString | Hardcoded ConvertTo-SecureString literals |
8/10 |
| PowerShell PSCredential | Hardcoded PSCredential objects |
6/10 |
| SQL sa Password | sa password = |
8/10 |
| MD5 / SHA1 / SHA256 / SHA512 | Hash strings by length | 3–4/10 |
.kdbx .kdb .pfx .p12 .ppk .pem .key .jks .wallet
Files whose name contains: password, creds, secret, token, id_rsa, apikey, .env, and similar.
Every finding gets a score from 1–10 based on how certain the match is:
| Score | Meaning |
|---|---|
| 9–10 | Near-certain — private key, AWS access key |
| 7–8 | High confidence — plaintext password, NTLM hash, connection string, SecureString |
| 5–6 | Moderate — API key pattern, PSCredential, suspicious filename |
| 3–4 | Low signal — hash strings that could be checksums rather than credentials |
Confidence is reduced automatically for files in docs/, examples/, test/, and similar directories. Files where only generic hash patterns match are downgraded to LOW with a note.
- Common placeholders (
changeme,example,${password},***, etc.) are excluded from password matches - Docs and example directories reduce confidence by 3 points automatically
- Hash-only findings are demoted to LOW with the note: "Hash strings detected — verify these are credential hashes and not integrity checksums."
- Lockfiles (
package-lock.json,yarn.lock,poetry.lock, etc.) are skipped entirely — they are dense with hash strings that would otherwise flood results
Drop a .leaklensignore file in your scan root to silence known-safe paths. The format mirrors .gitignore:
# Suppress entire paths
archive/**
docs/**
*.example.config
# Suppress a specific pattern type in specific paths
[md5_hash]
checksums/**
*.md5
Pattern IDs are listed in scanner/patterns.py.
Every content finding includes the exact line that triggered the match. The detail drawer shows each detected pattern alongside its line number and the full matched line, so you can confirm the finding without opening the file:
Plaintext Password Line 4
DB_PASSWORD=Tr0ub4dor&3
AWS Access Key Line 7
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
Lines are truncated at 120 characters. Findings flagged only by file type or filename (e.g. .pfx, id_rsa) show a note in place of a snippet.
Every completed scan is saved as a timestamped JSON file (LeakLens_<timestamp>.json) and a SQLite database (LeakLens_<timestamp>.db) in the reports/ directory.
The Reports tab in the UI lists all saved reports — click any entry to reload its findings into the main view, complete with filtering, search, and the detail drawer.
The SQLite database enables fast paginated queries against historical scans without loading the entire result set into memory.
If a scan is stopped before completion, LeakLens writes a checkpoint file to reports/. Enabling the Resume checkbox on the next scan of the same path will skip already-processed files and continue from where it left off.
The scanner uses a producer/consumer architecture:
- A single walk thread traverses the file tree and feeds a bounded queue
- A configurable number of worker threads (default 8, max 16) analyse files in parallel
- Results flow through an events queue back to the SSE generator
This allows I/O-bound SMB reads to overlap with CPU-bound pattern matching. The current scan rate (files/second) is shown live in the progress bar.
When scanning a UNC path, findings include share context that is useful in an audit report:
{
"smbServer": "fileserver01",
"smbShare": "\\\\fileserver01\\IT",
"smbRelativePath": "scripts\\deploy.ps1",
"lastModified": "2024-11-14 08:23",
"lastAccessed": "2025-01-02 13:45"
}The detail drawer also surfaces this information alongside tailored remediation advice that references the share name.
| Level | Criteria |
|---|---|
| 🔴 HIGH | Confidence ≥ 8 — private key, plaintext password, AWS key, sensitive binary file |
| 🟡 MEDIUM | Confidence 5–7 — API key pattern, PSCredential, suspicious filename |
| 🟢 LOW | Confidence ≤ 4 — hash strings, low-signal patterns |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/scan |
Start a scan. Returns an SSE stream of events. |
POST |
/api/scan/stop |
Stop the active scan. |
GET |
/api/status |
{scanning: bool, version: str} |
POST |
/api/shares |
Enumerate SMB shares on a host. |
GET |
/api/scans |
List all SQLite-backed scan metadata. |
GET |
/api/scans/<scan_id>/export |
Export all findings for a scan as JSON. |
GET |
/api/findings |
Paginated findings query against a scan DB. |
| Parameter | Type | Default | Description |
|---|---|---|---|
scanPath |
string | — | UNC or local path to scan (required) |
maxFileSizeMB |
int | 10 | Skip files larger than this |
workers |
int | 8 | Number of parallel worker threads (1–16) |
resume |
bool | false | Resume from the last checkpoint for this path |
username |
string | — | SMB username |
password |
string | — | SMB password |
domain |
string | — | SMB domain |
smbPort |
int | 445 | SMB port (use when the server listens on a non-standard port) |
| Parameter | Default | Description |
|---|---|---|
scan_id |
— | Required. Timestamp ID of the scan (e.g. 20240101_120000) |
page |
0 | 0-based page number |
per_page |
100 | Rows per page (1–500) |
risk |
ALL | Filter by risk: HIGH, MEDIUM, LOW, or ALL |
search |
— | Substring filter on filename or full path |
A Samba container pre-loaded with intentionally dirty files is included so you can verify LeakLens works without touching real infrastructure. Requires Docker or Podman.
Start it:
# Linux / macOS
./testserver/start-testserver.sh
# Windows
testserver\start-testserver.batThe container runs Samba on port 4445. No mounting is needed — LeakLens connects directly over SMB.
Scan it with the UI:
- Click ⬡ SMB: Browse Shares & Credentials in the Scan Configuration panel
- Enter
127.0.0.1:4445in the Server / Host field - Leave Username and Password blank (the share allows guest access)
- Click ⬡ Discover Shares —
testshareappears in the list - Click testshare to select it — the Path field is populated automatically
- Click Start Scan
Scan it from the CLI:
python3 leaklens.py scan --path "\\\\127.0.0.1\\testshare" --smb-port 4445Stop it:
./testserver/stop-testserver.sh # Linux / macOS
testserver\stop-testserver.bat # WindowsSee USAGE.md for the complete test server walkthrough.
Expected findings across the 8 test files:
| File | Patterns triggered |
|---|---|
deploy.ps1 |
Plaintext Password, Connection String, PowerShell SecureString, Hardcoded PSCredential |
app.config |
Plaintext Password, Generic API Key/Token |
passwords.txt |
NTLM Hash |
.env |
AWS Access Key, Plaintext Password, Base64 Credential, Stripe API Key |
nightly-backup.bat |
Net Use Credential |
db_maintenance.py |
Plaintext Password, Bearer Token |
id_rsa |
Private Key Header |
project-notes.md |
(clean — no findings) |
Install dev dependencies, then run pytest:
pip install -r requirements-dev.txt
python -m pytest tests/ -v149 tests across three modules: pattern regexes, engine helpers, and API endpoints. See USAGE.md for a full walkthrough including the live test server.
LeakLens/
├── leaklens.py # Entry point — Flask server + all API routes
├── requirements.txt # Pinned runtime dependencies
├── requirements-dev.txt # Adds pytest for development
├── start.bat / start.sh # Launchers
├── scanner/
│ ├── engine.py # Scan orchestrator — walk, workers, SQLite, SSE
│ ├── content.py # Detection helpers, scan_content(), build_finding()
│ ├── patterns.py # 25 detection rules (pre-compiled)
│ ├── smb.py # smbprotocol helpers
│ ├── suppress.py # .leaklensignore parser
│ └── checkpoint.py # Resume checkpoint helpers
├── frontend/
│ ├── index.html # Browser UI shell
│ └── Assets/
│ ├── app.js # SSE client, virtual scroll, filters
│ └── styles.css
├── tests/
│ ├── test_patterns.py # Positive/negative tests for all 25 patterns
│ ├── test_engine.py # scan_content, build_finding, suppression helpers
│ └── test_api.py # Flask endpoint tests
├── reports/ # Auto-created: SQLite + JSON per scan
└── testserver/ # Samba container for testing
Triggered by clicking Discover Shares in the UI (POST /api/shares).
Three methods are tried in order — the first one that succeeds wins:
POST /api/shares {host, username, password, domain}
│
▼
list_shares() [scanner/smb.py]
│
├─ 1. impacket (if installed — all platforms)
│ DCE/RPC over SMB named pipe (\srvsvc)
│ NetShareEnum Level 1
│ ↳ Fastest, pure Python, supports non-standard ports
│
├─ 2. net use + net view (Windows built-in — no extra packages)
│
│ With credentials:
│ net use \\server\IPC$ <password> /user:<domain\username>
│ net view \\server /all
│ net use \\server\IPC$ /delete /yes ← always cleaned up
│
│ Without credentials (anonymous / current session):
│ net view \\server /all
│
└─ 3. smbclient binary (Linux / macOS)
smbclient -L //server -U user%pass
↳ Installed via: apt install smbclient / brew install samba
↓
[{name: "share1"}, {name: "share2"}, ...] → returned to browser
Key design point: steps 2 and 3 separate authentication from enumeration — Windows has no single built-in command that does both. net use establishes an authenticated SMB session to IPC$ (the inter-process communication share used by Windows for RPC), which makes net view run under those credentials. The session is deleted immediately after enumeration regardless of success or failure.
Triggered by clicking Start Scan (POST /api/scan).
The response body is an SSE stream — findings appear in the browser as they are found, not after the scan completes.
POST /api/scan {scanPath, workers, maxFileSizeMB, resume, ...}
│
▼
scan_path() [scanner/engine.py]
│
├─ Walk thread (1 thread)
│ Local path: os.scandir() — recursive directory walk
│ SMB path: smbclient.scandir() via walk_smb()
│ ↳ Credit-limited semaphore (max 4 concurrent SMB ops)
│ ↓
│ Bounded file queue (maxsize = workers × 8)
│ ↳ Back-pressures the walk if workers are busy
│
├─ Worker threads (N threads, default 8, max 16)
│ Pull file paths from the queue
│ │
│ ├─ Skip: file too large / excluded extension / lockfile
│ ├─ Read: open() for local / smbclient.open_file() for SMB
│ ├─ Match: re.finditer() against 25 pre-compiled patterns
│ ├─ Score: confidence 1–10
│ │ ↳ directory penalty (docs/, examples/, test/ → −3)
│ │ ↳ hash-only findings capped at LOW
│ ├─ Suppress: .leaklensignore rules checked per file + pattern
│ └─ Emit: finding event → bounded event queue
│
├─ SSE generator (Flask main thread)
│ Pulls events from the event queue
│ Writes findings to SQLite (batched every 50 rows, WAL mode)
│ Yields "data: {...}\n\n" to the HTTP response
│ ↳ Checkpoint written every 500 files (enables resume)
│
└─ Browser (frontend/Assets/app.js)
fetch() + ReadableStream (not EventSource — scan is a POST)
Decodes SSE lines, pushes findings into filteredFindings[]
Virtual-scrolled table — only visible rows rendered (rAF-throttled)
Live progress bar shows files/second rate
Resume: when resume: true, a checkpoint file maps each file's SHA-256 to its result. Already-processed files are skipped on re-scan. The SQLite database is opened in append mode against the original scan row.
Browser ──POST /api/scan──► Flask SSE stream
│
Walk thread ──► file queue ──► Worker threads
│
Event queue
│
SSE generator ◄────────────────────────
│
SQLite DB (reports/LeakLens_<id>.db)
│
Browser ◄── paginated /api/findings (historical)
Run LeakLens only on systems and file shares you are authorized to access. Scanning reads file contents — ensure you have the appropriate permissions before use.
LeakLens is open source and community-driven. Bug reports, feature requests, and PRs are welcome. Especially new detection patterns and test cases.


