lenny_nif
Elixir NIF bindings for the Lenny secret redaction engine.
Scans and redacts known secrets using BLAKE3 + rolling hash (no secret values stored in memory at runtime). Includes 216 built-in pattern rules for detecting unknown secrets (API keys, tokens, connection strings).
Installation
Add to your mix.exs:
def deps do
[{:lenny_nif, "~> 0.1.0"}]
endRequires Elixir 1.14+ and Rust 1.88+ (for NIF compilation via Rustler).
Usage
Exact-match redaction
engine = Lenny.new_engine()
:ok = Lenny.load_secrets(engine, [
%{name: "db_pass", value: "hunter2", tier: "alert"}
])
result = Lenny.scan_string(engine, "password is hunter2")
result.output #=> "password is [REDACTED:db_pass]"
result.has_redactions #=> truePattern scanning
scanner = Lenny.new_pattern_scanner()
matches = Lenny.scan_patterns(scanner, "AWS_KEY=AKIAIOSFODNN7EXAMPLE")
hd(matches).rule_id #=> "aws-access-key"API
Engine
Lenny.new_engine()-- creates engine with built-in pattern rulesLenny.new_engine_no_patterns()-- creates engine for exact-match onlyLenny.load_secrets(engine, secrets)-- load secrets (list of maps or 2-tuples)Lenny.scan(engine, binary)-- scan binary inputLenny.scan_string(engine, string)-- scan string input
Secret format
Each secret is a map with required keys name and value:
%{
name: "db_pass",
value: "hunter2",
tier: "alert", # "log" (default) | "alert" | "page"
canary: false, # default: false
redaction: "tagged", # "tagged" (default) | "full" | "partial"
prefix: 4, # for "partial" redaction
suffix: 4, # for "partial" redaction
transformations: [] # ["base64", "url"]
}
Legacy 2-tuple format is also supported: {"db_pass", "hunter2"}.
Pattern Scanner
Lenny.new_pattern_scanner()-- create standalone pattern scannerLenny.scan_patterns(scanner, binary)-- returns list of match maps withrule_id,description,tier,start,end
Full Documentation
See the Lenny project for configuration, deployment, threat model, and the complete list of pattern rules.
License
MIT