Skip to main content

Haul: credential exposure scanner for developer machines

If an infostealer ran on your machine right now, what would it take?

Haul finds every API key, token, SSH key and password lying where stealer malware looks first,
scores how exposed each one is, and tells you exactly how to fix it.
It never reads a secret out. Read-only, offline, zero dependencies.

PyPI Python 3.11 to 3.14 Windows and Linux Zero runtime dependencies No network access Typed with mypy strict License: free to use

pip install haul-scanner

Why · Features · Screenshots · Quick start · Coverage · How it works · Safety · CI · FAQ


Defensive use only. Haul is a read-only self-audit tool. Run it on systems you own or are explicitly authorized to test. It scans the current user's own account, refuses other users' profiles and network paths, and makes zero network connections.

Why Haul

Infostealers don't need an exploit. They run once as you, then sweep everything you can already read:

  • cloud and package-registry credentials: ~/.aws, gcloud, az, kubeconfig, .npmrc, .pypirc
  • SSH keys with no passphrase, .env files in every project folder, shell and PowerShell history
  • and, increasingly, the chat history of AI coding tools, where keys get pasted and .env files get echoed back

Most secret scanners look at repositories. Haul looks at your machine: the home folder, project folders, app-data stores and AI-tool transcripts that never reach a repo but sit on disk, readable by anything you run. It reports, ranks and explains. It never takes.

What you get

Nine scanners, one pass
Cloud & package CLIs, SSH keys, .env files, shell & PowerShell history, environment variables, 12 AI coding tools, browsers, desktop apps and sensitive files.
AI-history aware
Claude Code, Cursor, Copilot, Windsurf, Codex, Gemini CLI and more. Streams transcripts of any size and reads SQLite chat stores read-only. A key echoed 500× is one finding.
Git aware
Knows whether a .env is ignored, not ignored, or already committed, by reading .git directly. Git is never executed.
Real risk, not noise
58 provider rules plus assignment, YAML, JSON and XML heuristics. Aggressive false-positive filtering, and a 0–100 exposure score for every finding.
Never discloses a value
Secrets are sealed in memory the moment they are found. Reports carry only a non-reversible fingerprint, and an output firewall double-checks every byte.
Zero network, zero dependencies
An irreversible audit hook blocks sockets, DNS and subprocesses for the whole run. Pure standard library, so there is no supply chain to attack.
Reports people actually read
A worst-first terminal view with clickable paths, a self-contained HTML report, plus JSON, CSV and SARIF 2.1.0.
Built for CI
Exit codes by severity, --fail-on, baselines that survive edits, and SARIF that maps straight onto your repository's files.

See it in action

Run haul and it walks your machine once. The live progress bar keeps moving even through hundreds of megabytes of AI transcripts.

Haul starting: the banner and the live progress bar

The report is worst first. Every finding names its kind (API KEY, TOKEN, PRIVATE KEY, PASSWORD…), what it is and where it lives. Paths are clickable in modern terminals, and each fix appears once, numbered, under What to do.

Haul terminal report, worst findings first

Need to hand it to someone? haul -f html -o report.html writes one self-contained file with live search, severity filters and the same fixes. It needs no internet and loads nothing external.

Haul HTML report with search and severity filters

The HTML report filtered to one severity

Haul HTML report filtered to High findings

Every screenshot is real Haul output, captured on a demo profile seeded with fabricated secrets.

Quick start

Install it once, then run it as any normal user. Haul needs Python 3.11+ and nothing else.

pip install haul-scanner
Prefer an isolated install? Use pipx or uv.
pipx install haul-scanner
uv tool install haul-scanner
Run this To get
haul The full scan: terminal report, worst first
haul --min-severity high Only what matters most
haul -f html -o haul-report.html A readable, shareable HTML report
haul --only ai-tools Just your AI coding tools' history and configs
haul --clean A dry run of redacting secrets from AI-chat history
haul --list-scanners Every scanner and what it covers

What it scans

haul --list-scanners output

Scanner What it covers
cloud-cli AWS (static keys, SSO and CLI caches), Azure, gcloud, kubeconfig, Docker, npm, Yarn, PyPI, Cargo, RubyGems, Composer, NuGet, Maven, Terraform Cloud, Vault, .netrc / _netrc, GitHub CLI and .git-credentials, resolved per OS.
ssh ~/.ssh plus every IdentityFile in ~/.ssh/config. OpenSSH, PEM, PKCS#8 and PuTTY .ppk, flagging keys with no passphrase. Reads only the header; key material is never decoded.
dotenv .env, .env.*, .npmrc, .yarnrc.yml and .pypirc across your project folders. Templates such as .env.example are skipped. Scored higher when not git-ignored, much higher when committed.
shell bash, zsh and fish rc files, PowerShell profiles, and PSReadLine, bash, zsh and REPL histories, using a stricter detection profile built for history.
env-vars Secret-looking variables in the environment Haul inherited. The report names the variable, never its value.
ai-tools Claude Code, Claude Desktop, Cursor, VS Code (Copilot chat), Windsurf, Cline, Roo Code, Codex, Gemini CLI, Continue, Zed and Aider: transcripts, configs, MCP env blocks, the tools' own login files and SQLite chat stores.
browsers Presence only: Chrome, Edge, Brave, Chromium, Vivaldi, Opera and Firefox password and cookie stores, plus how this OS protects them. Never opened.
desktop-apps FileZilla (plaintext vs. master password), portable WinSCP, saved RDP passwords, OpenVPN profiles with inline keys. Presence and protection only.
sensitive-files Key stores (.pfx, .p12, .jks, .kdbx), crypto wallets, password-manager and browser exports, and stray private keys like an EC2 .pem in Downloads.

Haul walks your working folders once into a shared inventory. It skips node_modules and build output, never follows symlinks or junctions, never touches network drives, and skips cloud-only OneDrive files, so a scan can never trigger a download.

How it works

Haul's pipeline: read, provider rules, heuristics, filters, seal, then mask, score, firewall, report

  • Specific beats generic. Provider rules run first and claim their match, so an Anthropic key is reported once as Anthropic, never again as "OpenAI-shaped". Literal prefilters skip rules that can't match a line, which keeps multi-hundred-megabyte transcripts fast.
  • Two detection profiles. Config files also report plaintext passwords under strong keys (DB_PASSWORD=…). History (transcripts, shell history, logs) reports only tokens and high-entropy values, because prose and code samples are full of password: ….
  • Noise is filtered out. Haul drops placeholders (your-token-here), references (${DB_PASSWORD}, op://…), UUIDs, paths, URLs and token counters (max_tokens).
  • Nothing is skipped. AI tools often write a whole conversation on one JSON line. Haul scans long lines in overlapping 64 KB windows and streams large files, so memory stays flat.
  • Optional offline validation. --verify-format checks structure (JWT expiry, key shapes, GitHub token checksums) to cut false positives further, entirely in memory.

Severity and scoring

Haul scoring: base severity plus exposure modifiers, mapped to Info, Low, Medium, High and Critical

Every finding gets a 0–100 score: a base severity, moved by how exposed the secret really is. The bands are ≥ 85 Critical, ≥ 65 High, ≥ 40 Medium and ≥ 20 Low; anything lower is Info. The worst severity becomes the exit code.

A finding tells you everything except the secret:

  • its type, file, line, and how many times it occurs
  • a non-reversible preview: a domain-separated SHA-256 fingerprint, plus the first 4 characters only for long random tokens, where they merely name the provider (ghp_, AKIA). Passwords never show a single character.
  • its protection (passphrase, DPAPI, master password, none), inferred scope (a long-lived AKIA key vs. a temporary ASIA one), and a concrete fix
Exit code Meaning
0 Nothing at or above --fail-on (default low)
1 · 2 · 3 · 4 Worst finding is Low · Medium · High · Critical
64 Usage error: a bad flag, an unknown scanner name, a refused path
70 · 74 Internal error · I/O error
130 Interrupted (Ctrl+C)

Reports

Format Best for
terminal (default) Reading at a glance. Colors and clickable paths when interactive, plain text when piped.
html Handing to a person. One standalone file with search, filters and cards.
json Automation. Schema 2 with summary, findings and errors.
csv Spreadsheets. Hardened against formula injection.
sarif Code scanning. SARIF 2.1.0 with paths relative to SRCROOT and stable fingerprints.

Write several at once into a folder:

haul -f terminal -f html -f json -f csv -f sarif -o ./reports
What the JSON looks like
{
  "tool": "haul",
  "version": "0.2.0",
  "schema": 2,
  "summary": {
    "total": 20, "unique_secrets": 15, "files_scanned": 5,
    "by_severity": { "Critical": 11, "High": 4, "Medium": 5, "Low": 0, "Info": 0 },
    "highest_severity": "Critical", "exit_code": 4, "elapsed_seconds": 0.02
  },
  "findings": [
    {
      "secret_type": "AWS Access Key ID",
      "severity": "Critical", "score": 92,
      "file": "C:\\Users\\dev\\.aws\\credentials", "line": 2, "occurrences": 1,
      "preview": "AKIA…[sha256:a108299cdc4e len=20]",
      "fingerprint": "a108299cdc4e",
      "protection": "none", "scope": "long-lived IAM user key",
      "remediation": "Prefer `aws sso login` / IAM Identity Center or short-lived STS credentials; …",
      "baseline_key": "cloud-cli|AWS Access Key ID|C:\\Users\\dev\\.aws\\credentials|a108299cdc4e"
    }
  ],
  "errors": []
}

Command-line reference

Option What it does
paths… Extra project folders to include (on this machine)
--home DIR Scan this home instead of yours. Other users' profiles and network paths are refused.
--only · --skip SCANNERS Choose scanners (comma-separated or repeated). Unknown names are an error, never ignored.
--max-depth N How deep to walk project folders (1–32, default 6)
--verify-format Offline structural validation to cut false positives
--allow-plugins Load third-party scanners from the haul.scanners entry point
-f, --format terminal, html, json, csv, sarif (repeatable)
-o, --output A file for one format, or a folder for several
--min-severity Only report findings at or above this severity
--fail-on Exit non-zero only when a finding reaches this severity
--baseline FILE Hide the findings recorded in a previous JSON report
--no-color · -q, --quiet No colors · no banner, progress or notices
--clean [--apply] [--yes] [--no-backup] Redact secrets from AI-tool history (details)
--list-scanners · --version List the scanners · print the version
The full haul --help

haul --help output

Use it in CI

Haul makes a good gate. Fail on High or worse, and send SARIF to GitHub code scanning. Paths are relative to the folder Haul runs in, so findings land on the right files.

name: secrets
on: [push, pull_request]
permissions:
  contents: read
  security-events: write
jobs:
  haul:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install haul-scanner
      - name: Scan the repository
        run: haul --only dotenv,sensitive-files -f sarif -o haul.sarif --fail-on high -q "$GITHUB_WORKSPACE"
      - if: always()
        uses: github/codeql-action/upload-sarif@v3
        with: { sarif_file: haul.sarif }

Baselines accept known findings and fail only on new ones. They ignore line numbers, so editing a file doesn't bring an accepted finding back:

haul -f json -o baseline.json        # record today's state once
haul --baseline baseline.json        # from now on, only new findings

Cleaning AI-chat history

A key pasted into an AI chat stays in its transcript for good. --clean removes it. This is the only mode that writes to disk, and it is deliberately careful:

haul --clean dry run

  • A dry run by default. It lists what would change. Rewriting needs --apply, then typing redact (or --yes).
  • History only. It never touches configs, a tool's own login file (that would log you out) or SQLite stores, and only works on regular files you own inside your home.
  • Byte-exact. It works on raw bytes, so line endings and encodings survive unchanged. Whole private-key blocks are redacted, not just their first line.
  • Backup first. It writes a backup with the same permissions, never overwrites an earlier one, replaces the file atomically, and backs off if the file changes mid-way.
  • Never discloses. Each secret becomes a non-reversible marker: «haul-redacted:sha256:…».

Rotate first. The *.haul-bak-* backups still contain the original secrets. Rotate the exposed credentials, then delete the backups. Redaction is cleanup, not remediation.

Safety model

A tool that knows where every secret lives must be impossible to turn into a stealer. Haul enforces that in layers, each of which would stop a leak on its own:

Haul safety layers around the sealed SecretValue

Layer Guarantee
SecretValue Raw values are sealed the moment they're found. repr, str and format give <redacted>; pickle and copy refuse; comparison is constant-time.
Masking Previews are a domain-separated SHA-256 fingerprint, plus a provider prefix for long tokens only. Just five internal modules may unseal a value, and the release build fails if any other code tries.
Finding A finding validates itself and refuses to hold a sealed value anywhere, even nested. The only secret-derived field is the masked preview.
Output firewall Every rendered report is checked against the secrets seen in this run before a single byte is written.
Network kill-switch An irreversible audit hook blocks sockets, connects, DNS, subprocess, exec, os.startfile and loading network libraries. Network paths are refused before they are touched.
Build gates Fabricated secrets are planted in every supported location, and the release fails if any of them shows up in any output format.

The full threat model, and its honest limits, is in SECURITY.md.

Inside the package

One focused package, written against the standard library only and fully typed. Each layer has a single job:

Haul package layout: interface, engine, detection, scanners, safety and remediation layers

Plugins

Third-party scanners plug in through an entry point and load only with --allow-plugins. They inherit every safety layer above, including the network kill-switch.

from collections.abc import Iterator

from haul.core.context import ScanContext
from haul.core.models import Finding, ScanCategory
from haul.core.registry import register
from haul.scanners.base import Scanner


@register
class MyToolScanner(Scanner):
    name = "my-tool"
    category = ScanCategory.CLOUD_CLI
    description = "Credentials written by my-tool"

    def scan(self, ctx: ScanContext) -> Iterator[Finding]:
        path = ctx.home_path(".mytool", "config")
        if path.is_file():
            yield from self.scan_file(ctx, path, remediation="Run `mytool login --keychain`.")
[project.entry-points."haul.scanners"]
my-tool = "my_package.haul_plugin"

Performance

1 walk

Your folders are read once into a shared inventory, not once per scanner.

~15 MB/s

per core on AI transcripts, thanks to literal prefilters and keyword-driven parsing.

~100 s

for a full scan of a laptop holding 1.2 GB of AI transcripts.

Flat memory

Large files are streamed and long lines windowed. Nothing is loaded whole.

FAQ

Does Haul upload or send anything?

No. It makes zero network connections, and an audit hook enforces that at runtime: sockets, DNS, subprocesses and network libraries are blocked for the whole process. Reports are written only where you point -o, or to your terminal.

Does it read my browser passwords?

No. Browser stores are reported by presence only, together with the protection that guards them on your OS. Haul never opens, decrypts, parses or counts their contents.

Why do some previews show 4 characters and others none?

For long random tokens the first 4 characters only name the provider (ghp_, AKIA, sk_l) and reveal nothing useful. For passwords, the first characters are part of the secret, so Haul shows none.

It found a key in an AI chat I deleted long ago. Is that real?

Almost certainly. Many AI tools keep transcripts and SQLite chat stores long after a conversation disappears from the UI. Rotate the key, then run haul --clean to remove it from the history files.

Does it scan git history?

It reads .git to tell you a file is committed (and scores it higher), but it doesn't walk old commits. Rotate anything that was ever committed, and use a history scanner for the rest.

Why is the package called haul-scanner?

The name haul was already taken on PyPI. The command you run is still simply haul.

Is Haul open source?

No. Haul is free to use, for personal and commercial work, but it is not open source. It is distributed through PyPI under its own license; see License.

Does it run on macOS?

Haul knows the macOS locations of every supported tool, but macOS is not yet a verified platform. Windows (the primary target) and Linux are.

Limitations

  • Heuristic by nature. Expect occasional false positives and misses. --verify-format and baselines help.
  • The working tree only. Git history beyond the index is out of scope.
  • Presence-only stores (browsers, gcloud and Azure caches, key stores) are reported, never inspected.
  • No Windows Registry reads. WinSCP sessions stored in the registry, for example, are not covered.
  • Defense in depth, not a sandbox. The kill-switch binds everything that goes through Python's audit layer; native extension code could bypass it, which is why plugins are opt-in.

Security

Found a way to make Haul emit, copy or transmit a secret, reach the network, or write outside --clean's scope? Please report it privately through a security advisory, not a public issue.

Reports are sensitive too. They contain no secret values, but they are a precise map of where secrets live on a machine. Store and share them like any sensitive document.

License

Free to use, all rights reserved. Haul is proprietary freeware © 2026 Arman Manteghi.

You may You may not
Install and run Haul for free, personally or commercially Modify it, or redistribute, resell or sublicense it
Use it on systems you own or are authorized to assess Remove its copyright or license notices
Keep the unmodified package in an internal mirror Use it against systems you aren't authorized to assess

The binding text is in LICENSE.



Built by Arman Manteghi
Read-only · zero network · zero dependencies · no secret ever leaves the process

Metadata

Release files for haul-scanner 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for haul-scanner 0.2.0
File Interpreter ABI Platform
haul_scanner-0.2.0-py3-none-any.whl Python 3 none any Details

Release files / haul_scanner-0.2.0-py3-none-any.whl

Download URL haul_scanner-0.2.0-py3-none-any.whl
Size 105.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9f737f4c51f3a288469ea3f439a657206a93a3700c95154d1d74d0968a2765af
BLAKE2b-256 checksum
How to use checksums
3d98e0e8d9b5c8ae15e5600f4585152fa5c95e8b97f2086a8b55529b8c617a11
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.4

Security for haul-scanner 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page