# `MobDev.SecurityScan.Finding`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/security_scan/finding.ex#L1)

A single normalized security finding.

Findings come from many sources — Hex `mix_audit`, `osv-scanner`,
the OpenSSL/SQLite/Erlef advisory feeds, semgrep, etc. — and are
normalized into this struct so the report and rubric treat them
uniformly. `source` records which scanner produced it; `layer`
records which surface area it covers (`:hex_deps`, `:bundled_runtime`,
`:c_source`, ...).

`severity` is one of `:critical`, `:high`, `:medium`, `:low`,
`:unknown`. Scanners report severity differently (CVSS scores,
GHSA ratings, vendor scales); upstream callers normalize before
building a Finding.

# `severity`

```elixir
@type severity() :: :critical | :high | :medium | :low | :unknown
```

# `t`

```elixir
@type t() :: %MobDev.SecurityScan.Finding{
  description: String.t() | nil,
  fixed_in: String.t() | nil,
  id: String.t() | nil,
  layer: atom(),
  package: String.t() | nil,
  severity: severity(),
  source: atom(),
  title: String.t() | nil,
  url: String.t() | nil,
  version: String.t() | nil
}
```

# `dedupe_key`

```elixir
@spec dedupe_key(t()) :: {String.t() | nil, String.t() | nil, String.t() | nil}
```

Deduplication key. Two findings dedupe to the same key when they
describe the same advisory against the same package+version, even
if they came from different sources (e.g. mix_audit and osv-scanner
both reporting GHSA-XXXX against `:plug` 1.10).

# `sort_key`

```elixir
@spec sort_key(t()) :: {non_neg_integer(), String.t()}
```

Sort order helper: severities ranked critical → unknown.

Use as `Enum.sort_by(findings, &Finding.sort_key/1)`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
