MobDev.Plugin.SignatureGate (mob_dev v0.5.14)

Copy Markdown View Source

Host-side build-time gate: runs Verify.verify_plugin/2 + the TrustStore check across every activated plugin and refuses the build on any failure.

This is the Phase 2 cryptographic counterpart to the capability drift check in Validator. Both run at the same hook point inside Validator.raise_on_capability_drift!/1 so the iOS-sim, iOS-device, and Android paths all enforce them as a one-liner.

Three distinct failure modes are surfaced (per MOB_PLUGIN_SECURITY.md, Phase 2):

  • Missing signature — author hasn't run mix mob.plugin.sign. Suppressible per-plugin via config :mob, :acknowledge_unsafe_plugins with a persistent banner.
  • Invalid signature — sig is present but doesn't verify; the manifest or sources have been tampered with after signing. Not suppressible.
  • Untrusted fingerprint — signature verifies but the public key isn't in config :mob, :trusted_plugins (or is a different key from the trusted one, the key-rotation case). Not suppressible; user must run mix mob.plugin.trust <name>.

Summary

Types

Errors check_plugin/2 can return.

Functions

Runs the signature + trust check across plugins (the MobDev.Plugin.activated/0 shape — [{plugin_dir, manifest}]).

Prints a stderr banner when any activated plugin is allowed only via :acknowledge_unsafe_plugins. Idempotent within a single Mix invocation in spirit — the banner fires every time it's called, so callers should invoke it once per build.

Runs check_activated/1 and raises a Mix.raise/1 with a clear, actionable message when any plugin fails. No-op on success.

Types

gate_error()

@type gate_error() ::
  {:missing_signature, atom()}
  | {:missing_pubkey, atom()}
  | {:invalid_signature, atom()}
  | {:untrusted, atom(), MobDev.Plugin.Crypto.fingerprint(),
     MobDev.Plugin.Crypto.fingerprint() | nil}

Errors check_plugin/2 can return.

Functions

check_activated(plugins)

@spec check_activated([{Path.t(), map() | nil}]) :: :ok | {:error, [gate_error()]}

Runs the signature + trust check across plugins (the MobDev.Plugin.activated/0 shape — [{plugin_dir, manifest}]).

Returns :ok when every plugin verifies AND is trusted (or, for missing signatures, is listed in config :mob, :acknowledge_unsafe_plugins). Returns {:error, errors} otherwise — a list of gate_error/0 tagged by plugin name.

Reads the trust map from mob.exs (via TrustStore.load_trusted_plugins/0) and the acknowledgement list from :mob's Application env or mob.exs. Pass the trust_map + acknowledged list explicitly via check_activated/3 from tests that need isolation.

check_activated(plugins, trust_map, acknowledged)

@spec check_activated(
  [{Path.t(), map() | nil}],
  MobDev.Plugin.TrustStore.trust_map(),
  [atom()]
) ::
  :ok | {:error, [gate_error()]}

Pure variant of check_activated/1 for tests.

maybe_print_unsafe_banner(plugins)

@spec maybe_print_unsafe_banner([{Path.t(), map() | nil}]) :: :ok

Prints a stderr banner when any activated plugin is allowed only via :acknowledge_unsafe_plugins. Idempotent within a single Mix invocation in spirit — the banner fires every time it's called, so callers should invoke it once per build.

raise_on_signature_drift!(plugins)

@spec raise_on_signature_drift!([{Path.t(), map() | nil}]) :: :ok

Runs check_activated/1 and raises a Mix.raise/1 with a clear, actionable message when any plugin fails. No-op on success.