lens3329 0.2.1

Cryptographic inventory scanner: crypto library discovery, lockfile version resolution (Cargo/npm/PyPI), offline advisory/CVE correlation, and CycloneDX CBOM / SARIF export.
Documentation
# 3329lens

Cryptographic inventory scanner. It walks a codebase, finds the cryptography it
depends on, resolves real versions from lockfiles, correlates those against an
offline advisory database, and exports the result as a CycloneDX CBOM or a
SARIF log.

It answers two questions that are hard to answer by reading a dependency tree:
**what cryptography is actually in here**, and **which of it breaks when a
cryptographically relevant quantum computer arrives**.

Everything runs offline. The scanner makes no network requests.

## Install

3329lens is installed from source with Cargo. It is pure Rust, with no
assembly or C crypto to compile, and CI tests it on Linux, macOS (Intel and
Apple Silicon) and Windows. Prebuilt binaries are not offered yet.

```bash
cargo install --locked lens3329      # installs the `3329lens` binary
3329lens --help
```

`--locked` builds against the dependency versions 3329lens was tested with,
rather than whatever is newest on crates.io today.

If you don't have Rust yet, install it with [rustup](https://rustup.rs) first:

### Linux and macOS

```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
cargo install --locked lens3329
```

On macOS, if the build fails with a linker error, install Apple's command line
tools with `xcode-select --install`. On a minimal Linux install, the
equivalent is your distribution's C toolchain (`build-essential` on
Debian/Ubuntu, `gcc` on Fedora), which Rust needs to link the final binary.

### Windows

1. Download and run `rustup-init.exe` from [rustup.rs]https://rustup.rs.
   When it asks, let it install the **Visual Studio C++ Build Tools**. Rust
   needs Microsoft's linker to produce a Windows executable.
2. Open a **new** PowerShell or Command Prompt window, then run:

   ```powershell
   cargo install --locked lens3329
   3329lens --help
   ```

Under WSL, follow the Linux steps instead. A WSL install can scan your
Windows drives through `/mnt/c/...`, but scanning large Windows trees across
that boundary is much slower than a native Windows build.

### After installing

The binary goes in Cargo's bin directory: `~/.cargo/bin` on Linux and macOS,
`%USERPROFILE%\.cargo\bin` on Windows. rustup adds that directory to your
`PATH`, but only shells opened after the install pick up the change. If you
get `3329lens: command not found`, open a new terminal.

To upgrade, run the same `cargo install --locked lens3329` again. To remove
3329lens, run `cargo uninstall lens3329`.

Pre-1.0. See [SECURITY.md](SECURITY.md) for the support and disclosure policy.

## Quick start

```bash
# Human-readable inventory of the current directory
3329lens scan

# Scan a specific tree, limit the walk depth
3329lens scan --path ./my-project --depth 5

# Machine-readable
3329lens scan --path . --format json --output inventory.json
```

Four output formats: `text` (default), `json`, `cbom` (CycloneDX 1.6) and
`sarif` (SARIF 2.1.0).

### Correlating against advisories

Correlation needs a local advisory database — a clone of the RustSec
`advisory-db`, an OSV JSON export, or both:

```bash
git clone --depth 1 https://github.com/rustsec/advisory-db

3329lens scan --path . --advisory-db ./advisory-db
```

Only **lockfile-resolved** versions are correlated. A declared range such as
`^1.2` is skipped rather than guessed, because guessing produces either false
alarms or false comfort.

If the database you point at yields no usable advisories, that is a **hard
error**, not an empty result. A scan reporting "nothing matched" because
nothing *loaded* is indistinguishable from a clean bill of health, and would
make the CI gate below pass for the wrong reason.

### Gating CI

```bash
3329lens scan --path . --advisory-db ./advisory-db --fail-on high
```

| Exit code | Meaning |
| --- | --- |
| `0` | Success — no finding met the `--fail-on` threshold |
| `1` | Operational error (bad path, unreadable database, write failure) |
| `2` | A vulnerability at or above the threshold was found |

Thresholds: `none` (default), `low`, `medium`, `high`, `critical`. The two
failure codes are kept distinct on purpose — a broken scan and a failing scan
are different problems and should not share an exit status.

### GitHub code scanning

```yaml
- run: 3329lens scan --path . --advisory-db ./advisory-db --format sarif --output results.sarif
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif
```

Each matched advisory becomes one SARIF result; advisory IDs become rules;
severity maps to `level`, and the CVSS base score is written to
`properties["security-severity"]`, which is what GitHub reads to bucket
severity. Results anchor to the originating manifest or lockfile.

### CycloneDX CBOM

```bash
3329lens scan --path . --depth 5 --format cbom --advisory-db ./advisory-db --output cbom.json
```

The CBOM carries `library` and `cryptographic-asset` components,
library→algorithm dependency links, synthesized purls (`pkg:cargo/…`,
`pkg:npm/…` with scoped-name encoding, `pkg:pypi/…`), quantum risk flagged via
`nistQuantumSecurityLevel` (`0` = breakable by Shor's algorithm), and a
`vulnerabilities[]` array when an advisory database is supplied.

## Interactive dashboard

```bash
3329lens scanner-dashboard --path ./my-project --depth 5
```

A terminal UI that populates as the scan proceeds, rather than making you wait
for a report. Four views:

| | View | Shows |
| --- | --- | --- |
| `1` | **Live Scan** | Discoveries as they happen, progress, file and directory counts, scan rate |
| `2` | **Category** | Collapsible groups by crypto type, with versions, risk and detail |
| `3` | **Statistics** | Category and quantum-risk distribution as bar charts |
| `4` | **Migration** | Prioritised post-quantum migration plan with status tracking |

| Key | Action |
| --- | --- |
| `Tab` / `1`–`4` | Cycle views / jump to a view |
| `↑` `↓` or `k` `j` | Move the selection |
| `Home`/`g`, `End`/`G` | Jump to first / last |
| `Enter` or `d` | Open the detail view for the selected library |
| `a` / `c` | Expand / collapse all categories (Category view) |
| `f` / `x` | Filter to quantum-vulnerable only / clear filters |
| `s` | Cycle sort: name, category, risk, size, date |
| `m` | Toggle migration status (Migration view) |
| `e` | Export — JSON, or the migration plan as Markdown in the Migration view |
| `v` | Export the inventory as CSV |
| `Space` | Pause / resume |
| `h` or `F1` | Help overlay |
| `q` / `Esc` | Quit |

Exports are written to timestamped files in the working directory.

## What it detects

- **Binary artifacts** — `.so`, `.dll`, `.dylib`, `.a`.
- **Declared dependencies** — `Cargo.toml`, `package.json`,
  `requirements.txt`, `pyproject.toml` (Poetry and PEP 621), `Pipfile`.
- **Resolved versions and transitive crypto** — when a lockfile sits beside a
  manifest (`Cargo.lock`, `package-lock.json` / `npm-shrinkwrap.json`,
  `poetry.lock`, `Pipfile.lock`), declared ranges are replaced with the
  resolved version and crypto pulled in *transitively* is surfaced too. This is
  what makes purls and CVE correlation meaningful rather than decorative.

Findings are categorised by crypto type — SSL/TLS, general crypto,
post-quantum, hash functions, and language-ecosystem groupings — and flagged
for quantum risk.

Version comparison is ecosystem-aware: semver for Cargo and npm, a from-scratch
PEP 440 comparator for PyPI, so Python pre/post/dev releases, epochs and short
versions like `3.1` compare correctly. Advisories are indexed by
`(ecosystem, name)`, so same-named packages on crates.io, npm and PyPI cannot
cross-match.

## Crate name vs. command name

The crate is published as **`lens3329`**; the product, the CLI binary, the
domain and the GitHub org are all **`3329lens`**.

The reason is mundane: Cargo rejects package names that start with a digit, so
`3329lens` is not a registrable crate name. The digits are transposed for the
registry only. If you are looking for the command-line tool, you want
`3329lens`; if you are embedding the scanner in your own Rust program, you want
`lens3329`.

(The name `3329` is the modulus *q* of ML-KEM/Kyber.)

## Using it as a library

The scanning and correlation core is usable on its own, with no UI dependencies:

```toml
[dependencies]
lens3329 = { version = "0.2", default-features = false }
```

```rust
use lens3329::scanner::CryptoScanner;
use lens3329::{advisories, cbom, sarif};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut scanner = CryptoScanner::new();
    scanner.scan("./my-project", Some(5))?;
    let findings = scanner.get_findings();

    // CycloneDX 1.6 CBOM
    let bom = cbom::findings_to_cbom(findings, "./my-project");
    println!("{}", serde_json::to_string_pretty(&bom)?);

    // Offline advisory correlation + SARIF
    let db = advisories::AdvisoryDb::load_from_dir(Path::new("./advisory-db"))?;
    let correlation = advisories::correlate(&db, findings);
    let log = sarif::build(&correlation, "./my-project");
    println!("{}", serde_json::to_string_pretty(&log)?);

    Ok(())
}
```

`CryptoScanner::scan_streaming` emits `StreamingScanEvent`s over a channel if
you want progress as the walk proceeds rather than a batch result.

Without `default-features = false` you also get the CLI and TUI stack (`clap`,
`colored`, `crossterm`, `ratatui`). The lean core depends only on `serde`,
`serde_json`, `toml`, `regex`, `semver`, `walkdir`, `chrono` and `getrandom`.

| Module | Responsibility |
| --- | --- |
| `scanner` | Filesystem walk, binary and manifest detection, `CryptoFinding` model |
| `lockfile` | `Cargo.lock`, npm, Poetry and Pipenv lockfile parsing → resolved versions |
| `advisories` | Advisory DB loading (RustSec/OSV), version matching, CVSS v3 scoring |
| `pep440` | PEP 440 version parsing and total ordering, for PyPI comparisons |
| `cbom` | CycloneDX 1.6 CBOM construction |
| `sarif` | SARIF 2.1.0 log construction |
| `library` | Shared scan-result types (`LibraryInfo`, `LibraryCategory`, `RiskLevel`) |

Full API documentation: [docs.rs/lens3329](https://docs.rs/lens3329).

## Scope and limitations

Please read this before relying on the output.

- **Algorithm assets in the CBOM are inferred from library identity, not from
  observed runtime calls.** An algorithm component means "this library can do
  X", not "X is actually invoked". Versions and transitive dependencies, by
  contrast, are evidence-based — they come from lockfiles.
- **Only lockfile-resolved versions are correlated against advisories.**
  Declared-only ranges are skipped rather than guessed, and a version that
  fails its ecosystem's parser matches nothing rather than being assumed
  vulnerable.
- **Advisory coverage is limited to the database you supply**, and to the
  Cargo, npm and PyPI ecosystems. This is not a replacement for `cargo audit`,
  `npm audit` or `pip-audit` — it is a cryptography-focused inventory that also
  correlates, and it is best run alongside them.
- **Quantum-risk flags are a property of the algorithm, not of your usage.**
  A library marked Shor-breakable may be used for something a quantum computer
  does not threaten.

Correlation is validated against the real OSV exports rather than hand-written
fixtures, which is how four silent range-matching defects were found and fixed;
see `tests/osv_corpus.rs`.

## Security

Report vulnerabilities privately — see [SECURITY.md](SECURITY.md). Note that
for a scanner, a **silent false negative is a security issue**, not a cosmetic
one: if `--fail-on` passes when it should have failed, we want to hear about it.

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) and
[NOTICE](NOTICE).

Copyright 2026 Lomyen Ltd.