rubocop-dev_doc 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,95 +0,0 @@
1
- module RuboCop
2
- module Cop
3
- module DevDoc
4
- module Style
5
- # Avoid dynamic `send` and `public_send` with an explicit receiver.
6
- #
7
- # ## Rationale
8
- # `send()` can call *any* method, including destructive ones like
9
- # `destroy`. The risk is specifically with **dynamic** method names —
10
- # when the argument is a variable or interpolated string, a crafted
11
- # value could invoke methods the developer never intended to expose.
12
- #
13
- # **Literal symbol arguments are exempt** — the method name is fixed at
14
- # code-write time and visible to reviewers, equivalent to a direct call.
15
- #
16
- # ## Safer alternatives
17
- #
18
- # **a) For model attributes — use bracket notation instead.**
19
- # `@model[column_name]` only accesses database columns, so it cannot
20
- # accidentally invoke methods like `destroy`.
21
- #
22
- # ❌ Dangerous — method_name could be :destroy or any other method
23
- # @user.send(method_name)
24
- #
25
- # ✔️ Safe — only accesses database columns
26
- # @user[method_name]
27
- #
28
- # **b) For non-model objects — use a prefix to restrict callable methods.**
29
- # By interpolating the dynamic part into a fixed prefix, only methods
30
- # with that prefix (e.g. `export_csv`, `export_pdf`) can be invoked,
31
- # preventing accidental calls to unintended methods.
32
- #
33
- # ❌ Unrestricted — any method can be called
34
- # obj.send(method_name)
35
- #
36
- # ✔️ Restricted — only methods with the prefix can be called
37
- # obj.send("export_#{method_name}")
38
- #
39
- # NOTE: A prefix narrows the callable surface but does not eliminate it —
40
- # an attacker-controlled suffix can still reach any method sharing the
41
- # prefix (e.g. `"export_#{x}"` could hit `export_and_destroy`). Use the
42
- # narrowest prefix that fits, and prefer an explicit whitelist when the
43
- # set of targets is small.
44
- #
45
- # @example
46
- # # bad — dynamic method name from a variable
47
- # @user.send(method_name)
48
- # obj.public_send(action)
49
- #
50
- # # bad — interpolation with no static prefix restricts nothing
51
- # obj.send("#{x}")
52
- # obj.send("#{x}_run")
53
- #
54
- # # good — literal symbol: method name is statically visible
55
- # instance.send(:private_helper, arg)
56
- #
57
- # # good — bracket notation for model attributes
58
- # @user[attribute_name]
59
- #
60
- # # good — static prefix restricts the callable methods
61
- # obj.send("export_#{method_name}")
62
- class AvoidSend < Base
63
- MSG = "Avoid dynamic `%<method>s` — use bracket notation for model attributes, " \
64
- "or a prefix (`obj.send(\"export_\#{x}\")`) to restrict callable methods.".freeze
65
- # `__send__` is the canonical alias — omitting it would leave a
66
- # zero-cost dodge for the exact dynamic dispatch this cop restricts.
67
- RESTRICT_ON_SEND = %i[send public_send __send__].freeze
68
-
69
- def on_send(node)
70
- return if node.receiver.nil?
71
-
72
- arg = node.first_argument
73
- return if arg&.sym_type?
74
- return if prefixed_dynamic_method?(arg)
75
-
76
- add_offense(node.loc.selector, message: format(MSG, method: node.method_name))
77
- end
78
-
79
- private
80
-
81
- # A dynamic string/symbol that begins with a static prefix (e.g.
82
- # `"export_#{x}"`) restricts the callable surface to methods sharing
83
- # that prefix, so it is exempt. Pure interpolation (`"#{x}"`) or a
84
- # trailing prefix (`"#{x}_run"`) restricts nothing and is still flagged.
85
- def prefixed_dynamic_method?(arg)
86
- return false unless arg&.type?(:dstr, :dsym)
87
-
88
- first = arg.children.first
89
- first&.str_type? && !first.value.empty?
90
- end
91
- end
92
- end
93
- end
94
- end
95
- end