AI AgentIntermediate14 min read

Scan Email Attachments for Leaked Secrets — Privately, On Your Machine

Build a private, offline scanner that checks email attachments for personal data and API keys using open-source Presidio — nothing is ever sent to the cloud.

Scan Email Attachments for Leaked Secrets — Privately, On Your Machine

Overview

Sensitive data leaks out of inboxes constantly — an API key pasted into a support ticket, a spreadsheet of customer records forwarded to a vendor, a contract PDF with ID numbers in an appendix. The usual scanners for this are cloud services, which means uploading the very files you're worried about to someone else's server. That's a hard sell for healthcare, legal, or financial data.

This tutorial takes the opposite approach: you'll build a private scanner that runs entirely on your own machine, using the open-source framework Presidio. Nothing is sent to the cloud — the detection happens offline, on your laptop, so the sensitive files never leave your control.

Build a private, offline scanner that checks email attachments for sensitive data — right on your own machine, with nothing sent to the cloud.
Build a private, offline scanner that checks email attachments for sensitive data — right on your own machine, with nothing sent to the cloud.

One honest note up front, because it shapes everything below: Presidio is a PII detector, not a dedicated secrets scanner. Out of the box it's excellent at finding personal data — names, credit cards, ID numbers, emails, phone numbers. To also catch API keys and credentials (Stripe, AWS, and the like), you add a couple of custom rules — which we'll do — or reach for a purpose-built secrets tool. We'll show both, so you finish with a scanner that actually covers your case.


Difficulty: Medium — a little Python required · You'll need: Python 3.9+, ~2 GB free disk for the language model, and a test mailbox · Cost: free and open-source · Updated: August 2026

Who This Is For

  • IT admins and security-minded founders who want to catch leaked credentials and PII in email but can't send those files to a third-party cloud.
  • Compliance and ops teams in healthcare, legal, or finance, where "the data never leaves our machines" is a hard requirement.
  • Developers who want a local, scriptable base they can wire into a mailbox, a ticketing system, or a pre-send hook.
  • What You'll Learn

  • Why running the scan locally matters — and how the pieces fit together.
  • How to set up Presidio to detect personal data offline.
  • How to extend it with custom rules so it also flags API keys and credentials.
  • The honest limits: what Presidio catches, what it doesn't, and which dedicated secrets tools to use for a secrets-first job.
  • The Big Idea: Keep the Scan Local

    The whole value here is that your files stay put. A cloud scanner works like this: upload file → their server analyzes it → you get a result. A local scanner cuts out the upload entirely.

    The flow: an email attachment is scanned by a local engine on your own machine, sensitive fields are redacted in a report, and nothing is ever sent to the cloud.
    The flow: an email attachment is scanned by a local engine on your own machine, sensitive fields are redacted in a report, and nothing is ever sent to the cloud.

    Your script pulls an attachment from a mailbox, extracts its text, runs it through Presidio's detection engine on your CPU, and writes a redacted report. After the one-time install, no step needs the internet (except, of course, reading the mailbox itself over an encrypted connection). For sensitive data, that offline guarantee is the entire point.

    Meet Presidio

    Presidio is a free, open-source (MIT-licensed) framework for detecting, redacting, and anonymizing sensitive data. It was created at Microsoft and is now maintained by the Data Privacy Stack community. Under the hood it combines three techniques: regular-expression patterns, named-entity recognition (via a spaCy NLP model), and rule-based logic with checksums (for example, validating a credit-card number). That mix is why it's more reliable than a pile of hand-written regexes.

    Presidio on GitHub — an open-source framework for detecting, redacting, and anonymizing sensitive data (PII), using NLP and pattern matching.
    Presidio on GitHub — an open-source framework for detecting, redacting, and anonymizing sensitive data (PII), using NLP and pattern matching.

    It ships with recognizers for many personal-data types — credit cards, crypto wallets, email addresses, IP addresses, IBANs, phone numbers, US SSNs, bank numbers, people's names, and more. What it does not include out of the box is recognizers for service API keys and secrets — those we'll add ourselves in Step 4.

    Presidio — open-source PII detection & redaction

    The MIT-licensed framework this tutorial is built on. Detects and redacts sensitive data locally using NLP and pattern matching. Originally by Microsoft.

    github.com

    Before You Start


    Two important corrections to the kind of setup you'll see floating around online:

  • The two core packages are presidio-analyzer and presidio-anonymizerthere is no python-dotemail package; ignore any guide that tells you to install it.
  • Presidio's NER needs a spaCy language model, which you download separately. Skipping this is the #1 reason a first run crashes.
  • Install everything:

    
    pip install presidio-analyzer presidio-anonymizer pdfminer.six python-docx
    python -m spacy download en_core_web_lg
    

    That last command downloads the English language model (~500 MB–1 GB) that Presidio uses to recognize names and other entities. It's a one-time download; after it, you're fully offline.

    Step-by-Step Guide

    Step 1: Set up the detection engine

    Create a file called email_guard.py and start with the two engines — one to find sensitive data, one to redact it:

    python
    from presidio_analyzer import AnalyzerEngine
    from presidio_anonymizer import AnonymizerEngine

    analyzer = AnalyzerEngine()
    anonymizer = AnonymizerEngine()

    def scan_text(text: str) -> dict:
    """Return the redacted text and the list of findings for one document."""
    results = analyzer.analyze(text=text, language="en")
    redacted = anonymizer.anonymize(text=text, analyzer_results=results).text
    findings = [(r.entity_type, r.score) for r in results]
    return {"redacted": redacted, "findings": findings}

    analyzer.analyze returns what was found and where; anonymizer.anonymize uses those spans to replace each hit with a placeholder like <CREDIT_CARD>. Everything runs on your machine.

    Step 2: Extract text from attachments

    Emails carry PDFs and Word docs, not plain strings. Add a small helper to pull text out of each:

    python
    from pdfminer.high_level import extract_text
    from docx import Document

    def extract_file_text(filepath: str) -> str:
    if filepath.lower().endswith(".pdf"):
    return extract_text(filepath)
    if filepath.lower().endswith(".docx"):
    return "\n".join(p.text for p in Document(filepath).paragraphs)
    if filepath.lower().endswith((".txt", ".csv")):
    with open(filepath, "r", errors="ignore") as f:
    return f.read()
    return "" # unsupported type — skip

    Step 3: Catch API keys too — add custom recognizers

    This is the step most guides skip. Presidio finds personal data by default; to also flag API keys and credentials, you register your own pattern recognizers. Here are two real, correct examples — a Stripe live-key shape and an AWS access-key ID:

    python
    from presidio_analyzer import Pattern, PatternRecognizer

    stripe_key = PatternRecognizer(
    supported_entity="STRIPE_KEY",
    patterns=[Pattern(name="stripe_live", regex=r"sk_live_[0-9a-zA-Z]{24,}", score=0.9)],
    )
    aws_key = PatternRecognizer(
    supported_entity="AWS_ACCESS_KEY",
    patterns=[Pattern(name="aws_akia", regex=r"AKIA[0-9A-Z]{16}", score=0.9)],
    )

    analyzer.registry.add_recognizer(stripe_key)
    analyzer.registry.add_recognizer(aws_key)

    Now a document containing sk_live_... or AKIA... gets flagged just like a credit card would. Add one recognizer per credential format your organization uses (internal token prefixes, database URLs, and so on) — this is how you tune the scanner to your leaks.

    Step 4: Connect to a mailbox over IMAP (read-only)

    To pull messages, connect over IMAP. Use an app-specific password, never your main one, and keep the access read-only:

    python
    import imaplib, email

    def open_mailbox() -> imaplib.IMAP4_SSL:
    mail = imaplib.IMAP4_SSL("imap.gmail.com")
    mail.login("your_test_account@gmail.com", "your_app_password")
    mail.select("inbox", readonly=True) # readonly = we never modify the mailbox
    return mail


    Notice open_mailbox returns the connection — the original version of this script created mail inside one function and then referenced it from another, which would crash with a NameError. Passing the object around explicitly avoids that.

    Step 5: Put the pipeline together

    Now walk each message, save its attachments to a temp folder, scan them, and print a summary — no cloud calls anywhere:

    python
    import os, tempfile

    def run():
    mail = open_mailbox()
    _, ids = mail.search(None, "ALL")
    for email_id in ids[0].split():
    _, msg_data = mail.fetch(email_id, "(RFC822)")
    msg = email.message_from_bytes(msg_data[0][1])
    for part in msg.walk():
    filename = part.get_filename()
    if not filename:
    continue
    with tempfile.TemporaryDirectory() as tmp:
    path = os.path.join(tmp, filename)
    with open(path, "wb") as f:
    f.write(part.get_payload(decode=True))
    text = extract_file_text(path)
    if not text.strip():
    continue
    result = scan_text(text)
    if result["findings"]:
    kinds = ", ".join(sorted({t for t, _ in result["findings"]}))
    print(f"⚠️ {filename}: found {kinds}")

    if __name__ == "__main__":
    run()

    Run it with python email_guard.py. Send yourself a test email with a Word doc containing a fake key like sk_live_0000000000000000000000000000, and you'll see it flagged in the console — entirely offline.

    What Presidio Catches — and What It Doesn't

    It's worth being clear-eyed about the tool's boundaries, because "scanner" can mean two different jobs:

    PII detection (names, IDs, cards, emails) is Presidio's job; catching API keys and live credentials is the job of a dedicated secrets scanner.
    PII detection (names, IDs, cards, emails) is Presidio's job; catching API keys and live credentials is the job of a dedicated secrets scanner.
  • Presidio's home turf is PII — personal data, with NLP that understands context (it can tell a person's name from a random capitalized word). For attachments full of customer records, it's excellent.
  • For a secrets-first job — scanning code, configs, and repos for hundreds of credential formats — a purpose-built tool is the right call. Gitleaks is fast and pattern-based; TruffleHog goes further and verifies whether a found key is actually live; detect-secrets (from Yelp) is built for baselining large codebases. All three run locally, just like Presidio.
  • The honest takeaway: use Presidio (plus your custom recognizers) for PII and email attachments, and add a dedicated secrets scanner when your real target is source code and credentials.

    Gitleaks — fast local secrets scanner

    A lightweight, pattern-based tool for finding API keys and credentials in code and files. Runs entirely on your machine; great as a pre-commit hook.

    github.com
    detect-secrets (Yelp) — baseline-driven secrets scanning

    Built to retrofit secrets scanning into large existing codebases with a baseline workflow, so you only get alerted on newly introduced secrets.

    github.com

    Common Mistakes to Avoid


  • Skipping the spaCy model. pip install alone isn't enough — run python -m spacy download en_core_web_lg, or Presidio will crash on the first analyze call.

  • Chasing fake packages. There is no python-dotemail. If a tutorial tells you to install it, that guide was AI-generated without being tested.

  • Using your real email password. Always create an app-specific password and open the mailbox readonly=True. Never hard-code credentials into a script you might share or commit.

  • Assuming it catches everything. Presidio won't flag an API key unless you add a recognizer for it (Step 3). Know your gaps and fill them.
  • Pro Tips

  • Tune to your own secrets. Add one custom PatternRecognizer per internal credential format — that's where a generic scanner becomes your scanner.
  • Scan screenshots too. Presidio has an image redactor (presidio-image-redactor) that pairs OCR with the same engine to catch secrets pasted into screenshots.
  • Keep a redacted archive. Store the redacted output, not the original, when you need to log what was found without keeping the sensitive value.
  • Start on a test inbox. Prove the pipeline on a throwaway account with planted test data before you ever point it at a real mailbox.
  • Layer the tools. Presidio for PII in attachments, Gitleaks/TruffleHog for credentials in code — they're complementary, not competitors.
  • Key Takeaways

  • The win is privacy: this scanner runs entirely offline, so sensitive files never leave your machine — unlike cloud scanners.
  • Presidio is a PII detector (MIT-licensed, NLP + regex + checksums) that's excellent for personal data in email attachments.
  • It doesn't catch API keys by default — add custom PatternRecognizer rules (Step 3) for the credential formats you care about.
  • Watch the real gotchas: download the spaCy model, ignore the fake python-dotemail package, and always use an app-specific, read-only mailbox login.
  • For a secrets-first job, use a dedicated local tool — Gitleaks, TruffleHog, or detect-secrets — alongside Presidio.
  • Sources: Presidio (GitHub) · Gitleaks · TruffleHog · detect-secrets (Yelp)

    Learn AI, after work

    Track your progress, earn XP, and unlock more free tutorials in the AfterWork Bytes app.

    Open this tutorial in the app

    More AI tutorials