Class: RuboCop::Cop::DevDoc::Style::AvoidSymbolizingBoundaryInput
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Style::AvoidSymbolizingBoundaryInput
- Defined in:
- lib/rubocop/cop/dev_doc/style/avoid_symbolizing_boundary_input.rb
Overview
Flag .to_sym (including &.to_sym, and through laundering hops
like .presence/.to_s/.downcase) chained directly onto an
attacker-controlled boundary source: params[...] (also .dig,
.fetch, .require) or request.headers[...].
Rationale
A bare .to_sym lets ANY client-supplied value cross into the symbol
domain, where downstream case/== dispatch treats it as known
vocabulary. The allow-list is the missing DECISION: which values do
we recognise, and what happens to the rest? Skipping it also mints a
symbol per novel value (the classic symbol-DoS vector; GC reclaims
dynamic symbols since Ruby 2.2, so today this is churn rather than a
hard leak — the vocabulary laundering is the real bug).
Resolve the value through the glib-web helper, which only ever returns a symbol already in the allow-list:
❌
@mode = params[:mode]&.to_sym || :active
✔️ (glib-web >= 5.1.2)
@mode = glib_allowlist_symbol_param(params[:mode], MODES, default: :active)
When recognised inputs must map to differently-spelled symbols
(e.g. 'spring-promo' → :spring_promo), use an explicit
case-when at the boundary instead — same allow-list property.
Sources detected
params[…],params.dig(…),params.fetch(…),params.require(…)request.headers[…]
ENV[…] is deliberately NOT a source here (unlike the sibling
DevDoc/Style/StringSymbolComparison): ENV is operator-controlled,
not attacker-reachable, and .to_sym on boot-time config is
legitimate where the controller helper doesn't even exist.
Relationship with DevDoc/Style/StringSymbolComparison
Complementary pair over the same boundary doctrine: that cop flags comparing an UNCONVERTED string source against a symbol (always false); this cop flags CONVERTING without validating. The sanctioned exit from both is the allow-list helper.
Limitations
Only the direct form is caught. Once the value flows through a
helper method (report_params[:x]) or a local/instance variable,
static analysis loses the boundary and review has to catch it.
Laundering transforms (.presence, .to_s, .strip,
.downcase, .upcase, .squish) do NOT reset the trail — they
transform, they don't validate.
Constant Summary collapse
- MSG =
'Unvalidated `.to_sym` on `%<source>s` lets any client value into the symbol ' \ 'domain (and mints a symbol per novel value). Allow-list it instead: ' \ '`glib_allowlist_symbol_param(value, ALLOWED, default:)`. Laundering through ' \ '`.to_s`/`.presence`/`.downcase` is not validation.'.freeze
- RESTRICT_ON_SEND =
%i[to_sym].freeze
- TRANSFORM_HOPS =
String-preserving transforms that commonly sit between the boundary read and the
.to_sym— they launder the value, not validate it, so the chain is still an offense. %i[presence to_s strip downcase upcase squish].freeze
Instance Method Summary collapse
- #on_send(node) ⇒ Object (also: #on_csend)
Instance Method Details
#on_send(node) ⇒ Object Also known as: on_csend
85 86 87 88 89 90 |
# File 'lib/rubocop/cop/dev_doc/style/avoid_symbolizing_boundary_input.rb', line 85 def on_send(node) source = boundary_source_in(node.receiver) return unless source add_offense(node, message: format(MSG, source: source.source)) end |