| Package Name | Ecosystem | Vulnerable Versions | First Patched Version |
|---|---|---|---|
| stimulus_reflex | rubygems | >= 3.5.0.pre0, < 3.5.0.rc4 | 3.5.0.rc4 |
| stimulus_reflex | rubygems | < 3.4.2 | 3.4.2 |
| stimulus_reflex | npm | < 3.4.2 | 3.4.2 |
| stimulus_reflex | npm | >= 3.5.0-pre0, < 3.5.0-rc4 | 3.5.0-rc4 |
The vulnerability stems from two key functions: 1) The channel's method invocation logic allowed any public method (including from parent classes like StimulusReflex::Reflex) to be called if parameter counts matched, enabling access to dangerous methods like instance_variable_set. 2) The parameter validation policy focused only on arity checks rather than method ownership. The patch introduced method origin validation in ReflexFactory (verify_method_name!) and restricted methods to those explicitly defined in developer-controlled reflex ancestors, confirming these as the vulnerable points.
.aritysystem.arity == -1{
"target": "ChatReflex#system",
"args": ["[command here]"]
}
Using public_send instead of send does not help but the following payloads do not work since :rest parameters are not counted in the current version
{
"target": "ChatReflex#send",
"args": ["system", "[command here]"]
}
{
"target": "ChatReflex#instance_eval",
"args": ["system('[command here]')"]
}
Pre-versions of 3.5.0 added a render_collection method on reflexes with a :req parameter. Calling this method could lead to arbitrary code execution:
{
"target": "StimulusReflex::Reflex#render_collection",
"args": [
{ "inline": "<% system('[command here]') %>" }
]
}
Patches are available on RubyGems and on NPM.
The patched versions are:
You can add this guard to mitigate the issue if running an unpatched version of the library.
1.) Make sure all your reflexes inherit from the ApplicationReflex class
2.) Add this before_reflex callback to your app/reflexes/application_reflex.rb file:
class ApplicationReflex < StimulusReflex::Reflex
before_reflex do
ancestors = self.class.ancestors[0..self.class.ancestors.index(StimulusReflex::Reflex) - 1]
allowed = ancestors.any? { |a| a.public_instance_methods(false).any?(method_name.to_sym) }
raise ArgumentError.new("Reflex method '#{method_name}' is not defined on class '#{self.class.name}' or on any of its ancestors") if !allowed
end
end
Ongoing coverage of React2Shell