Obscura validates every configured operator before it modifies text. A bad
configuration returns {:error, %Obscura.Anonymizer.Error{}}; it never returns
a successful or partially anonymized result.
Errors expose only stable codes, operator and field atoms, safe reason atoms, and allowlisted metadata. They never contain source text, replacements, salts, callback return values, or exception messages.
Replace
%{type: :replace, value: "[EMAIL]"}value is optional and defaults to [REDACTED]. When present, it must be a
binary. The only accepted keys are type and value.
Redact
%{type: :redact}Redact replaces the detected span with an empty binary. It accepts no options
other than type.
Mask
%{type: :mask, char: "*", keep_last: 4}chardefaults to*and must contain exactly one valid Unicode grapheme.keep_lastdefaults to0and must be a non-negative integer.- Masking operates on graphemes, while result offsets remain byte offsets.
- When
keep_lastexceeds the source grapheme count, the source is unchanged.
Empty or multi-grapheme mask strings, negative counts, invalid types, and unknown options are rejected.
Hash
Hash replacements use this self-describing format:
$obscura$v1$hash$ALGORITHM$MODE$BASE64URL_SALT$HEX_DIGESTSupported algorithms are :sha256 and :sha512.
Secure Mode
%{type: :hash, mode: :secure, algorithm: :sha256}Secure mode is the default. It generates a fresh 16-byte salt with
:crypto.strong_rand_bytes/1 for every replacement. The same source value
therefore produces different replacements. Supplying a salt in secure mode is
an error.
Deterministic Mode
%{
type: :hash,
mode: :deterministic,
algorithm: :sha256,
salt: "application-salt"
}The salt must be a binary containing at least 16 bytes. Identical value, salt, and algorithm inputs produce identical replacements. This reveals equality and does not make low-entropy identifiers unguessable.
Obscura.Operator.Hash.verify/2 verifies a source value against either encoded
mode without raising for malformed input.
Hash Migration
The pre-release hash contract previously defaulted to deterministic SHA-256,
allowed an empty or short salt, and returned sha256:HEX. That format is no
longer generated or accepted by verify/2.
To migrate intentional deterministic use, add mode: :deterministic, provide
at least 16 salt bytes, and store the new versioned replacement. Otherwise omit
the salt and use the secure default. Existing stored legacy hashes require an
application-specific migration because Obscura cannot recover their source
values.
Pseudonymize
%{type: :pseudonymize}Pass the vault in the operator config or anonymizer options:
{:ok, vault} = Obscura.Vault.Memory.start_link()
text = "Email user@example.test"
Obscura.redact(text,
entities: [:email],
operators: %{email: %{type: :pseudonymize}},
vault: vault
)Pseudonymization is reversible vault-backed tokenization, not encryption. The vault and token formatting options are checked during operator preflight.
Custom Operators
Custom operators implement Obscura.Operator.Custom:
defmodule MyOperator do
@behaviour Obscura.Operator.Custom
@impl Obscura.Operator.Custom
def apply(value, %{entity: entity}, options) do
{:ok, "<#{options.prefix}:#{entity}:#{byte_size(value)}>", %{format: :custom}}
end
endConfigure the module and a map of explicit options:
%{type: :custom, module: MyOperator, options: %{prefix: "private"}}Valid callback results are {:ok, replacement} and
{:ok, replacement, metadata}. The replacement must be a binary and metadata
must be a map. Missing modules, missing behaviour declarations, invalid
returns, callback errors, exceptions, throws, and exits become sanitized
operator errors. The callback receives only the entity context in addition to
the explicit source value and options.
Custom operator modules are trusted application code. They receive the raw matched value and can log, transmit, retain, or raise with it before Obscura sanitizes the callback boundary. Review callback implementations and avoid embedding source values in their own telemetry or process metadata.
Error Contract
Obscura.Anonymizer.Error has these public fields:
code: stable machine-readable category;operator: operator atom when known;field: invalid field atom when known;reason: safe reason atom;metadata: allowlisted non-sensitive details.
Applications should branch on code, operator, and field rather than the
human-readable exception message. Unknown options and malformed operator
collections are errors, including configurations for entities which do not
appear in the current input.
Operator types, keys, defaults, preflight behavior, and stable error codes are
part of the 0.1.x contract. Human-readable messages and additive metadata are
not. See docs/public-api-stability.md.
Default Inspect implementations for anonymizer result and item structs hide
redacted output, replacements, token-like values, and metadata. Their explicit
public fields still contain the values required by the operation and must be
handled as sensitive data.