Named lists policies can test membership against, such as known-bad JA4 fingerprints or allow-listed networks.
Lists belong to an instance and live in :persistent_term, so a membership
test on the request path is a lookup with no copying. Replacing a list
triggers a global GC scan in the runtime: reload lists when they change,
not per request.
There are three kinds of lists:
- exact lists, whose members are compared as terms (
put/3); - CIDR lists, whose members are address ranges and which match any
address inside them (
put_cidrs/3); - substring lists, which match any string containing one of their
members, case included (
put_substrings/3), for user agents and other headers.
Lists can also be configured with the instance, and are then loaded when it starts:
config :my_app, Limen,
lists: [
bad_ja4: ["t13d1812h1_85036bcba153_375ca2c5e164"],
office: {:cidr, ["192.0.2.0/24", "2001:db8::/32"]},
scrapers: {:substrings, ["SiteSucker", "HTTrack"]}
]In a policy, signal(:user_agent) in list(:scrapers) then holds for any
user agent containing SiteSucker or HTTrack. A substring list is
searched in one pass, however many members it has.
In a policy, signal(:ja4) in list(:bad_ja4) compiles to member?/3
against the request's instance. Testing a list that was never defined is
not an error: it has no members.
Summary
Functions
Removes the list name of instance.
Returns the members of list name of instance as given, or [].
Whether value is a member of list name of instance.
Defines or replaces the exact list name of instance.
Defines or replaces the CIDR list name of instance.
Defines or replaces the substring list name of instance.
Types
@type instance() :: atom() | Limen.Instance.t()
@type name() :: atom()
Functions
Removes the list name of instance.
Returns the members of list name of instance as given, or [].
Whether value is a member of list name of instance.
For CIDR lists, value must be an address tuple, and for substring lists
a string.
iex> Limen.Lists.put(:doc_instance, :doc_example, ["a", "b"])
iex> Limen.Lists.member?(:doc_instance, :doc_example, "a")
true
iex> Limen.Lists.put_cidrs(:doc_instance, :doc_networks, ["192.0.2.0/24"])
iex> Limen.Lists.member?(:doc_instance, :doc_networks, {192, 0, 2, 7})
true
iex> Limen.Lists.put_substrings(:doc_instance, :doc_agents, ["HTTrack"])
iex> Limen.Lists.member?(:doc_instance, :doc_agents, "Mozilla/4.5 (compatible; HTTrack 3.0x)")
true
iex> Limen.Lists.member?(:doc_instance, :undefined_list, "a")
false
@spec put(instance(), name(), Enumerable.t()) :: :ok
Defines or replaces the exact list name of instance.
@spec put_cidrs(instance(), name(), [String.t() | Limen.IP.prefix()]) :: :ok
Defines or replaces the CIDR list name of instance.
Raises ArgumentError on an invalid range.
Defines or replaces the substring list name of instance.
A string is a member when it contains any of substrings, which must not
be empty strings.