UnicodeSecurity.Reason (UnicodeSecurity v0.1.0)

Copy Markdown View Source

A standards fact or policy violation found in the original input.

Use code and severity for application logic. message is concise text for logs, not a localization API, and must not be parsed. details contains documented reason-specific facts and never input-derived atom keys.

byte_offset and codepoint_index are zero-based positions in the original input, not grapheme indexes. Global findings have nil positions; malformed UTF-8 has a nil codepoint index because no reliable scalar index exists. Reasons sort by original byte offset, code, then deterministic details; global findings follow positional findings. Duplicate offset/code/details are removed.

Codes and details

  • :invalid_utf8: %{invalid_byte: byte}.
  • :invalid_item_type: %{actual_type: type} where type is one of :atom, :integer, :float, :list, :tuple, :map, :bitstring, :function, :pid, :port, or :reference. Nil and booleans are atoms; structs are maps. Audit items with this code have nil positions.
  • :input_too_long: %{actual_bytes: count, maximum_bytes: 4096}.
  • :empty_input: %{}; valid UTF-8 with positions zero.
  • :profile_syntax: %{codepoint: scalar, rule: :whitespace | :punctuation | :unsupported_category}.

  • :restricted_character: %{codepoint: scalar, identifier_types: sorted_types}; original raw status, independent of canonical closure except explicit profile punctuation exceptions.
  • :invalid_join_control_context, :default_ignorable, :bidi_control: %{codepoint: scalar}; join context validity does not suppress the other codes.
  • :disallowed_script, :denied_script: %{script: ordinary_script_atom}; one finding per excluded Script_Extensions candidate if none survive.
  • :mixed_scripts: %{scripts: sorted_observed_scripts} excluding Common/Inherited.
  • :mixed_numbers: %{zero_codepoints: sorted_unique_decimal_zero_scalars}.
  • :restriction_level_below_policy: %{actual: raw_level, minimum: policy_minimum}; the decision uses membership extended only by explicit punctuation exceptions.
  • :exact_duplicate, :skeleton_collision, and the three confusable classes: %{indexes: ordered_zero_based_indexes} in eager batch collections only.
  • Domain validity codes are :domain_empty_label, :domain_invalid_alabel, :domain_idna_disallowed, :domain_hyphen_rule, :domain_bidi_rule, :domain_joiner_rule, :domain_label_too_long, :domain_name_too_long, :domain_invalid_ascii, and :domain_invalid_hostname. :domain_deviation_character is advisory. Every domain detail includes label_index, original label, and original_byte_offset; global findings use nil values. Positional findings include source_scope: :scalar | :label. codepoint, when present, names the scalar that triggered the rule after IDNA mapping or A-label decoding. Rules and limits are in the specific domain finding's details.

Severities

CodesStrictDefaultPermissive
invalid_item_type, invalid_utf8, input_too_longcriticalcriticalcritical
empty_inputhighhighhigh
profile_syntax, restricted_character, invalid_join_control_context, disallowed_script, denied_scripthighhighmedium
default_ignorable, bidi_controlcriticalhighhigh
mixed_scripts, mixed_numbers, restriction_level_below_policyhighmediumlow
single_script_confusable, mixed_script_confusable, whole_script_confusable, skeleton_collisioncriticalhighmedium
exact_duplicateinfoinfoinfo
domain validity codescriticalhighhigh
domain_deviation_charactermediumlowinfo

No reasons or only :info means safe, :low/:medium suspicious, and :high/:critical dangerous. Current checks do not emit collision or generic confusable findings. Messages are static and contain no complete untrusted input.

Summary

Types

actual_type()

@type actual_type() ::
  :atom
  | :integer
  | :float
  | :list
  | :tuple
  | :map
  | :bitstring
  | :function
  | :pid
  | :port
  | :reference

code()

@type code() ::
  :invalid_item_type
  | :invalid_utf8
  | :input_too_long
  | :empty_input
  | :profile_syntax
  | :restricted_character
  | :invalid_join_control_context
  | :disallowed_script
  | :denied_script
  | :default_ignorable
  | :bidi_control
  | :mixed_scripts
  | :mixed_numbers
  | :restriction_level_below_policy
  | :single_script_confusable
  | :mixed_script_confusable
  | :whole_script_confusable
  | :skeleton_collision
  | :exact_duplicate
  | :domain_empty_label
  | :domain_invalid_alabel
  | :domain_idna_disallowed
  | :domain_hyphen_rule
  | :domain_bidi_rule
  | :domain_joiner_rule
  | :domain_label_too_long
  | :domain_name_too_long
  | :domain_invalid_ascii
  | :domain_deviation_character
  | :domain_invalid_hostname

details()

@type details() ::
  %{}
  | %{actual_type: actual_type()}
  | %{invalid_byte: byte()}
  | %{actual_bytes: non_neg_integer(), maximum_bytes: 4096}
  | %{codepoint: non_neg_integer(), rule: syntax_rule()}
  | %{codepoint: non_neg_integer(), identifier_types: [atom()]}
  | %{codepoint: non_neg_integer()}
  | %{script: atom()}
  | %{scripts: [atom()]}
  | %{zero_codepoints: [non_neg_integer()]}
  | %{indexes: [non_neg_integer()]}
  | %{
      actual: UnicodeSecurity.Result.restriction_level(),
      minimum: UnicodeSecurity.Result.restriction_level()
    }
  | map()

severity()

@type severity() :: :info | :low | :medium | :high | :critical

syntax_rule()

@type syntax_rule() :: :whitespace | :punctuation | :unsupported_category

t()

@type t() :: %UnicodeSecurity.Reason{
  byte_offset: non_neg_integer() | nil,
  code: code() | nil,
  codepoint_index: non_neg_integer() | nil,
  details: details(),
  message: binary() | nil,
  severity: severity() | nil
}