A local file snaffer. Point it at a folder; it walks the tree and surfaces files that are likely to contain credentials, keys, or other sensitive material — using the same rule engine and the same TOML rule files as SnaffCon/Snaffler and cisagov/snafflepy.
The difference is that snaffloco works on a local directory rather than remote SMB shares. It requires no network access, no domain membership, and no special privileges — it runs as whoever launched it.
Snaffler is the standard tool for finding credential files on Windows file shares during a pentest. snafflepy brought the same idea to Linux/Python over SMB. snaffloco brings the same classification logic to local paths: useful when you already have a shell, are reviewing a mounted image, auditing a build server, or working somewhere without SMB access.
Because it uses the same TOML rule format, any rule written for Snaffler works here unchanged.
- Python 3.11+ (uses the built-in
tomllib)
or Python 3.9 / 3.10 withpip install tomli - No other mandatory dependencies
pip install cryptography(optional — enables richer certificate analysis: subject, expiry, password-protection status)
# 1. Download rules
python3 snaffloco.py --sync
# 2. Scan a directory
python3 snaffloco.py /path/to/scan
That's it. Rules are saved next to the script in ./rules/ and reused on every subsequent run.
python3 snaffloco.py PATH [options]
python3 snaffloco.py --sync [--rules-dir DIR]
| Argument | Description |
|---|---|
PATH |
Directory to scan. The only required argument for a normal scan. |
| Flag | Default | Description |
|---|---|---|
-o FILE / --output FILE |
— | Write findings to FILE in JSONL format (one JSON object per line). Console output is unaffected. |
--rules-dir DIR |
./rules or ~/.snaffloco/rules |
Directory containing TOML rule files. |
--max-size SIZE |
1M |
Maximum file size to read for content scanning. Accepts K, M, G suffixes (e.g. 512K, 2M). Files larger than this are still checked by name/extension but not read. |
--go-loud |
off | Disable all filtering. Every file is reported as a Green finding. Useful for producing a complete file inventory. |
--no-content |
off | Skip content scanning. Only filename, extension, and path rules run. Faster, but misses credentials embedded in code. |
-q / --quiet |
off | Suppress the progress line. Findings still print to stdout. Useful when piping output. |
--sync |
— | Download the latest rule files from upstream and exit. Does not scan anything. |
# Scan a web root
python3 snaffloco.py /var/www
# Scan a home directory and save findings to a file
python3 snaffloco.py /home -o findings.jsonl
# Fast pass — names and extensions only, no file reads
python3 snaffloco.py /srv --no-content
# Produce a full file inventory
python3 snaffloco.py /mnt/image --go-loud -o inventory.jsonl
# Pipe findings into jq without progress noise
python3 snaffloco.py /etc -q 2>/dev/null | grep RED
# Use a custom rules directory
python3 snaffloco.py /data --rules-dir ~/my-rules
# Sync rules to a custom location
python3 snaffloco.py --sync --rules-dir ~/my-rulesEach finding prints immediately as it is discovered:
BLACK /etc/ssh/id_rsa (3.2 KB) KeepSSHKeysByFileName via FileName
RED /var/www/app/config.py (12.4 KB) KeepAwsKeysInCode via FileContentAsString
awskey = "AKIAIOSFODNN7EXAMPLE"
YELLOW /opt/backup/db.sqldump (48.1 MB) KeepDatabaseByExtension via FileExtension
Triage levels, from most to least critical:
| Level | Meaning |
|---|---|
| BLACK | Almost certainly sensitive — private keys, password databases, hash files, network device configs |
| RED | High interest — credentials in code, SSH keys by content, certificate files, git credential stores |
| YELLOW | Worth reviewing — VPN configs, packet captures, deployment images, database dumps |
| GREEN | Low-signal but notable — shell history, rc files, generic config files |
| GRAY | Informational |
The progress line on stderr updates in place and is cleared before each finding prints, so the two streams don't interfere:
1,432 files 2,140/s 1s BLACK 3 RED 7 YELLOW 2 /var/www/html/wp-config.php
Redirecting stdout (> findings.txt) leaves the progress line visible on screen. Redirecting stderr (2>/dev/null) silences it entirely.
One JSON object per line:
{"ts":"2026-05-18T09:25:17.171217+00:00","path":"/etc/ssh/id_rsa","size":3276,"triage":"Black","rule":"KeepSSHKeysByFileName","scope":"FileEnumeration","match_location":"FileName","matched":"id_rsa","context":null}
{"ts":"2026-05-18T09:25:18.003441+00:00","path":"/var/www/app/config.py","size":12644,"triage":"Red","rule":"KeepAwsKeysInCode","scope":"ContentsEnumeration","match_location":"FileContentAsString","matched":"awskey","context":"...awskey = \"AKIAIOSFODNN7EXAMPLE\"..."}Field reference:
| Field | Type | Description |
|---|---|---|
ts |
string | UTC timestamp (ISO 8601) |
path |
string | Absolute path to the file |
size |
integer | File size in bytes |
triage |
string | Black / Red / Yellow / Green / Gray |
rule |
string | Name of the rule that matched |
scope |
string | FileEnumeration or ContentsEnumeration |
match_location |
string | What was examined: FileName, FileExtension, FilePath, FileContentAsString, FileLength, FileMD5 |
matched |
string | The string or pattern that triggered the match |
context |
string | null | Up to 400 characters of surrounding text for content matches |
Parsing with jq:
# Critical findings only
jq 'select(.triage == "Black")' findings.jsonl
# All paths, sorted by triage severity
jq -r '[.triage, .path] | @tsv' findings.jsonl | sort
# Unique rules that fired
jq -r '.rule' findings.jsonl | sort -u
# Content matches with context
jq 'select(.scope == "ContentsEnumeration") | {path, matched, context}' findings.jsonlsnaffloco implements the same four-stage classification pipeline as Snaffler:
DirectoryEnumeration → FileEnumeration → ContentsEnumeration
↘ PostMatch (secondary filter)
1. Directory pruning — before descending into a directory, DirectoryEnumeration rules are checked. A Discard match prunes the entire subtree. This is how large false-positive directories like node_modules, site-packages, and WindowsPowerShell\Modules are skipped without scanning their contents.
2. File classification — for each file, FileEnumeration rules are checked against the filename, extension, or full path. Three outcomes:
Discard— file is skippedSnaffle— file is reported (after PostMatch check)Relay— file passes to one or more named content rules
3. Content scanning — triggered only via Relay. File text is read (up to --max-size) and matched against regex patterns. A Relay rule connecting .py files to KeepAwsKeysInCode, for example, means Python files are grepped for AWS key patterns without every file needing to be read.
4. PostMatch filter — a secondary set of Discard rules applied after a Snaffle hit, used to cut known false positives by name or extension.
5. Certificate analysis — files matching a CheckForKeys rule have their raw bytes inspected for PRIVATE KEY and CERTIFICATE markers. With the cryptography package installed, this also extracts the subject, expiry date, and whether the key is password-protected.
All matching is case-insensitive. Symlinks are not followed. Files that cannot be read due to permissions are silently skipped.
Rules live in ./rules/ next to the script (or ~/.snaffloco/rules/ as a fallback). Each rule is a TOML file with one or more [[ClassifierRules]] sections:
[[ClassifierRules]]
EnumerationScope = "FileEnumeration"
RuleName = "KeepSSHKeysByFileName"
MatchAction = "Snaffle"
MatchLocation = "FileName"
WordListType = "Exact"
WordList = ["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519"]
Triage = "Black"[[ClassifierRules]]
EnumerationScope = "FileEnumeration"
RuleName = "RelayPythonByExtension"
MatchAction = "Relay"
RelayTargets = ["KeepAwsKeysInCode", "KeepInlinePrivateKey", "KeepPassOrKeyInCode"]
MatchLocation = "FileExtension"
WordListType = "Exact"
WordList = ["\\.py"]
Triage = "Green"| Field | Values |
|---|---|
EnumerationScope |
DirectoryEnumeration, FileEnumeration, ContentsEnumeration, PostMatch |
MatchAction |
Discard, Snaffle, Relay, CheckForKeys |
MatchLocation |
FileName, FileExtension, FilePath, FileContentAsString, FileLength, FileMD5 |
WordListType |
Exact, Contains, Regex, EndsWith, StartsWith |
Triage |
Black, Red, Yellow, Green, Gray |
Rules can be organized into any subdirectory structure — snaffloco recurses through all *.toml files under the rules directory.
python3 snaffloco.py --syncThis downloads all TOML rule files from cisagov/snafflepy into the rules directory. The snafflepy rules are the canonical portable TOML representation of the Snaffler rule set — the original Snaffler (C#) compiles its rules directly into the binary rather than shipping them as external files.
Sync uses only Python's standard library (urllib); no additional packages required.
Drop any .toml file containing [[ClassifierRules]] sections into the rules directory. Custom rules are loaded alongside the defaults. Use a subdirectory to keep them organized:
rules/
custom/
KeepInternalSecrets.toml
DiscardTestData.toml
snaffloco makes no attempt to change user or elevate privileges. It calls standard Python file I/O (os.walk, Path.read_text) under the identity of the process that launched it. Files the current user cannot read are silently skipped.
To scan as a different user, start a shell as that user first:
# Windows
runas /user:DOMAIN\otheruser cmd.exe
# then in that shell:
python3 snaffloco.py C:\path\to\scan
# Linux
sudo -u otheruser python3 snaffloco.py /path/to/scan- SnaffCon/Snaffler — the original C# tool
- cisagov/snafflepy — Python SMB port; source of the TOML rule files