ZOYKU火線榜

awslabs/ferret-scan技術文件

awslabs/ferret-scan open-source repository on GitHub

多語言回退提示:當前倉庫原作者暫未發布在地化文件,已為您無縫切換至官方英文原版(English),排版及程式碼實例完整保真。
極速安裝
git clone https://github.com/awslabs/ferret-scan.git
Ferret Scan

ferret-scan

Find and redact sensitive data before it leaks.

License Go version from go.mod Latest release PyPI version Homebrew formula version (tracks the latest release) ECR Public image version (tracks the latest release) Go Tests on main GitHub stars

A single-binary Go CLI (plus embedded web UI and Go library — pkg/scan for detection, pkg/redact for redaction) that detects PII, secrets, and IP markers in your files and streams — then redacts them in place, format-preserving, with context-aware confidence scoring. No runtime dependencies. No data leaves your host.

ferret-scan demo


A customer SSN pasted into a log line. An AWS access key committed in a diff. A support transcript archived to S3. A photo with EXIF location data attached to a ticket. Sensitive data leaks through the seams between systems — and once it lands in a log store or an object bucket, it is expensive to get back out. ferret-scan is the control you put in front of those seams: it finds the sensitive values, scores how likely each one is real, and redacts them so the rest of the data keeps flowing.


Try it in 30 seconds

# 1. Install
pip install ferret-scan

# 2. Scan a file (values are HIDDEN by default — findings are safe to share)
ferret-scan --file secrets.env

# 3. Redact a stream in one pipe (redacted text -> stdout, findings -> stderr)
echo "card 5500-0000-0000-0004 from [email protected]" \
  | ferret-scan --stdin --enable-redaction

That last command turns sensitive values into masked-but-shaped output:

Before After (--enable-redaction)
card 5500-0000-0000-0004 from [email protected] card ****-****-****-0004 from j*****@example.com

Sensitive values are masked while the shape of the data survives — so downstream tooling, tests, and log pipelines keep working. (Examples here use reserved documentation values.)


Why ferret-scan

  • Confidence you can act on. Scoring is context-aware, not just regex. A credit card in a financial document scores higher than the same digits in a test fixture, because the engine re-weights on document type and surrounding domain. The document type (CSV, TSV, JSON, configuration, code, ...) is read from the whole document, not from its first screen: every line up to 32 KiB and 2,000 lines, and evenly spaced windows of a longer one. Findings land in three bands: HIGH (90–100), MEDIUM (60–89), LOW (0–59).
  • Redaction that composes. The stdin gateway streams redacted bytes to stdout and findings to stderr, so it drops into any Unix pipe or CI step. Three strategies: simple, format_preserving (default), and synthetic (realistic fakes for building test datasets).
  • Explainable, offline. --explain attaches a plain-language rationale, a verdict (likely_real / likely_test / uncertain), and a drafted suppression reason to every finding. Fully deterministic — no network, no LLM.
  • Ships everywhere. One static binary, no runtime deps, a scratch-based Docker image (~5–10 MB), a pip package, a Homebrew tap, a pre-commit hook, and a stable Go library (pkg/scan + pkg/redact).
  • Safe by design. Matched values are hidden unless you pass --show-match. In-memory redaction produces payload-free audit records. The web UI binds to 127.0.0.1 with CSRF and CSP protections.

Install

Pick whichever fits your workflow — all are first-class.

Method Command
Homebrew (macOS/Linux) brew tap awslabs/ferret-scan https://github.com/awslabs/ferret-scan && brew install awslabs/ferret-scan/ferret-scan
pip (CLI + pre-commit) pip install ferret-scan
Docker docker pull public.ecr.aws/awslabs/ferret-scan:latest
From source (Go toolchain per .go-version) git clone https://github.com/awslabs/ferret-scan.git && cd ferret-scan && go build ./cmd

Docker one-liner (mount the current directory and scan it):

docker run --rm -v "$PWD:/data" public.ecr.aws/awslabs/ferret-scan:latest --file /data --recursive

What it detects

Nineteen validators, each purpose-built. Enable a subset with --checks CREDIT_CARD,SECRETS,SSN or run them all (the default).

Validator What it catches Notes
CREDIT_CARD Card numbers from 8 brands: Visa, Mastercard, American Express, Discover, JCB, Diners Club, UnionPay, Maestro Luhn-validated; emits VISA, MASTERCARD, AMERICAN_EXPRESS, DISCOVER, JCB, DINERS_CLUB, UNIONPAY, MAESTRO; a known test number (the card numbers payment processors publish for their sandboxes, a short test-pattern list, repeating shapes) is still reported, so still redacted, but capped at LOW 15
SECRETS API keys, tokens, credentials Provider tokens (AWS long-term AKIA and temporary ASIA access keys, GitHub, Google Cloud, Stripe, GitLab, Docker Hub, Slack), PEM/PGP private keys, JWTs, AWS secret keys, credentials in URLs (scheme://user:pass@host), keyword-labelled values, and entropy analysis. Temporary ASIA keys are HIGH beside their secret key or session token and LOW 55 alone; a joined key name (apikey, accesstoken) counts like api_key; an exact-length sha1- to sha512- integrity digest (lockfile integrity, <script integrity>, CSP) is skipped
SSN US Social Security Numbers and IRS ITINs Domain-aware (HR / Tax / Healthcare context); emits SSN and ITIN (9XX area, 4th–5th digits 50–65, 70–88, 90–92 or 94–99)
BANK_ACCOUNT Routing numbers, IBANs, SWIFT/BIC ABA checksum; IBAN registry length + BBAN structure + mod-97 (all 89 SWIFT IBAN Registry countries, compact or grouped in fours); SWIFT format; emits ABA_ROUTING, IBAN, SWIFT_BIC, US_BANK_ACCOUNT
OTP 2FA secrets and recovery codes otpauth:// URIs, TOTP/HOTP secrets (base32), recovery code blocks; emits OTPAUTH_URI, OTP_SECRET, RECOVERY_CODES
DATE_OF_BIRTH Dates of birth in PII context Very conservative — requires DOB keywords; dates without context score <30
PHYSICAL_ADDRESS US street addresses Requires street-type suffix (St/Ave/Blvd/…); emits US_STREET_ADDRESS, PO_BOX
DRIVERS_LICENSE Driver's license numbers Eight US state shapes (CA, TX/PA, FL/MI, NY/GA, IL, OH, NJ, WI), Great Britain DVLA, and Ontario and Quebec as printed; format names the shape, not the issuing state; keyword-dependent to avoid FPs
MEDICAL_ID Healthcare identifiers NPI (Luhn-validated), DEA (checksum; all 18 registrant letters, either case), MRN, insurance IDs, Medicare MBI (either case), UK NHS and Scottish CHI numbers (mod-11, beside a label naming them)
EMAIL Email addresses Emits BUSINESS for known SaaS/corporate domains
PHONE Phone numbers US/Canada (incl. toll-free), UK and European national forms; +CC and 00-prefixed international forms, gated on E.164 country codes; trunk (0) forms; tel: links and vCard TEL values; extensions
IP_ADDRESS IPv4 / IPv6 addresses Skips RFC1918 / reserved / test ranges; context-keyword gated
PASSPORT Passport numbers US / UK / CA / EU (letters in either case; a value with a lowercase letter needs an adjacent passport label) + MRZ
VIN Vehicle Identification Numbers ISO 3779; the position-9 check digit is required in North America (1–5, 7) and China (H, L), and elsewhere a VIN without one needs a known manufacturer prefix (WMI) or a VIN label
PERSON_NAME Personal names Embedded name databases, titles, nobiliary particles, cultural variations
CLOUD_RESOURCES Cloud resource identifiers AWS ARNs, Azure IDs, GCP, OCI, IBM CRN, Alibaba
INTELLECTUAL_PROPERTY IP / confidentiality markers Patents, trademarks, copyrights, trade secrets
SOCIAL_MEDIA Social media handles / profiles Requires configuration to activate
METADATA EXIF / document metadata File-path only (needs filesystem); available via CLI and pkg/scan.ScanFile, not via ScanText/pkg/redact (in-memory)

Example output

By default, matched values are hidden — the report is safe to paste into a ticket or share with a teammate.

LEVEL    VALIDATOR    TYPE                 CONF%    LINE       MATCH      FILE
[HIGH  ] ssn          SSN                   100.00% line     3 [HIDDEN]   demo.txt
[HIGH  ] email        BUSINESS              100.00% line     1 [HIDDEN]   demo.txt
[HIGH  ] secrets      AWS_ACCESS_KEY        100.00% line     4 [HIDDEN]   demo.txt
[MEDIUM] phone        PHONE                  75.00% line     3 [HIDDEN]   demo.txt

Pass --show-match to reveal the underlying values, and --explain to see why each was flagged.

Output formats

Choose a format with --format:

text (default) · json · csv · yaml · junit · gitlab-sast · sarif

The sarif, gitlab-sast, and junit formats slot directly into GitHub code scanning, GitLab SAST reports, and CI test dashboards.


Ways to run it

Scan files, directories, and globs

ferret-scan --file ./src --recursive
ferret-scan --file "logs/*.txt" --checks SECRETS,CREDIT_CARD
ferret-scan --file report.pdf --explain --format json --output findings.json

Redact a stream (composes in pipes; redacted bytes to stdout, findings to stderr)

cat customer-export.csv | ferret-scan --stdin --enable-redaction --redaction-strategy synthetic > safe-export.csv

Pre-commit hook — block secrets before they land

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/awslabs/ferret-scan
    rev: v2.5.5
    hooks:
      - id: ferret-scan

CI/CD — emit a SARIF or GitLab SAST report

ferret-scan --file . --recursive --format sarif --output ferret.sarif --quiet

Container — scan a mounted directory with no local install

docker run --rm -v "$PWD:/data" public.ecr.aws/awslabs/ferret-scan:latest --file /data --recursive

Web UI — folder drag-and-drop and bulk suppression management

ferret-scan --web --port 8080

Go library — embed redaction in-process (see below).


Web UI

Launch a local, CloudScape-styled interface for interactive scanning, folder drag-and-drop, and bulk suppression management:

ferret-scan --web

It binds to 127.0.0.1 by default (auto-detecting 0.0.0.0 only inside containers, where the port publish is the boundary: publish it as -p 127.0.0.1:8080:8080), enforces CSRF/Origin checks, and sends CSP headers. The UI has no authentication, so to use it from another machine, forward the port over SSH (ssh -L 8080:127.0.0.1:8080 <host>) rather than widening the bind; --bind 0.0.0.0 exposes it to every host that can reach this one, and prints a warning at startup.

ferret-scan web UI — main interface

ferret-scan web UI — scan results


Embed it: the Go library

Two public packages let you embed ferret-scan in your Go application — no subprocess, no CLI, no payload leakage:

Package Purpose Input
pkg/scan Detection — find sensitive data Text strings or file paths
pkg/redact Redaction — mask/replace findings Text strings (Engine-based, one-call detect+redact)

Detection and redaction are separate concerns — use one or both:

Detect only (pkg/scan)

import "github.com/awslabs/ferret-scan/v2/pkg/scan"

// In-memory text detection (no disk, no temp files)
result, err := scan.ScanText(ctx, "card 5500-0000-0000-0004", scan.TextOptions{
    Checks:  []string{"CREDIT_CARD", "SSN"},
    Explain: true,  // attach "why flagged" rationale
})
if err != nil {
    return err // includes ctx's own error when it fired mid-scan; result then holds the partial findings
}
if result.Incomplete {
    log.Printf("not fully examined: %+v", result.NotExamined) // e.g. a validator size cap fired
}
for _, f := range result.Findings {
    fmt.Printf("%s (line %d, %s, %s)\n", f.Type, f.LineNumber, f.Band(), f.Rationale)
}

// File-based detection (PDF, DOCX, XLSX, images, text — 90+ types)
result, err = scan.ScanFile(ctx, "report.docx", scan.FileOptions{})
if err != nil {
    return err
}

// Check if a file type is supported before scanning
ok, reason := scan.CanProcessFile("archive.zip")  // false, "Unsupported file type"

// Redact text in-place using pre-computed findings (no re-detection)
redacted, err := scan.RedactText(text, result.Findings, scan.StrategyFormatPreserving)
if err != nil {
    return err
}

// Redact a file (writes a redacted copy of the same type: .docx→.docx, .jpg→.jpg)
// PDF, TIFF, GIF, BMP and WEBP are reported but cannot be rewritten — see docs/redaction-support.md
fileResult, err := scan.RedactFileContext(ctx, "report.docx", scan.RedactFileOptions{
    OutputDir: "/tmp/redacted",
    Strategy:  scan.StrategyFormatPreserving,
})
if errors.Is(err, scan.ErrIncomplete) {
    // Not fully examined (cut short, unparseable, no text): fileResult.Coverage says why,
    // and nothing at fileResult.RedactedFilePath is safe to share.
    return err
} else if err != nil {
    return err
}

pkg/scan exposes: ScanText, ScanFile, RedactText, RedactFile, RedactFileContext, CanProcessFile, CheckNames, ConfidenceOf, ParseStrategy. All delegate to the internal engine with zero duplication. Check the error before using any result: a redaction that could not examine its whole input returns an error wrapping ErrIncomplete (redact.ErrIncomplete below), never a quiet success.

The METADATA validator requires filesystem access — available via ScanFile but not ScanText.

Detect + redact in one call (pkg/redact)

pkg/redact is the higher-level API when you want both detection and redaction in one step with a reusable, pre-warmed engine. An Engine is built once and reused; it is safe for concurrent use.

import "github.com/awslabs/ferret-scan/v2/pkg/redact"

engine, err := redact.NewEngine(redact.EngineOptions{
    Checks:   []string{"CREDIT_CARD", "EMAIL"},
    Strategy: redact.FormatPreserving,
})
if err != nil {
    log.Fatal(err)
}
defer engine.Close()

result, err := engine.Redact(ctx, redact.Request{
    Text:  "card 5500-0000-0000-0004 from [email protected]",
    Label: "req-abc-123",
})
if err != nil {
    // errors.Is(err, redact.ErrIncomplete): the scan was cut short (ctx's deadline, a size cap)
    // and result.Redacted may still hold values in clear. Log result.AuditRecord(); never release it.
    return err
}
log.Println(result.Redacted)              // ****-****-****-0004 from j*****@example.com
log.Printf("%+v", result.AuditRecord())   // payload-free audit summary

Result.AuditRecord() returns a payload-free record — per-type finding counts, byte counts, duration — and never the matched bytes. LogWriter defaults to io.Discard, so the no-leak property is enforced by construction. Matched substrings are only reachable via explicit opt-in (Result.FindingsWithMatchText()). Input is capped at 100 MB.

Build a PII redaction gateway

Because both packages run entirely in memory — no subprocess, no filesystem, and payload-free audit records — you can build a single-tenant redaction service without ferret-scan ever writing sensitive bytes to disk or logs. A representative shape:

  • Embed the Engine in an AWS Lambda (provided.al2023, arm64), constructed once in init() and reused across invocations.
  • Front it with an API Gateway HTTP API using IAM (SigV4) auth.
  • Callers POST JSON { "text": "...", "strategy": "format_preserving" } and receive { "redacted": "...", "request_id": "...", "duration_ms": <n> }.
  • CloudWatch records audit counts only — never payload bytes — because AuditRecord carries no matched substrings.
  • Gateway throttling bounds abuse and DoS.

This is an architecture the public API enables; ferret-scan itself ships the CLI, web UI, and library — not the gateway. Note that Lambda synchronous invokes cap request bodies at ~6 MB.


Exit codes

Code Meaning
0 The scan ran. Findings do not change the exit code — they are reported in the output.
1 The run failed: bad arguments (including an input path that does not exist), an unreadable config, an output file that cannot be written, a redaction audit log that cannot be written or validated, or a report that could not be formatted. No findings report was produced; redacted copies written before the failure are left in place (the audit log is exported after them).
2 --preprocess-only only: there were no files to preprocess. (An ordinary scan of an empty directory, a glob with no matches, or a tree where every file was excluded exits 0 with total_files: 0 — an honest empty report, not a failure.)
3 --fail-on-incomplete only: the run completed but did not fully do what was asked — a file was not fully scanned, or findings were reported and not redacted. See Coverage disclosure.

Pre-commit mode (--pre-commit-mode) maps findings onto the exit code instead, so a commit can be blocked; 3 still overrides a 0 there.

The distinction that matters for CI: 0 means the tool worked, not that the file was clean. Parse the output for findings, and treat 1 as "this report is not trustworthy" — a formatting failure now exits 1 rather than writing the error where the document should be.


Security posture

  • Values hidden by default. Findings never include matched text unless you pass --show-match (CLI) or opt into Result.FindingsWithMatchText() (library).
  • Payload-free audit records. The library's AuditRecord reports counts, sizes, and timing — never the sensitive bytes.
  • Memory scrubbing. Sensitive buffers use a SecureString with multi-pass zeroing (a MEDIUM security posture, bounded by what the Go runtime allows).
  • Suppression system. Rule-based false-positive management, with bulk operations in the web UI, so you tune signal without editing source.
  • Hardened web server. 127.0.0.1 binding by default, CSRF/Origin checks, and CSP headers.

For the full model — trust boundaries, threats, and mitigations — see THREAT_MODEL.md.


Documentation

Topic Link
Full documentation index docs/README.md
Configuration & profiles docs/configuration.md
Threat model THREAT_MODEL.md
Writing your own validator docs/development/creating_validators.md

License

Apache-2.0. Copyright Amazon.com, Inc. or its affiliates. An awslabs open-source project — contributions welcome. See LICENSE.txt.

文件鏡像歸檔自 GitHub 官方源路徑 · 造訪原專案 GitHub
最後同步快照:2026-11-09