Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

snaffloco

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.


Why

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.


Requirements

  • Python 3.11+ (uses the built-in tomllib)
    or Python 3.9 / 3.10 with pip install tomli
  • No other mandatory dependencies
  • pip install cryptography (optional — enables richer certificate analysis: subject, expiry, password-protection status)

Quick start

# 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.


Usage

python3 snaffloco.py PATH [options]
python3 snaffloco.py --sync [--rules-dir DIR]

Positional argument

Argument Description
PATH Directory to scan. The only required argument for a normal scan.

Options

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.

Examples

# 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-rules

Output

Console (stdout)

Each 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.

JSONL output file (-o)

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.jsonl

How it works

snaffloco 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 skipped
  • Snaffle — 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

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.

Syncing rules

python3 snaffloco.py --sync

This 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.

Writing custom rules

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

User context

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

References

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages