# lenny_nif

Elixir NIF bindings for [Lenny](https://github.com/ZenSRE/lenny), a secret redaction engine built for AI coding tools.

Lenny scans output streams and redacts known secrets using BLAKE3 + rolling hash -- no secret values are stored in memory at runtime. It also includes 216 built-in pattern rules for detecting unknown secrets (API keys, tokens, connection strings).

## Installation

Add `lenny_nif` to your dependencies in `mix.exs`:

```elixir
def deps do
  [{:lenny_nif, "~> 0.0.1"}]
end
```

### Requirements

- Elixir 1.17+
- Rust 1.88+ (installed via [rustup](https://rustup.rs/)) -- Rustler compiles the NIF from source

## Usage

### Exact-match redaction

Load known secrets, then scan text to redact any occurrences:

```elixir
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  #=> true
```

### Pattern scanning

Detect secrets by pattern without loading known values:

```elixir
scanner = Lenny.new_pattern_scanner()
matches = Lenny.scan_patterns(scanner, "AWS_KEY=AKIAIOSFODNN7EXAMPLE")
hd(matches).rule_id  #=> "aws-access-key"
```

## API Reference

### Engine

| Function | Description |
|---|---|
| `Lenny.new_engine/0` | Create engine with built-in pattern rules |
| `Lenny.new_engine_no_patterns/0` | Create engine for exact-match only |
| `Lenny.load_secrets/2` | Load secrets (list of maps or 2-tuples) |
| `Lenny.scan/2` | Scan binary input |
| `Lenny.scan_string/2` | Scan string input |

### Pattern Scanner

| Function | Description |
|---|---|
| `Lenny.new_pattern_scanner/0` | Create standalone pattern scanner |
| `Lenny.scan_patterns/2` | Returns list of match maps with `rule_id`, `description`, `tier`, `start`, `end` |

Scan functions run on dirty CPU schedulers to avoid blocking the BEAM.

### Secret format

Each secret is a map with required keys `name` and `value`:

```elixir
%{
  name: "db_pass",
  value: "hunter2",
  tier: "alert",        # "log" (default) | "alert" | "page"
  canary: false,        # default: false
  redaction: "tagged",  # "tagged" (default) | "full" | "partial"
  prefix: 4,            # chars to keep (partial only)
  suffix: 4,            # chars to keep (partial only)
  transformations: []   # ["base64", "url"]
}
```

Legacy 2-tuple format is also supported: `{"db_pass", "hunter2"}`.

## Documentation

See the [Lenny project](https://github.com/ZenSRE/lenny) for configuration, threat model, and the complete list of pattern rules.

## License

MIT and Apache-2.0
