From df5317a1c93708040076065a3e6586f7b3c302d8 Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Sun, 28 Aug 2022 01:55:04 -0700 Subject: [PATCH 1/9] feat: redis sandboxing --- .circleci/config.yml | 1 + .../patches/sandbox/redis_readonly.rb | 52 +++++++++++++++++++ safer_rails_console.gemspec | 1 + spec/integration/patches/sandbox_spec.rb | 16 ++++++ spec/internal/rails_6_0/Gemfile | 1 + spec/internal/rails_6_1/Gemfile | 1 + spec/internal/rails_7_0/Gemfile | 1 + 7 files changed, 73 insertions(+) create mode 100644 lib/safer_rails_console/patches/sandbox/redis_readonly.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index c1a3bd9..3555376 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,6 +41,7 @@ jobs: POSTGRES_USER: "circleci" POSTGRES_DB: "safer_rails_console_test" POSTGRES_HOST_AUTH_METHOD: "trust" + - image: cimg/redis:6.2.6 working_directory: ~/safer_rails_console steps: - checkout diff --git a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb new file mode 100644 index 0000000..74832ae --- /dev/null +++ b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module SaferRailsConsole + module Patches + module Sandbox + module RedisReadonly + # rubocop:disable Style/WordArray + WRITE_COMMANDS = %w{ + bitop restore pfdebug lpushx incr move getex decrby renamenx flushdb setex + setnx linsert rpush bzpopmax hset del copy xsetid georadiusbymember setrange blmove set rpop + lset xtrim zrangestore psetex xclaim append georadius incrbyfloat bitfield expire sort hsetnx + sadd zincrby lpop spop sunionstore getdel lrem zunionstore sdiffstore zremrangebyscore ltrim + bzpopmin xack pfadd unlink swapdb sinterstore zrem xgroup hdel zdiffstore xautoclaim xdel hmset + zpopmax zremrangebyrank setbit pexpireat mset hincrbyfloat incrby blpop getset expireat xreadgroup + hincrby migrate lmove pexpire flushall smove msetnx decr persist rpushx pfmerge xadd zremrangebylex + restore-asking geoadd rpoplpush zadd lpush srem brpoplpush zpopmin brpop geosearchstore zinterstore rename + }.freeze + + def self.raise_exception_on_write_command(command) + if WRITE_COMMANDS.include?(command.to_s) + raise ::Redis::CommandError.new("Write commands are not allowed in readonly mode: #{command}") + end + end + + def self.handle_and_reraise_exception(error) + if error.message.include?('Write commands are not allowed') + puts SaferRailsConsole::Colors.color_text( # rubocop:disable Rails/Output + 'An operation could not be completed due to read-only mode.', + SaferRailsConsole::Colors::RED + ) + end + + raise error + end + + module RedisPatch + def process(commands) + commands.flatten.each do |command| + SaferRailsConsole::Patches::Sandbox::RedisReadonly.raise_exception_on_write_command(command) + rescue Redis::CommandError => e + SaferRailsConsole::Patches::Sandbox::RedisReadonly.handle_and_reraise_exception(e) + end + + super + end + end + + ::Redis::Client.prepend(RedisPatch) if defined?(::Redis::Client) + end + end + end +end diff --git a/safer_rails_console.gemspec b/safer_rails_console.gemspec index 5f80a48..4f0036e 100644 --- a/safer_rails_console.gemspec +++ b/safer_rails_console.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'overcommit', '~> 0.39.0' spec.add_development_dependency 'pg', '~> 1.1' spec.add_development_dependency 'rake', '~> 12.0' + spec.add_development_dependency 'redis', '~> 4.7' spec.add_development_dependency 'rspec', '~> 3.6' spec.add_development_dependency 'rspec_junit_formatter' spec.add_development_dependency 'salsify_rubocop', '~> 1.27.0' diff --git a/spec/integration/patches/sandbox_spec.rb b/spec/integration/patches/sandbox_spec.rb index e35438e..2891fd9 100644 --- a/spec/integration/patches/sandbox_spec.rb +++ b/spec/integration/patches/sandbox_spec.rb @@ -27,6 +27,22 @@ end end + context "redis readonly" do + it "enforces readonly commands" do + # Run a console session that makes some redis changes + run_console_commands('Redis.new.set("test", "value")') + + # Run a new console session to ensure the redis changes were not saved + result = run_console_commands('puts "Redis.get(\'test\') = #{Redis.new.get(\'test\').nil?}"') # rubocop:disable Lint/InterpolationCheck Layout/LineLength + expect(result.stdout).to include('Redis.get("test") = true') + end + + it "lets the user know that an operation could not be completed" do + result = run_console_commands('Redis.new.set("test", "value")') + expect(result.stdout).to include('An operation could not be completed due to read-only mode.') + end + end + def run_console_commands(*commands) commands += ['exit'] run_console('--sandbox', input: commands.join("\n")) diff --git a/spec/internal/rails_6_0/Gemfile b/spec/internal/rails_6_0/Gemfile index 87e967a..9557c1c 100644 --- a/spec/internal/rails_6_0/Gemfile +++ b/spec/internal/rails_6_0/Gemfile @@ -4,5 +4,6 @@ source 'https://rubygems.org' gem 'pg' gem 'rails', '~> 6.0.0' +gem 'redis', '~> 4.0.0' gem 'safer_rails_console', path: '../../../' diff --git a/spec/internal/rails_6_1/Gemfile b/spec/internal/rails_6_1/Gemfile index 1474a50..59d5221 100644 --- a/spec/internal/rails_6_1/Gemfile +++ b/spec/internal/rails_6_1/Gemfile @@ -4,5 +4,6 @@ source 'https://rubygems.org' gem 'pg' gem 'rails', '~> 6.1.0' +gem 'redis', '~> 4.0.0' gem 'safer_rails_console', path: '../../../' diff --git a/spec/internal/rails_7_0/Gemfile b/spec/internal/rails_7_0/Gemfile index f9ffce2..2422942 100644 --- a/spec/internal/rails_7_0/Gemfile +++ b/spec/internal/rails_7_0/Gemfile @@ -8,5 +8,6 @@ source 'https://rubygems.org' gem 'pg' gem 'rails', '~> 7.0.0' +gem 'redis', '~> 4.0.0' gem 'safer_rails_console', path: '../../../' From 5bc44332564df517edcc8a9121e6537fc44e0cb5 Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Sun, 28 Aug 2022 01:57:40 -0700 Subject: [PATCH 2/9] lint --- lib/safer_rails_console/patches/sandbox/redis_readonly.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb index 74832ae..61829d3 100644 --- a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb +++ b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb @@ -15,6 +15,7 @@ module RedisReadonly hincrby migrate lmove pexpire flushall smove msetnx decr persist rpushx pfmerge xadd zremrangebylex restore-asking geoadd rpoplpush zadd lpush srem brpoplpush zpopmin brpop geosearchstore zinterstore rename }.freeze + # rubocop:enable Style/WordArray def self.raise_exception_on_write_command(command) if WRITE_COMMANDS.include?(command.to_s) From 24dddff60604974bd18b60bd1e7dd9b48a57701f Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Sun, 28 Aug 2022 02:08:22 -0700 Subject: [PATCH 3/9] fix spec --- spec/integration/patches/sandbox_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integration/patches/sandbox_spec.rb b/spec/integration/patches/sandbox_spec.rb index 2891fd9..7d793fb 100644 --- a/spec/integration/patches/sandbox_spec.rb +++ b/spec/integration/patches/sandbox_spec.rb @@ -33,8 +33,8 @@ run_console_commands('Redis.new.set("test", "value")') # Run a new console session to ensure the redis changes were not saved - result = run_console_commands('puts "Redis.get(\'test\') = #{Redis.new.get(\'test\').nil?}"') # rubocop:disable Lint/InterpolationCheck Layout/LineLength - expect(result.stdout).to include('Redis.get("test") = true') + result = run_console_commands('puts "Redis.get(\"test\").nil? = #{Redis.new.get(\'test\').nil?}"') # rubocop:disable Lint/InterpolationCheck Layout/LineLength + expect(result.stdout).to include('Redis.get("test").nil? = true') end it "lets the user know that an operation could not be completed" do From 9ea3c4a7e001624607bab5cc213362712cc27e2b Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Fri, 2 Dec 2022 10:17:16 -0800 Subject: [PATCH 4/9] switch to whitelist of readonly commands --- .../patches/sandbox/redis_readonly.rb | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb index 61829d3..aae1d69 100644 --- a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb +++ b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb @@ -4,21 +4,19 @@ module SaferRailsConsole module Patches module Sandbox module RedisReadonly - # rubocop:disable Style/WordArray - WRITE_COMMANDS = %w{ - bitop restore pfdebug lpushx incr move getex decrby renamenx flushdb setex - setnx linsert rpush bzpopmax hset del copy xsetid georadiusbymember setrange blmove set rpop - lset xtrim zrangestore psetex xclaim append georadius incrbyfloat bitfield expire sort hsetnx - sadd zincrby lpop spop sunionstore getdel lrem zunionstore sdiffstore zremrangebyscore ltrim - bzpopmin xack pfadd unlink swapdb sinterstore zrem xgroup hdel zdiffstore xautoclaim xdel hmset - zpopmax zremrangebyrank setbit pexpireat mset hincrbyfloat incrby blpop getset expireat xreadgroup - hincrby migrate lmove pexpire flushall smove msetnx decr persist rpushx pfmerge xadd zremrangebylex - restore-asking geoadd rpoplpush zadd lpush srem brpoplpush zpopmin brpop geosearchstore zinterstore rename - }.freeze - # rubocop:enable Style/WordArray + READ_COMMANDS = [ + 'hvals', 'bitcount', 'zscan', 'hget', 'smembers', 'hrandfield', 'zrevrange', 'bitpos', 'hlen', 'xlen', 'post', + 'zscore', 'dbsize', 'get', 'hstrlen', 'zrangebylex', 'scan', 'georadiusbymember_ro', 'zmscore', 'smismember', + 'zcount', 'lrange', 'stralgo', 'zrank', 'pttl', 'lpos', 'geopos', 'ttl', 'zrangebyscore', 'sdiff', 'llen', + 'sismember', 'zrevrangebyscore', 'zdiff', 'zrandmember', 'geodist', 'hexists', 'zrange', 'hmget', 'lindex', + 'zrevrangebylex', 'sunion', 'randomkey', 'zrevrank', 'xrange', 'xpending', 'hgetall', 'getrange', 'exists', + 'keys', 'georadius_ro', 'lolwut', 'hscan', 'object', 'zlexcount', 'type', 'geohash', 'touch', 'hkeys', + 'strlen', 'scard', 'substr', 'zinter', 'srandmember', 'mget', 'xinfo', 'geosearch', 'zunion', 'xread', + 'pfcount', 'xrevrange', 'sscan', 'memory', 'bitfield_ro', 'dump', 'host:', 'sinter', 'getbit', 'zcard' + ].freeze def self.raise_exception_on_write_command(command) - if WRITE_COMMANDS.include?(command.to_s) + unless READ_COMMANDS.include?(command.to_s) raise ::Redis::CommandError.new("Write commands are not allowed in readonly mode: #{command}") end end From 4e046fe3582cbc5c254ea3587379f2bf88e4d5dc Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Fri, 2 Dec 2022 10:23:50 -0800 Subject: [PATCH 5/9] fix spec --- spec/integration/patches/sandbox_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integration/patches/sandbox_spec.rb b/spec/integration/patches/sandbox_spec.rb index 7d793fb..f47dcf8 100644 --- a/spec/integration/patches/sandbox_spec.rb +++ b/spec/integration/patches/sandbox_spec.rb @@ -39,7 +39,7 @@ it "lets the user know that an operation could not be completed" do result = run_console_commands('Redis.new.set("test", "value")') - expect(result.stdout).to include('An operation could not be completed due to read-only mode.') + expect(result.stdout).to include('Write commands are not allowed in readonly mode: test') end end From 8f50e7706f7f2cfb2ce548d1ed842f5b01494d81 Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Fri, 2 Dec 2022 10:54:22 -0800 Subject: [PATCH 6/9] working with redis-rb --- lib/safer_rails_console/patches/sandbox/redis_readonly.rb | 4 ++-- spec/integration/patches/sandbox_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb index aae1d69..7d3a858 100644 --- a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb +++ b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb @@ -34,12 +34,12 @@ def self.handle_and_reraise_exception(error) module RedisPatch def process(commands) - commands.flatten.each do |command| + begin + command = commands.flatten.first SaferRailsConsole::Patches::Sandbox::RedisReadonly.raise_exception_on_write_command(command) rescue Redis::CommandError => e SaferRailsConsole::Patches::Sandbox::RedisReadonly.handle_and_reraise_exception(e) end - super end end diff --git a/spec/integration/patches/sandbox_spec.rb b/spec/integration/patches/sandbox_spec.rb index f47dcf8..7d793fb 100644 --- a/spec/integration/patches/sandbox_spec.rb +++ b/spec/integration/patches/sandbox_spec.rb @@ -39,7 +39,7 @@ it "lets the user know that an operation could not be completed" do result = run_console_commands('Redis.new.set("test", "value")') - expect(result.stdout).to include('Write commands are not allowed in readonly mode: test') + expect(result.stdout).to include('An operation could not be completed due to read-only mode.') end end From 905135b5642c06e2240ef9215a594a57e1918b49 Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Fri, 2 Dec 2022 11:28:32 -0800 Subject: [PATCH 7/9] handle redis-client as well --- .../patches/sandbox/redis_readonly.rb | 19 ++++++++++++++++--- safer_rails_console.gemspec | 1 + spec/integration/patches/sandbox_spec.rb | 16 ++++++++++++++++ spec/internal/rails_6_0/Gemfile | 1 + spec/internal/rails_6_1/Gemfile | 1 + spec/internal/rails_7_0/Gemfile | 1 + 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb index 7d3a858..b4674c9 100644 --- a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb +++ b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb @@ -15,9 +15,9 @@ module RedisReadonly 'pfcount', 'xrevrange', 'sscan', 'memory', 'bitfield_ro', 'dump', 'host:', 'sinter', 'getbit', 'zcard' ].freeze - def self.raise_exception_on_write_command(command) - unless READ_COMMANDS.include?(command.to_s) - raise ::Redis::CommandError.new("Write commands are not allowed in readonly mode: #{command}") + def self.raise_exception_on_write_command(command, service = ::Redis) + unless READ_COMMANDS.include?(command.downcase.to_s) + raise "#{service}".constantize::CommandError.new("Write commands are not allowed in readonly mode: #{command}") end end @@ -44,7 +44,20 @@ def process(commands) end end + module RedisClientPatch + def call(commands, redis_config) + command = commands.first + begin + SaferRailsConsole::Patches::Sandbox::RedisReadonly.raise_exception_on_write_command(command, ::RedisClient) + rescue RedisClient::CommandError => e + SaferRailsConsole::Patches::Sandbox::RedisReadonly.handle_and_reraise_exception(e) + end + super + end + end + ::Redis::Client.prepend(RedisPatch) if defined?(::Redis::Client) + ::RedisClient.register(RedisClientPatch) if defined?(::RedisClient) end end end diff --git a/safer_rails_console.gemspec b/safer_rails_console.gemspec index 4f0036e..2155e24 100644 --- a/safer_rails_console.gemspec +++ b/safer_rails_console.gemspec @@ -43,6 +43,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'pg', '~> 1.1' spec.add_development_dependency 'rake', '~> 12.0' spec.add_development_dependency 'redis', '~> 4.7' + spec.add_development_dependency 'redis-client', '~> 0.11' spec.add_development_dependency 'rspec', '~> 3.6' spec.add_development_dependency 'rspec_junit_formatter' spec.add_development_dependency 'salsify_rubocop', '~> 1.27.0' diff --git a/spec/integration/patches/sandbox_spec.rb b/spec/integration/patches/sandbox_spec.rb index 7d793fb..536b291 100644 --- a/spec/integration/patches/sandbox_spec.rb +++ b/spec/integration/patches/sandbox_spec.rb @@ -43,6 +43,22 @@ end end + context "redis-client readonly" do + it "enforces readonly commands" do + # Run a console session that makes some redis changes + run_console_commands('RedisClient.new.call("SET", "test", "value")') + + # Run a new console session to ensure the redis changes were not saved + result = run_console_commands('puts "RedisClient.new.call(\"GET\", \"test\").nil? = #{RedisClient.new.call(\'GET\', \'test\').nil?}"') # rubocop:disable Lint/InterpolationCheck Layout/LineLength + expect(result.stdout).to include('RedisClient.new.call("GET", "test").nil? = true') + end + + it "lets the user know that an operation could not be completed" do + result = run_console_commands('RedisClient.new.call("SET", "test", "value")') + expect(result.stdout).to include('An operation could not be completed due to read-only mode.') + end + end + def run_console_commands(*commands) commands += ['exit'] run_console('--sandbox', input: commands.join("\n")) diff --git a/spec/internal/rails_6_0/Gemfile b/spec/internal/rails_6_0/Gemfile index 9557c1c..78a426d 100644 --- a/spec/internal/rails_6_0/Gemfile +++ b/spec/internal/rails_6_0/Gemfile @@ -5,5 +5,6 @@ source 'https://rubygems.org' gem 'pg' gem 'rails', '~> 6.0.0' gem 'redis', '~> 4.0.0' +gem 'redis-client', '~> 0.11' gem 'safer_rails_console', path: '../../../' diff --git a/spec/internal/rails_6_1/Gemfile b/spec/internal/rails_6_1/Gemfile index 59d5221..3144e06 100644 --- a/spec/internal/rails_6_1/Gemfile +++ b/spec/internal/rails_6_1/Gemfile @@ -5,5 +5,6 @@ source 'https://rubygems.org' gem 'pg' gem 'rails', '~> 6.1.0' gem 'redis', '~> 4.0.0' +gem 'redis-client', '~> 0.11' gem 'safer_rails_console', path: '../../../' diff --git a/spec/internal/rails_7_0/Gemfile b/spec/internal/rails_7_0/Gemfile index 2422942..a34e402 100644 --- a/spec/internal/rails_7_0/Gemfile +++ b/spec/internal/rails_7_0/Gemfile @@ -9,5 +9,6 @@ source 'https://rubygems.org' gem 'pg' gem 'rails', '~> 7.0.0' gem 'redis', '~> 4.0.0' +gem 'redis-client', '~> 0.11' gem 'safer_rails_console', path: '../../../' From e172c4c2af999568d7a8438c4fe0ba7ca5d3b6f1 Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Fri, 2 Dec 2022 11:30:27 -0800 Subject: [PATCH 8/9] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c058a8..b2647f4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://circleci.com/gh/salsify/safer_rails_console.svg?style=svg)](https://circleci.com/gh/salsify/safer_rails_console) [![Gem Version](https://badge.fury.io/rb/safer_rails_console.svg)](https://badge.fury.io/rb/safer_rails_console) -This gem makes Rails console sessions less dangerous in specified environments by warning, color-coding, and auto-sandboxing PostgreSQL connections. In the future we'd like to extend this to make other external connections read-only too (e.g. disable job queueing, non-GET HTTP requests, etc.) +This gem makes Rails console sessions less dangerous in specified environments by warning, color-coding, and auto-sandboxing PostgreSQL and Redis connections. In the future we'd like to extend this to make other external connections read-only too (e.g. disable job queueing, non-GET HTTP requests, etc.) ## Installation From bb64558fd1aa00a4df5fe9fafeaa60bfc0a049a6 Mon Sep 17 00:00:00 2001 From: Miles Zimmerman Date: Fri, 2 Dec 2022 11:41:04 -0800 Subject: [PATCH 9/9] everything passes locally --- lib/safer_rails_console/patches/sandbox/redis_readonly.rb | 7 +++++-- spec/integration/patches/sandbox_spec.rb | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb index b4674c9..62bd51f 100644 --- a/lib/safer_rails_console/patches/sandbox/redis_readonly.rb +++ b/lib/safer_rails_console/patches/sandbox/redis_readonly.rb @@ -17,7 +17,9 @@ module RedisReadonly def self.raise_exception_on_write_command(command, service = ::Redis) unless READ_COMMANDS.include?(command.downcase.to_s) - raise "#{service}".constantize::CommandError.new("Write commands are not allowed in readonly mode: #{command}") + raise service.to_s.constantize::CommandError.new( + "Write commands are not allowed in readonly mode: #{command}" + ) end end @@ -48,7 +50,8 @@ module RedisClientPatch def call(commands, redis_config) command = commands.first begin - SaferRailsConsole::Patches::Sandbox::RedisReadonly.raise_exception_on_write_command(command, ::RedisClient) + SaferRailsConsole::Patches::Sandbox::RedisReadonly.raise_exception_on_write_command(command, + ::RedisClient) rescue RedisClient::CommandError => e SaferRailsConsole::Patches::Sandbox::RedisReadonly.handle_and_reraise_exception(e) end diff --git a/spec/integration/patches/sandbox_spec.rb b/spec/integration/patches/sandbox_spec.rb index 536b291..2764037 100644 --- a/spec/integration/patches/sandbox_spec.rb +++ b/spec/integration/patches/sandbox_spec.rb @@ -49,7 +49,10 @@ run_console_commands('RedisClient.new.call("SET", "test", "value")') # Run a new console session to ensure the redis changes were not saved - result = run_console_commands('puts "RedisClient.new.call(\"GET\", \"test\").nil? = #{RedisClient.new.call(\'GET\', \'test\').nil?}"') # rubocop:disable Lint/InterpolationCheck Layout/LineLength + result = run_console_commands( + 'puts "RedisClient.new.call(\"GET\", \"test\").nil? = ' \ + '#{RedisClient.new.call(\'GET\', \'test\').nil?}"' # rubocop:disable Lint/InterpolationCheck + ) expect(result.stdout).to include('RedisClient.new.call("GET", "test").nil? = true') end