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

Wrapper around the `osv-scanner` CLI (https://google.github.io/osv-scanner/).

`osv-scanner` queries the [OSV.dev](https://osv.dev) database, which
aggregates advisories from many ecosystems (Hex, Maven/Gradle, Swift
PM, npm, PyPI, RubyGems, ...) into a single feed. Several Mob scan
layers (`hex_deps`, `gradle_deps`, `swift_deps`) call this helper so
the binary integration lives in one place.

All public functions are pure orchestration — no parsing logic, no
finding shape. `Parser` does the actual JSON → `Finding` translation,
which keeps the network/process side easy to mock and the parser
trivially testable with fixture JSON.

# `target`

```elixir
@type target() :: {:lockfile, Path.t()} | {:directory, Path.t()}
```

What to scan. `{:lockfile, path}` for a single lockfile, `{:directory, path}`
for a recursive scan that finds every supported manifest under the tree.

# `installed?`

```elixir
@spec installed?() :: boolean()
```

True if `osv-scanner` is on PATH.

# `scan`

```elixir
@spec scan(target(), atom(), keyword()) ::
  {:ok, [MobDev.SecurityScan.Finding.t()]}
  | {:error,
     :not_installed | {:not_found, Path.t()} | {:scan_failed, String.t()}}
```

Scan a target and return findings tagged with the given `layer`.

Returns:

  * `{:ok, findings}` — scan completed (findings list may be empty)
  * `{:error, :not_installed}` — binary not on PATH
  * `{:error, {:not_found, path}}` — target path doesn't exist
  * `{:error, {:scan_failed, reason}}` — binary exited non-zero or
    produced unparseable output

`osv-scanner` exits with code 1 when *findings* are present and 0
when clean — this function treats both as success and only signals
`:scan_failed` for true errors (code 127, malformed JSON, etc.).

---

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