Lua Policy Plugins
Nauthilus Lua plugins can emit typed policy attributes directly into the active request decision context. This lets a plugin collect a signal once and then let a top-level Policy rule decide the final effect.
Enable the Bundled Registry
The bundled policy-aware plugins emit attributes below lua.plugin.*. Register these attributes before using them in policies:
policy:
namespaces:
authn:
schema_contributions:
lua:
registry_scripts:
- "/etc/nauthilus/lua-plugins.d/policy/registry.lua"
The registry is loaded when the policy snapshot is built. If a plugin emits an unknown attribute, the Lua execution fails instead of silently creating a loose fact.
Plugin Helper
Bundled plugins use nauthilus_policy_facts:
local policy_facts = require("nauthilus_policy_facts")
policy_facts.emit("soft_delay", "risky", true)
policy_facts.emit_public("geoip", "rejected", true, {
status_message = "Policy violation",
})
policy_facts.status_message("geoip", "Policy violation")
emitandemit_manywrite policy attributes and request-localpolicy_facts.emit_publicandemit_many_publicalso writepolicy_fact_<namespace>_<key>custom logs.status_messagesets the normal Nauthilus status message and emitslua.plugin.<namespace>.status_message.setstores only request-local Lua context data and does not emit a policy attribute.
Attribute Reference
All attributes below are registered by lua-plugins.d/policy/registry.lua.
Environment Source Plugins
| Plugin | Attribute | Type | Meaning |
|---|---|---|---|
account_longwindow_metrics.lua | lua.plugin.account_longwindow.username | string | Account name used by the long-window collector. |
account_longwindow_metrics.lua | lua.plugin.account_longwindow.authenticated | bool | Whether the request was authenticated when metrics were collected. |
account_longwindow_metrics.lua | lua.plugin.account_longwindow.uniq_ips_24h | number | Unique account IP estimate over 24 hours. |
account_longwindow_metrics.lua | lua.plugin.account_longwindow.uniq_ips_7d | number | Unique account IP estimate over 7 days. |
account_longwindow_metrics.lua | lua.plugin.account_longwindow.fails_24h | number | Failed account attempts over 24 hours. |
account_longwindow_metrics.lua | lua.plugin.account_longwindow.fails_7d | number | Failed account attempts over 7 days. |
account_longwindow_metrics.lua | lua.plugin.account_longwindow.has_pw_token | bool | Whether the request produced a sprayed-password token. |
global_pattern_monitoring.lua | lua.plugin.global_pattern.attempts | number | Global attempts in the current window. |
global_pattern_monitoring.lua | lua.plugin.global_pattern.unique_ips | number | Global unique IP estimate. |
global_pattern_monitoring.lua | lua.plugin.global_pattern.unique_users | number | Global unique user estimate. |
global_pattern_monitoring.lua | lua.plugin.global_pattern.attempts_per_ip | number | Attempts-per-IP ratio. |
global_pattern_monitoring.lua | lua.plugin.global_pattern.attempts_per_user | number | Attempts-per-user ratio. |
global_pattern_monitoring.lua | lua.plugin.global_pattern.ips_per_user | number | IPs-per-user ratio. |
security_metrics.lua | lua.plugin.security_metrics.global_ips_per_user_24h | number | Global IPs-per-user ratio over 24 hours. |
security_metrics.lua | lua.plugin.security_metrics.global_ips_per_user_7d | number | Global IPs-per-user ratio over 7 days. |
security_metrics.lua | lua.plugin.security_metrics.protected_accounts | number | Number of accounts currently in protection mode. |
failed_login_hotspot.lua | lua.plugin.failed_login_hotspot.username | string | Username evaluated against the failed-login hotspot set. |
failed_login_hotspot.lua | lua.plugin.failed_login_hotspot.count | number | Failed-login hotspot score. |
failed_login_hotspot.lua | lua.plugin.failed_login_hotspot.rank | number | Failed-login hotspot rank. |
failed_login_hotspot.lua | lua.plugin.failed_login_hotspot.triggered | bool | Whether the hotspot threshold matched. |
blocklist.lua | lua.plugin.blocklist.matched | bool | Whether the remote client matched the blocklist. |
blocklist.lua | lua.plugin.blocklist.client_ip | ip | Client IP sent to the blocklist service. |
blocklist.lua | lua.plugin.blocklist.status_message | string | Client-visible blocklist message. |
lua.plugin.blocklist.matched carries a public status_message detail when it is emitted.
Subject Source Plugins
| Plugin | Attribute | Type | Meaning |
|---|---|---|---|
account_centric_monitoring.lua | lua.plugin.account_monitoring.attack_detected | bool | Whether account-centric monitoring detected an attack pattern. |
account_centric_monitoring.lua | lua.plugin.account_monitoring.username | string | Username evaluated by the monitor. |
account_centric_monitoring.lua | lua.plugin.account_monitoring.uniq_ips_1h | number | Unique account IP estimate over 1 hour. |
account_centric_monitoring.lua | lua.plugin.account_monitoring.uniq_ips_24h | number | Unique account IP estimate over 24 hours. |
account_centric_monitoring.lua | lua.plugin.account_monitoring.uniq_ips_7d | number | Unique account IP estimate over 7 days. |
account_centric_monitoring.lua | lua.plugin.account_monitoring.failed_24h | number | Failed account attempts over 24 hours. |
account_centric_monitoring.lua | lua.plugin.account_monitoring.ratio_24h | number | Account IP-to-failure ratio over 24 hours. |
account_protection_mode.lua | lua.plugin.account_protection.active | bool | Whether account protection mode is active. |
account_protection_mode.lua | lua.plugin.account_protection.reason | string | Comma-separated protection reason codes. |
account_protection_mode.lua | lua.plugin.account_protection.backoff_level | number | Current protection backoff level. |
account_protection_mode.lua | lua.plugin.account_protection.delay_ms | number | Delay applied in milliseconds. |
account_protection_mode.lua | lua.plugin.account_protection.enforce_reject | bool | Whether protection mode rejects unauthenticated traffic. |
account_protection_mode.lua | lua.plugin.account_protection.status_message | string | Client-visible protection message. |
geoip.lua | lua.plugin.geoip.guid | string | GeoIP service request identifier. |
geoip.lua | lua.plugin.geoip.current_country_code | string | Current ISO-3166 alpha-2 country code. |
geoip.lua | lua.plugin.geoip.country_codes | string_list | Country codes observed by the GeoIP service. |
geoip.lua | lua.plugin.geoip.rejected | bool | Whether the GeoIP service requested rejection. |
geoip.lua | lua.plugin.geoip.error | bool | Whether the GeoIP service returned an error. |
geoip.lua | lua.plugin.geoip.status_message | string | Client-visible GeoIP message. |
idp_policy.lua | lua.plugin.idp_policy.rejected | bool | Whether the IdP Lua policy rejected the request. |
idp_policy.lua | lua.plugin.idp_policy.reason | string | Reason returned by the IdP Lua policy. |
idp_policy.lua | lua.plugin.idp_policy.oidc_cid | string | OIDC client identifier evaluated by the plugin. |
idp_policy.lua | lua.plugin.idp_policy.grant_type | string | OIDC grant type evaluated by the plugin. |
idp_policy.lua | lua.plugin.idp_policy.status_message | string | Client-visible IdP policy message. |
monitoring.lua | lua.plugin.director.backend_server | string | Backend server selected by the director subject source. |
soft_delay.lua | lua.plugin.soft_delay.risky | bool | Whether the soft-delay subject source considered the request risky. |
soft_delay.lua | lua.plugin.soft_delay.applied_ms | number | Delay applied in milliseconds. |
lua.plugin.account_protection.active, lua.plugin.geoip.rejected, and lua.plugin.idp_policy.rejected carry a public status_message detail when emitted with a client-visible message.
Policy Example
policy:
namespaces:
authn:
schema_contributions:
lua:
registry_scripts:
- "/etc/nauthilus/lua-plugins.d/policy/registry.lua"
providers:
lua_subject_geoip:
kind: lua_subject
script_path: /etc/nauthilus/lua-plugins.d/subject/geoip.lua
targets: [{action: authenticate}]
executions: [host_sync]
domain_plans:
password:
checkpoints:
subject_analysis:
providers:
- name: geoip
use: authn/lua_subject_geoip
actions: [authenticate]
auth_decision:
providers: []
policy_sets:
configured:
visibility: private
rules:
- name: deny_geoip_rejection
checkpoint: auth_decision
actions: [authenticate]
if:
attribute: lua.plugin.geoip.rejected
is: true
then:
decision: deny
reason: geoip_policy_rejected
response_marker: auth.response.fail
response_message:
from: attribute_detail
attribute: lua.plugin.geoip.rejected
detail: status_message
fallback: "Invalid login or password"
targets:
- namespace: authn
action: authenticate
schema: authn/authenticate/v1
mode: enforce
default_policy: authn/standard_auth
domain_plan: authn/password
plans:
auth_decision:
policy_sets: [authn/configured]
Writing Your Own Emitter
Register the attribute:
nauthilus_policy.register_attribute({
id = "lua.plugin.example.risky",
stage = "subject_analysis",
operations = { "authenticate" },
category = "environment",
type = "bool",
description = "Example risk flag",
})
Emit it from a request-time Lua plugin:
local policy = require("nauthilus_policy")
policy.emit_attribute({
id = "lua.plugin.example.risky",
value = true,
})
The emitted type, operation, and stage must match the registry definition.
Lua backends can emit the built-in master-user fact through the dedicated helper:
local policy = require("nauthilus_policy")
policy.emit_master_user({
active = true,
master_user = "admin@example.test",
target_user = "alice@example.test",
})
This records auth.master_user.active for the active authenticate request and attaches master_user and target_user details when provided.
Generic Target-Aware Lua Providers
The emitter interface above belongs to the authentication pipeline. A generic Policy target instead uses a provider
with kind: lua and one or both exact callbacks:
_G["policy.facts.collect"] = function(request)
return {
facts = {
{name = "risk_score", value = {kind = "integer", value = "42"}},
},
}
end
_G["policy.effects.execute"] = function(request)
return {state = "succeeded"}
end
Each invocation receives a fresh restricted Lua state. Generic scripts have deterministic base, table, string, and math
operations, but no io, os, package, require, file loading, dynamic loading, or legacy Nauthilus modules. Host
deadlines and cancellation stop execution, and globals cannot leak into the next request.
policy.facts.collect receives detached, redacted target, caller, and admitted facts views. It returns either a
bounded fact list or one closed error_class (invalid_input, unavailable, timeout, or internal). Local names are
host-qualified as lua.<module>.<name> and must match produced_facts, the target schema, source lua, type, and bounds.
Unknown fields, duplicate facts, foreign authorities, or invalid values are contract violations and fail closed.
policy.effects.execute is called only for a policy-selected obligation whose typed parameters already passed the
effect schema. Its closed results are succeeded, failed, and outcome_unknown; failed and unknown outcomes require
an error class. Lua cannot select a decision, change plan ordering, authorize a target, mutate a response, request a
retry, or detach its own work. The host owns synchronous finalization and accepted host_post_action scheduling.
policy:
namespaces:
dkim2:
providers:
risk:
kind: lua
module: reputation
script_path: /etc/nauthilus/lua/reputation.lua
targets: [{action: sign-message}]
produced_facts: [lua.reputation.risk_score]
executions: [host_sync]
failure: indeterminate
timeout: 100ms
Every value uses the same strict Policy representation: strings and booleans are native, signed integers are base-10 strings, doubles must be finite, byte values use standard Base64, timestamps use UTC RFC 3339, and string lists are dense and ordered. Empty lists and missing values remain distinct. Records are schema-defined ordered field lists; they cannot nest records recursively. See Generic Policy API for the wire form and Policy configuration for plans, schemas, failure handling, and effects.