File: no_auto_literal_strings.rb

package info (click to toggle)
ruby-sequel 4.37.0-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 8,076 kB
  • ctags: 5,629
  • sloc: ruby: 91,441; makefile: 2
file content (84 lines) | stat: -rw-r--r-- 2,676 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# frozen-string-literal: true
#
# The no_auto_literal_strings extension removes Sequel's automatic conversion
# of strings to literal strings in the dataset filter methods.  By default,
# Sequel considers a string passed to a filter method as a literal string:
#
#   DB[:table].where("name > 'A'")
#
# This is fine, except when the string is derived from user input:
#
#   DB[:table].where("name > '#{params[:user]}'") # SQL Injection!
#
# Sequel does support using placeholders for such strings:
#
#   DB[:table].where("name > ?", params[:user].to_s) # Safe
#
# However, if you forget to user placeholders, and pass a string to a filter
# method that is derived from user input, you open yourself up to SQL injection.
# With this extension, using a plain string in a filter method will result
# in an exception being raised.  You either need to explicitly use a literal
# string:
#
#   DB[:table].where(Sequel.lit("name > ?", params[:user].to_s))
#
# or you need to construct the same SQL using a non-string based approach:
#
#   DB[:table].where{|o| o.name > params[:user].to_s}
#
# Note that as listed in Sequel's security guide, a large number of dataset
# methods call down to a filtering method, and this protects all of those
# cases.
#
# This extension also protects the use of a plain string passed to Dataset#update:
#
#   DB[:table].update("column = column + 1")
#
# Again, you either need to explicitly use a literal string:
#
#   DB[:table].update(Sequel.lit("column = column + 1"))
#
# or construct the same SQL using a non-string based approach:
#
#   DB[:table].update(:column => Sequel.expr(:column) + 1)
#
# Related module: Sequel::Dataset::NoAutoLiteralStrings

#
module Sequel
  class Dataset
    module NoAutoLiteralStrings
      # Raise an error if passing a plain string or an array whose first
      # entry is a plain string.
      def filter_expr(expr = nil)
        case expr
        when LiteralString
          super
        when String
          raise Error, "plain string passed to a dataset filtering method"
        when Array
          if expr.first.is_a?(String) && !expr.first.is_a?(LiteralString)
            raise Error, "plain string passed to a dataset filtering method"
          end
          super
        else
          super
        end
      end

      # Raise an error if passing a plain string.
      def update_sql(values=OPTS)
        case values
        when LiteralString
          super
        when String
          raise Error, "plain string passed to a dataset filtering method"
        else
          super
        end
      end
    end

    register_extension(:no_auto_literal_strings, NoAutoLiteralStrings)
  end
end