Skip to content

Commit 2f1e98d

Browse files
justin808claude
andcommitted
Security fixes and generator improvements
- Fix command injection vulnerabilities in dev tools - Replace string interpolation with array-form system() calls - Use Open3.capture2 instead of backticks for pgrep - Add procfile path validation - Harden PID validation with Integer() and exception handling - Improve Shakapacker installation in generators - Use bundle add + bundle exec for proper gem management - Return boolean from ensure_shakapacker_installed for error handling - Enhance gem detection with broader rescue clause - Modernize package manager usage - Replace yarn references with npm in generators and templates - Update Redux generator to use npm install instead of yarn add - Fix spec file issues - Add missing require statements for dev classes - Replace RSpec anti-patterns (before(:all) with around hooks) - Fix Kernel mocking (allow_any_instance_of -> allow(Kernel)) - Use proper SystemExit expectations instead of stubbing exit - Remove obsolete registration.js.tt template (v15 uses auto-registration) - Fix environment variable concatenation in rake task 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 69705cd commit 2f1e98d

File tree

12 files changed

+103
-46
lines changed

12 files changed

+103
-46
lines changed

lib/generators/react_on_rails/install_generator.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class InstallGenerator < Rails::Generators::Base
2727

2828
def run_generators
2929
if installation_prerequisites_met? || options.ignore_warnings?
30-
ensure_shakapacker_installed
30+
return unless ensure_shakapacker_installed
31+
3132
invoke_generators
3233
add_bin_scripts
3334
add_post_install_message
@@ -91,24 +92,36 @@ def add_post_install_message
9192
end
9293

9394
def ensure_shakapacker_installed
94-
return if shakapacker_installed?
95+
return true if shakapacker_installed?
9596

9697
GeneratorMessages.add_info("Shakapacker not detected. Installing Shakapacker...")
9798

98-
result = system("rails shakapacker:install")
99-
unless result
100-
GeneratorMessages.add_error("Failed to install Shakapacker automatically. " \
101-
"Please run 'rails shakapacker:install' manually.")
102-
return
99+
added = system("bundle", "add", "shakapacker")
100+
unless added
101+
GeneratorMessages.add_error(<<~MSG.strip)
102+
Failed to add Shakapacker to your Gemfile.
103+
Please run 'bundle add shakapacker' manually and re-run the generator.
104+
MSG
105+
return false
106+
end
107+
108+
installed = system("bundle", "exec", "rails", "shakapacker:install")
109+
unless installed
110+
GeneratorMessages.add_error(<<~MSG.strip)
111+
Failed to install Shakapacker automatically.
112+
Please run 'bundle exec rails shakapacker:install' manually.
113+
MSG
114+
return false
103115
end
104116

105117
GeneratorMessages.add_info("Shakapacker installed successfully!")
118+
true
106119
end
107120

108121
def shakapacker_installed?
109122
Gem::Specification.find_by_name("shakapacker")
110123
true
111-
rescue Gem::MissingSpecError
124+
rescue Gem::MissingSpecError, Gem::LoadError
112125
false
113126
end
114127

lib/generators/react_on_rails/react_with_redux_generator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def create_appropriate_templates
5959
"app/views/hello_world/index.html.erb", config)
6060
end
6161

62-
def add_redux_yarn_dependencies
63-
run "yarn add redux react-redux"
62+
def add_redux_npm_dependencies
63+
run "npm install redux react-redux"
6464
end
6565

6666
def add_redux_specific_messages

lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt

Lines changed: 0 additions & 8 deletions
This file was deleted.

lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ ReactOnRails.configure do |config|
2020
#
2121
# ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
2222
#
23-
# with rspec then this controls what yarn command is run
23+
# with rspec then this controls what npm command is run
2424
# to automatically refresh your webpack assets on every test run.
2525
#
2626
# Alternately, you can remove the `ReactOnRails::TestHelper.configure_rspec_to_compile_assets`

lib/react_on_rails/dev/file_manager.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,19 @@ def cleanup_rails_pid_file
3030
server_pid_file = "tmp/pids/server.pid"
3131
return false unless File.exist?(server_pid_file)
3232

33-
pid = File.read(server_pid_file).to_i
33+
pid_content = File.read(server_pid_file).strip
34+
begin
35+
pid = Integer(pid_content)
36+
# PIDs must be > 1 (0 is kernel, 1 is init)
37+
if pid <= 1
38+
remove_file_if_exists(server_pid_file, "stale Rails pid file")
39+
return true
40+
end
41+
rescue ArgumentError, TypeError
42+
remove_file_if_exists(server_pid_file, "stale Rails pid file")
43+
return true
44+
end
45+
3446
return false if process_running?(pid)
3547

3648
remove_file_if_exists(server_pid_file, "stale Rails pid file")
@@ -45,6 +57,12 @@ def process_running?(pid)
4557
true
4658
rescue Errno::ESRCH
4759
false
60+
rescue Errno::EPERM
61+
# Process exists but we don't have permission to signal it
62+
true
63+
rescue ArgumentError, RangeError
64+
# Invalid PID (negative, too large, wrong type)
65+
false
4866
end
4967

5068
def remove_file_if_exists(file_path, description)

lib/react_on_rails/dev/process_manager.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ module Dev
55
class ProcessManager
66
class << self
77
def installed?(process)
8-
IO.popen "#{process} -v"
8+
IO.popen([process, "-v"]) { |io| io.close }
9+
true
910
rescue Errno::ENOENT
1011
false
1112
end
@@ -21,13 +22,19 @@ def ensure_procfile(procfile)
2122
end
2223

2324
def run_with_process_manager(procfile)
25+
# Validate procfile path for security
26+
unless valid_procfile_path?(procfile)
27+
warn "ERROR: Invalid procfile path: #{procfile}"
28+
exit 1
29+
end
30+
2431
# Clean up stale files before starting
2532
FileManager.cleanup_stale_files
2633

2734
if installed?("overmind")
28-
system "overmind start -f #{procfile}"
35+
system("overmind", "start", "-f", procfile)
2936
elsif installed?("foreman")
30-
system "foreman start -f #{procfile}"
37+
system("foreman", "start", "-f", procfile)
3138
else
3239
warn <<~MSG
3340
NOTICE:
@@ -36,6 +43,18 @@ def run_with_process_manager(procfile)
3643
exit 1
3744
end
3845
end
46+
47+
private
48+
49+
def valid_procfile_path?(procfile)
50+
# Reject paths with shell metacharacters
51+
return false if procfile.match?(/[;&|`$(){}[\]<>]/)
52+
53+
# Ensure it's a readable file
54+
File.readable?(procfile)
55+
rescue
56+
false
57+
end
3958
end
4059
end
4160
end

lib/react_on_rails/dev/server_manager.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "English"
4+
require "open3"
45

56
module ReactOnRails
67
module Dev
@@ -58,7 +59,11 @@ def kill_running_processes
5859
end
5960

6061
def find_process_pids(pattern)
61-
`pgrep -f "#{pattern}" 2>/dev/null`.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
62+
stdout, _status = Open3.capture2("pgrep", "-f", pattern, err: File::NULL)
63+
stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
64+
rescue Errno::ENOENT
65+
# pgrep command not found
66+
[]
6267
end
6368

6469
def terminate_processes(pids)

rakelib/run_rspec.rake

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,13 @@ def run_tests_in(dir, options = {})
119119

120120
command_name = options.fetch(:command_name, path.basename)
121121
rspec_args = options.fetch(:rspec_args, "")
122-
env_vars = "#{options.fetch(:env_vars, '')} TEST_ENV_COMMAND_NAME=\"#{command_name}\""
123-
env_vars << "COVERAGE=true" if ENV["USE_COVERALLS"]
122+
123+
# Build environment variables as an array for proper spacing
124+
env_tokens = []
125+
env_tokens << options.fetch(:env_vars, '').strip unless options.fetch(:env_vars, '').strip.empty?
126+
env_tokens << "TEST_ENV_COMMAND_NAME=\"#{command_name}\""
127+
env_tokens << "COVERAGE=true" if ENV["USE_COVERALLS"]
128+
129+
env_vars = env_tokens.join(' ')
124130
sh_in_dir(path.realpath, "#{env_vars} bundle exec rspec #{rspec_args}")
125131
end

spec/react_on_rails/dev/file_manager_spec.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require_relative "../spec_helper"
4+
require "react_on_rails/dev/file_manager"
45

56
RSpec.describe ReactOnRails::Dev::FileManager do
67
# Suppress stdout/stderr during tests
@@ -18,12 +19,12 @@
1819

1920
describe ".cleanup_stale_files" do
2021
before do
21-
allow_any_instance_of(Kernel).to receive(:`).and_return("")
22+
allow(Kernel).to receive(:`).and_return("")
2223
end
2324

2425
context "when overmind is not running" do
2526
before do
26-
allow_any_instance_of(Kernel).to receive(:`).with("pgrep -f \"overmind\" 2>/dev/null").and_return("")
27+
allow(Kernel).to receive(:`).with("pgrep -f \"overmind\" 2>/dev/null").and_return("")
2728
end
2829

2930
it "removes stale overmind socket files" do
@@ -47,7 +48,7 @@
4748

4849
context "when overmind is running" do
4950
before do
50-
allow_any_instance_of(Kernel).to receive(:`).with("pgrep -f \"overmind\" 2>/dev/null").and_return("1234")
51+
allow(Kernel).to receive(:`).with("pgrep -f \"overmind\" 2>/dev/null").and_return("1234")
5152
allow(File).to receive(:exist?).and_return(false)
5253
end
5354

@@ -61,7 +62,7 @@
6162

6263
context "when Rails server pid file exists" do
6364
before do
64-
allow_any_instance_of(Kernel).to receive(:`).with("pgrep -f \"overmind\" 2>/dev/null").and_return("")
65+
allow(Kernel).to receive(:`).with("pgrep -f \"overmind\" 2>/dev/null").and_return("")
6566
allow(File).to receive(:exist?).with(".overmind.sock").and_return(false)
6667
allow(File).to receive(:exist?).with("tmp/sockets/overmind.sock").and_return(false)
6768
allow(File).to receive(:exist?).with("tmp/pids/server.pid").and_return(true)
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,43 @@
11
# frozen_string_literal: true
22

33
require_relative "../spec_helper"
4+
require "react_on_rails/dev/pack_generator"
45

56
RSpec.describe ReactOnRails::Dev::PackGenerator do
6-
# Suppress stdout/stderr during tests
7-
before(:all) do
8-
@original_stderr = $stderr
9-
@original_stdout = $stdout
10-
$stderr = File.open(File::NULL, "w")
11-
$stdout = File.open(File::NULL, "w")
12-
end
13-
14-
after(:all) do
15-
$stderr = @original_stderr
16-
$stdout = @original_stdout
7+
# Suppress stdout/stderr during tests using around hook
8+
around do |example|
9+
original_stderr = $stderr
10+
original_stdout = $stdout
11+
begin
12+
$stderr = File.open(File::NULL, "w")
13+
$stdout = File.open(File::NULL, "w")
14+
example.run
15+
ensure
16+
$stderr = original_stderr
17+
$stdout = original_stdout
18+
end
1719
end
1820

1921
describe ".generate" do
2022
it "runs pack generation successfully in verbose mode" do
2123
command = "bundle exec rake react_on_rails:generate_packs"
22-
allow_any_instance_of(Kernel).to receive(:system).with(command).and_return(true)
24+
allow(Kernel).to receive(:system).with(command).and_return(true)
2325

2426
expect { described_class.generate(verbose: true) }.not_to raise_error
2527
end
2628

2729
it "runs pack generation successfully in quiet mode" do
2830
command = "bundle exec rake react_on_rails:generate_packs > /dev/null 2>&1"
29-
allow_any_instance_of(Kernel).to receive(:system).with(command).and_return(true)
31+
allow(Kernel).to receive(:system).with(command).and_return(true)
3032

3133
expect { described_class.generate(verbose: false) }.not_to raise_error
3234
end
3335

3436
it "exits with error when pack generation fails" do
3537
command = "bundle exec rake react_on_rails:generate_packs > /dev/null 2>&1"
36-
allow_any_instance_of(Kernel).to receive(:system).with(command).and_return(false)
37-
expect_any_instance_of(Kernel).to receive(:exit).with(1)
38+
allow(Kernel).to receive(:system).with(command).and_return(false)
3839

39-
described_class.generate(verbose: false)
40+
expect { described_class.generate(verbose: false) }.to raise_error(SystemExit)
4041
end
4142
end
4243
end

0 commit comments

Comments
 (0)