MobDev.SecurityScan.StateFile (mob_dev v0.5.0)

Copy Markdown View Source

Read/write the state sidecar for mix mob.security_scan.log.

The state file is a small JSON document that records the last-known set of findings and when each was first seen. Diff computation between runs depends on it.

Default location: .security_scan/state.json at the project root. Should be checked into git so a fresh CI run knows what the prior baseline was — without it, every scheduled run reports every finding as 'new' and the changelog becomes useless.

Schema

{
  "version": 1,
  "last_run_at": "2026-05-07T05:30:00Z",
  "findings": [
    {
      "key": "EEF-CVE-2026-32689|phoenix|1.8.5",
      "id": "EEF-CVE-2026-32689",
      "severity": "high",
      "package": "phoenix",
      "version": "1.8.5",
      "fixed_in": "1.7.22",
      "title": "...",
      "url": "...",
      "source": "osv_scanner",
      "layer": "hex_deps",
      "first_seen_at": "2026-05-07T05:30:00Z"
    }
  ]
}

Summary

Types

Finding-as-stored: same fields as Finding plus key + first_seen_at.

Dedup key derived from id|package|version.

Loaded state map.

Functions

Empty initial state for first-time scans.

Build the next state from the current report and a Diff (which carries first_seen_at for findings that already existed).

Load state from a JSON file. Returns empty/0 if the file is missing.

Encode + write the given state to disk. Creates parent dirs as needed.

Types

entry()

@type entry() :: %{
  key: key(),
  id: String.t() | nil,
  severity: atom(),
  package: String.t() | nil,
  version: String.t() | nil,
  fixed_in: String.t() | nil,
  title: String.t() | nil,
  url: String.t() | nil,
  source: atom() | nil,
  layer: atom() | nil,
  first_seen_at: DateTime.t() | String.t()
}

Finding-as-stored: same fields as Finding plus key + first_seen_at.

key()

@type key() :: String.t()

Dedup key derived from id|package|version.

state()

@type state() :: %{
  version: integer(),
  last_run_at: DateTime.t() | nil,
  findings: [entry()]
}

Loaded state map.

Functions

empty()

@spec empty() :: state()

Empty initial state for first-time scans.

from_report(report, diff, now)

Build the next state from the current report and a Diff (which carries first_seen_at for findings that already existed).

load(path)

@spec load(Path.t()) :: state()

Load state from a JSON file. Returns empty/0 if the file is missing.

save(path, state)

@spec save(Path.t(), state()) :: :ok

Encode + write the given state to disk. Creates parent dirs as needed.