Class: RuboCop::Cop::DevDoc::Style::AvoidInsecureSend

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/style/avoid_insecure_send.rb

Overview

Avoid insecure dynamic send and public_send with an explicit receiver.

Rationale

send() can call any method, including destructive ones like destroy. The risk is specifically with dynamic method names — when the argument is a variable or interpolated string, a crafted value could invoke methods the developer never intended to expose.

public_send respects method visibility, but still allows calling any public method. When the method name is dynamic, a prefix restricts the callable surface to methods sharing that prefix.

Rules

send() with a dynamic argument is always flagged. There is no safe use case for dynamic send — use public_send instead, and disable with a justification when bypassing visibility is intentional.

public_send() with a dynamic argument is allowed when:

  • The argument is a literal symbol or string (method name is fixed at code-write time).
  • The call uses a prefix pattern ("prefix_#{method_name}") that restricts callable methods.
  • The call is in a validator file (app/validators/) — method names come from model DSL, not user input.
  • The call is a mailer dispatch — method names are trusted internal symbols.

public_send() with a dynamic argument is flagged when:

  • The argument is an unrestricted variable or interpolation with no static prefix.

Safer alternatives

a) For model attributes — use bracket notation instead. @model[column_name] only accesses database columns, so it cannot accidentally invoke methods like destroy.

 Dangerous  method_name could be :destroy or any other method
@user.send(method_name)

✔️ Safe  only accesses database columns
@user[method_name]

b) For non-model objects — use a prefix to restrict callable methods. By interpolating the dynamic part into a fixed prefix, only methods with that prefix (e.g. export_csv, export_pdf) can be invoked, preventing accidental calls to unintended methods.

 Unrestricted  any method can be called
obj.public_send(method_name)

✔️ Restricted  only methods with the prefix can be called
obj.public_send("export_#{method_name}")

NOTE: A prefix narrows the callable surface but does not eliminate it — an attacker-controlled suffix can still reach any method sharing the prefix (e.g. "export_#{x}" could hit export_and_destroy). Use the narrowest prefix that fits, and prefer an explicit whitelist when the set of targets is small.

Examples:

# bad — dynamic method name from a variable
@user.send(method_name)
obj.public_send(action)

# bad — interpolation with no static prefix restricts nothing
obj.send("#{x}")
obj.send("#{x}_run")

# good — literal symbol: method name is statically visible
instance.public_send(:email)

# good — bracket notation for model attributes
@user[attribute_name]

# good — static prefix restricts the callable methods
obj.public_send("export_#{method_name}")

# good — validator pattern (trusted source)
record.public_send(attribute)

# good — mailer dispatch (trusted source)
mailer.public_send(action)

Constant Summary collapse

MSG_SEND =
"Avoid dynamic `send` — use `public_send` instead, or " \
"bracket notation for model attributes. Disable with a " \
"justification when bypassing visibility is intentional.".freeze
MSG_PUBLIC_SEND =
"Avoid unrestricted dynamic `public_send` — use a " \
"prefix (`obj.public_send(\"export_\#{x}\")`) to " \
"restrict callable methods, or bracket notation for " \
"model attributes.".freeze
RESTRICT_ON_SEND =

__send__ is the canonical alias — omitting it would leave a zero-cost dodge for the exact dynamic dispatch this cop restricts.

%i[send public_send __send__].freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



98
99
100
101
102
103
104
# File 'lib/rubocop/cop/dev_doc/style/avoid_insecure_send.rb', line 98

def on_send(node)
  return if node.receiver.nil?
  return if literal_argument?(node)
  return if prefixed_dynamic_method?(node)

  check_send(node)
end