SigilGuard.Policy behaviour (SigilGuard v0.2.0)

View Source

Risk classification and trust-gated policy enforcement for the SIGIL protocol.

Maps actions to risk levels and evaluates whether a given trust level is sufficient to proceed. Supports configurable risk mappings, confirmation flow for borderline cases, and rate limiting.

Matches the sigil-protocol Rust crate's RiskLevel enum (v0.1.5).

Risk Level Hierarchy

:low < :medium < :high
  • low — Read-only, within workspace. Requires :low trust.
  • medium — State-modifying but recoverable. Requires :medium trust.
  • high — Destructive or irreversible. Requires :high trust.

Default Trust Thresholds

Risk LevelMinimum Trust Required
:low:low
:medium:medium
:high:high

Custom Policy Implementation

defmodule MyApp.StrictPolicy do
  @behaviour SigilGuard.Policy

  @impl true
  def evaluate(action, trust_level, opts) do
    # Custom logic
  end

  @impl true
  def classify_risk(action, opts) do
    # Custom risk classification
  end
end

Summary

Callbacks

Classify the risk level of an action.

Evaluate an action against a trust level and return a verdict.

Functions

Classify the risk level of an action based on pattern matching.

Compare two risk levels.

Ensure the ETS table used by rate_check/2 exists.

Evaluate an action against a trust level.

Perform a rate check for an identity performing an action.

Return all risk levels in ascending order.

Return the default trust threshold for a risk level.

Types

risk_level()

@type risk_level() :: :low | :medium | :high

verdict()

@type verdict() :: :allowed | :blocked | {:confirm, String.t()}

Callbacks

classify_risk(action, opts)

@callback classify_risk(action :: String.t(), opts :: keyword()) :: risk_level()

Classify the risk level of an action.

evaluate(action, trust_level, opts)

@callback evaluate(
  action :: String.t(),
  trust_level :: SigilGuard.Identity.trust_level(),
  opts :: keyword()
) :: verdict()

Evaluate an action against a trust level and return a verdict.

Functions

classify_risk(action, opts \\ [])

@spec classify_risk(
  String.t(),
  keyword()
) :: risk_level()

Classify the risk level of an action based on pattern matching.

Uses :risk_mappings option or falls back to built-in heuristics based on action name prefixes.

Built-in Risk Heuristics

  • "delete_", "drop_", "destroy_", "execute_", "run_":high
  • "write_", "update_", "create_", "modify_", "send_":medium
  • "read_", "get_", "list_", "search_":low
  • Everything else → :medium

compare_risk(a, b)

@spec compare_risk(risk_level(), risk_level()) :: :lt | :eq | :gt

Compare two risk levels.

Examples

iex> SigilGuard.Policy.compare_risk(:low, :high)
:lt

iex> SigilGuard.Policy.compare_risk(:high, :medium)
:gt

ensure_rate_table(table \\ :sigil_guard_rates)

@spec ensure_rate_table(atom()) :: :ok

Ensure the ETS table used by rate_check/2 exists.

The default table is created automatically at application start; call this only to pre-create a custom :rate_store table from a process that outlives the callers (the table is owned by the process that creates it).

Safe to call concurrently — creation races resolve to the existing table.

evaluate(action, trust_level, opts \\ [])

Evaluate an action against a trust level.

Returns :allowed if trust is sufficient, {:confirm, reason} if the caller is one trust level below the threshold (allowing interactive confirmation), or :blocked otherwise.

Options

  • :risk_level — override the risk classification (default: look up via :risk_mappings)
  • :risk_mappings — map of action pattern to risk level
  • :trust_thresholds — override default trust thresholds per risk level

Examples

iex> SigilGuard.Policy.evaluate("read_file", :medium)
:allowed

iex> SigilGuard.Policy.evaluate("delete_database", :low)
:blocked

rate_check(identity, opts \\ [])

@spec rate_check(
  String.t(),
  keyword()
) :: :ok | {:error, :rate_limited}

Perform a rate check for an identity performing an action.

Returns :ok if within limits, or {:error, :rate_limited} if exceeded.

This is a fixed-window counter: an identity's first request opens a window, requests within :window_ms count against :max_requests, and the next request after the window expires opens a fresh one. Two limits follow from that design — suitable for coarse abuse protection, not strict quotas:

  • Check and increment are separate ETS operations, so concurrent callers can slightly exceed :max_requests under contention.
  • Up to 2 × max_requests can pass in a burst straddling a window boundary, as with any fixed-window scheme.

For strict guarantees, implement the SigilGuard.Policy behaviour with a dedicated rate limiter backend.

Options

  • :max_requests — maximum requests per window (default: 100)
  • :window_ms — time window in milliseconds (default: 60_000)
  • :rate_store — ETS table name for rate tracking (default: :sigil_guard_rates)

The default table is created at application start and lives as long as the application. A custom :rate_store table is created on first use and owned by the first calling process — its rate state is lost if that process exits.

risk_levels()

@spec risk_levels() :: [risk_level(), ...]

Return all risk levels in ascending order.

Examples

iex> SigilGuard.Policy.risk_levels()
[:low, :medium, :high]

trust_threshold(risk_level)

@spec trust_threshold(risk_level()) :: SigilGuard.Identity.trust_level()

Return the default trust threshold for a risk level.

Examples

iex> SigilGuard.Policy.trust_threshold(:high)
:high

iex> SigilGuard.Policy.trust_threshold(:low)
:low