Limen.Lists (limen v0.2.1)

Copy Markdown View Source

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

instance()

@type instance() :: atom() | Limen.Instance.t()

name()

@type name() :: atom()

Functions

delete(instance, name)

@spec delete(instance(), name()) :: :ok

Removes the list name of instance.

get(instance, name)

@spec get(instance(), name()) :: list()

Returns the members of list name of instance as given, or [].

member?(instance, name, value)

@spec member?(instance(), name(), term()) :: boolean()

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

put(instance, name, values)

@spec put(instance(), name(), Enumerable.t()) :: :ok

Defines or replaces the exact list name of instance.

put_cidrs(instance, name, ranges)

@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.

put_substrings(instance, name, substrings)

@spec put_substrings(instance(), name(), [String.t()]) :: :ok

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.