Skip to content

docs: RULE can't be read from Lua in libModSecurity v3 - #6

Merged
fzipi merged 4 commits into
mainfrom
docs/lua-rule-collection
Nov 2, 2026
Merged

fzipi merged 4 commits into
mainfrom
docs/lua-rule-collection

Conversation

@airween

@airween airween commented Nov 1, 2026

Copy link
Copy Markdown
Member

Proposed changes

Document that the RULE collection can't be read from Lua in libModSecurity v3, and how to pass a rule field to a script instead, as agreed in owasp-modsecurity/ModSecurity#2527.

  • Configuration directives, SecRuleScript: a new note in the Lua section:
    • m.getvar() and m.getvars() can't read RULE, because the Lua API has no access to the rule that runs the script;
    • up to 3.0.17, such a call, or one with any other unknown variable name, aborts the process (Lua m.getvar()/m.getvars() with an unknown variable name aborts the process ModSecurity#3728); later versions return nil or an empty table and write a debug log message;
    • the workaround: setvar:tx.rule_id=%{RULE.id} in the rule that runs the script with exec, and m.getvar("TX.rule_id") in the script, with an example of both.
  • Variables, RULE: one sentence in the v3 paragraph, with a link to the note.

Closes owasp-modsecurity/ModSecurity#2527

Further comments

  • m.getvar() and m.getvars() resolve the name with Variable::stringMatchResolve() and stringMatchResolveMulti() (src/variables/variable.h), whose lists have no RULE.
  • The workaround depends on the action order: libModSecurity v3 runs setvar in executeActionsIndependentOfChainedRuleResult(), before exec runs in executeActionsAfterFullMatch() (src/rule_with_operator.cc). The regression test added in the fix for #3728 checks this (misc-lua-getvar-unknown-variable.json, case 6).
  • The "later versions" sentence assumes that the fix for #3728 ships in the release after 3.0.17. If it ships later, the version needs to be updated before this is merged.

AI disclosure

Made with help of Claude Code, Opus 5.5

m.getvar() and m.getvars() resolve names without access to the rule
that runs the script, so the RULE collection isn't available to them.
Up to 3.0.17, such a call, or one with any other unknown name, aborts
the process (owasp-modsecurity/ModSecurity#3728); later versions
return nil or an empty table.

Add a note to the Lua section of SecRuleScript with the workaround:
copy the rule field into TX with setvar in the rule that runs the
script with exec, and read it from TX. libModSecurity v3 runs setvar
before exec, so the value is set when the script starts. Link the RULE
entry to the note.

Refs owasp-modsecurity/ModSecurity#2527

@fzipi fzipi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the note against the v3 code and #3730, and it's accurate: m.getvar()/m.getvars() resolve names through stringMatchResolve()/stringMatchResolveMulti(), which have no RULE; #3730 returns nil or an empty table and logs at level 2; and setvar runs in executeActionsIndependentOfChainedRuleResult() before exec runs in executeActionsAfterFullMatch(). Case 6 of the new regression test covers the workaround.

Approving on one condition: please merge this only after #3730 is merged, and check that the "later versions" wording matches the release #3730 lands in. Two small nits are inline.

The failing syntax-check job is lychee reporting a 404 on an existing web.archive.org link in this file, which this PR doesn't touch.


{{% notice note %}}

In libModSecurity v3, m.getvar() and m.getvars() can't read the [RULE]({{% relref "3-reference-manual/3-3-variables#rule" %}}) collection, because the Lua API has no access to the rule that runs the script. In libModSecurity 3.0.17 and earlier, calling them with RULE, or with any other variable name the Lua API doesn't know, aborts the process ([#3728](https://github.com/owasp-modsecurity/ModSecurity/issues/3728)); later versions return nil from m.getvar() and an empty table from m.getvars(), and write a debug log message. If a script started with the exec action needs a field of its rule, copy it into a transaction variable with setvar in the same rule (libModSecurity v3 runs setvar before exec), and read that variable from the script:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: since this note sits in the SecRuleScript section, readers may try the workaround there. In a SecRuleScript rule, the script is the operator, so that rule's own setvar runs after the script. Maybe add one sentence: for SecRuleScript, set the TX variable in an earlier rule.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @fzipi! I've applied the tostring() suggestion.

About SecRuleScript, I checked it with a regression test, and it turned out differently from both of our guesses:

  • The order isn't the problem. RuleScript::evaluate() (src/rule_script.cc) runs the setvar actions before m_lua.run(), and the debug log shows the same.
  • But %{RULE.id} expands to an empty string there, so the script gets nil. The same happens in a SecAction. Only a SecRule works.

The cause is in RunTimeString::evaluate() (src/run_time_string.cc:62): it casts the rule to RuleWithOperator, and RuleScript and RuleUnconditional aren't one, so the RULE variable gets NULL. An earlier rule wouldn't help either, because %{RULE.id} there is the earlier rule's id. I've opened owasp-modsecurity/ModSecurity#3733 for it.

So I've added this to the note: the workaround works in a SecRule; in a SecAction or a SecRuleScript rule, set the value as a constant until #3733 is fixed, and in a SecRuleScript rule the constant can go in the rule itself, because its setvar runs before the script.

Co-authored-by: Felipe Zipitría <3012076+fzipi@users.noreply.github.com>
In a SecAction or a SecRuleScript rule, libModSecurity v3 expands
%{RULE.id} to an empty string, so the setvar workaround only works in
a SecRule. Say so, and suggest a constant instead. A SecRuleScript rule
runs its setvar actions before the script, so the constant can be set
in that rule.

Refs owasp-modsecurity/ModSecurity#2527
@airween
airween requested a review from fzipi November 2, 2026 14:54
@fzipi
fzipi merged commit f382939 into main Nov 2, 2026
1 check passed
@fzipi
fzipi deleted the docs/lua-rule-collection branch November 2, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can I use m.getvar("RULE.id") to get the current rule id in 3.x version?

2 participants