Packages
mob_dev
0.5.7
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mob_dev/security_scan/bundled_versions.ex
defmodule MobDev.SecurityScan.BundledVersions do
@moduledoc """
Loads `priv/security/bundled_versions.exs` — the source-of-truth
manifest of what versions ship inside the OTP tarballs that
`MobDev.OtpDownloader` distributes.
See [`priv/security/bundled_versions.exs`](priv/security/bundled_versions.exs)
for the full schema and update procedure.
## Why a manifest, not a fingerprint-only approach
Manifest first, fingerprint second. The manifest is a *claim*
reviewable in git — every PR that touches it is auditable.
Fingerprinting is the *receipt* that proves the claim.
A fingerprint-only approach can silently fail when build flags
change and a version string is stripped or moves to a different
binary; the scanner just reports "version unknown" and you stop
noticing. A manifest-first approach forces a human to write down
what shipped — and the fingerprinter then catches drift.
"""
@external_resource Path.join([
__DIR__,
"..",
"..",
"..",
"priv",
"security",
"bundled_versions.exs"
])
@manifest_path Path.join([
:code.priv_dir(:mob_dev) |> to_string(),
"security",
"bundled_versions.exs"
])
@doc "Path to the manifest .exs file."
@spec manifest_path() :: Path.t()
def manifest_path, do: @manifest_path
@doc """
Load the manifest from disk. Returns the parsed map.
Raises if the file is missing or doesn't evaluate to a map with
the expected shape — the manifest is a hard requirement for the
bundled-runtime scan layer; a missing file is a real bug, not a
soft warning.
"""
@spec load() :: %{
active_hash: String.t(),
bundles: %{String.t() => map()}
}
def load do
path = manifest_path()
unless File.exists?(path) do
raise "bundled versions manifest missing at #{path}"
end
{manifest, _bindings} = Code.eval_file(path)
validate!(manifest)
manifest
end
@doc """
Return the bundle entry for a given OTP tarball hash.
Returns `{:ok, bundle}` when present, `{:error, :unknown_hash}`
otherwise. Useful for the fingerprinter when the hash on disk
doesn't match the manifest's `:active_hash` — the tarball might
be from an older or unpublished build.
"""
@spec for_hash(String.t()) :: {:ok, map()} | {:error, :unknown_hash}
def for_hash(hash) when is_binary(hash) do
case Map.fetch(load().bundles, hash) do
{:ok, bundle} -> {:ok, bundle}
:error -> {:error, :unknown_hash}
end
end
@doc "Return the currently active bundle (the hash Mob is shipping today)."
@spec active() :: map()
def active do
manifest = load()
Map.fetch!(manifest.bundles, manifest.active_hash)
end
defp validate!(%{active_hash: hash, bundles: bundles})
when is_binary(hash) and is_map(bundles) do
unless Map.has_key?(bundles, hash) do
raise "bundled versions manifest: active_hash #{inspect(hash)} not found in :bundles"
end
Enum.each(bundles, fn {h, bundle} -> validate_bundle!(h, bundle) end)
:ok
end
defp validate!(other) do
raise "bundled versions manifest must be %{active_hash: ..., bundles: %{...}}; got #{inspect(other)}"
end
@required_fields [:erts, :otp_release, :elixir, :openssl, :exqlite_beam]
defp validate_bundle!(hash, bundle) when is_map(bundle) do
Enum.each(@required_fields, fn key ->
unless Map.has_key?(bundle, key) do
raise "bundled versions manifest: bundle #{inspect(hash)} missing required field #{inspect(key)}"
end
end)
end
defp validate_bundle!(hash, other) do
raise "bundled versions manifest: bundle #{inspect(hash)} must be a map, got #{inspect(other)}"
end
end