refactor(dynamic): enhance Ruby harness with framework-specific route replay logic (Sinatra, Rails, Hanami), extend Gemfile staging, and update tests/fixtures

This commit is contained in:
elipeter 2026-05-26 12:59:02 -05:00
parent 41c7b73575
commit aaf49acefb
20 changed files with 773 additions and 218 deletions

View file

@ -1,8 +1,5 @@
source 'https://rubygems.org'
# Phase 15 fixture — Hanami Action shape. The adapter only inspects
# the class superclass / include list; the harness never actually
# boots `Hanami::Application`, so the gem is informational for
# cargo-side fixture pickup.
gem 'hanami'
# Hanami action fixture. The harness invokes the action with a Rack env.
gem 'hanami-controller'
gem 'rack'

View file

@ -1,13 +1,19 @@
# Phase 15 — Hanami Action.call, benign.
# Validates payload before running the fixed echo.
# Ruby Hanami Action.call, benign.
# Validates the real request parameter before running a fixed echo.
# nyx-shape: hanami
# nyx-route: GET /run
require 'hanami/action'
require 'rack/request'
class RunAction < Hanami::Action
def call(req)
payload = req && req.is_a?(Hash) ? (req['nyx.payload'] || '') : (ENV['NYX_PAYLOAD'] || '')
payload = if req.is_a?(Hash)
Rack::Request.new(req).params['payload'].to_s
elsif req.respond_to?(:params)
req.params['payload'].to_s
else
ENV['NYX_PAYLOAD'].to_s
end
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
return "invalid"

View file

@ -1,15 +1,21 @@
# Phase 15 — Hanami Action.call, vulnerable.
# Class includes Hanami::Action and exposes a `call` method that pipes
# the request body into /bin/sh.
# Ruby Hanami Action.call, vulnerable.
# The class imports Hanami::Action and reads the Rack request routed by
# the harness.
# nyx-shape: hanami
# nyx-route: GET /run
require 'hanami/action'
require 'rack/request'
class RunAction < Hanami::Action
def call(req)
STDOUT.print("__NYX_SINK_HIT__\n")
payload = req && req.is_a?(Hash) ? (req['nyx.payload'] || '') : (ENV['NYX_PAYLOAD'] || '')
payload = if req.is_a?(Hash)
Rack::Request.new(req).params['payload'].to_s
elsif req.respond_to?(:params)
req.params['payload'].to_s
else
ENV['NYX_PAYLOAD'].to_s
end
out = `echo hello #{payload}`
STDOUT.print(out)
out

View file

@ -1,6 +1,5 @@
source 'https://rubygems.org'
# Phase 15 fixture — Rack middleware shape. The harness constructs
# a Rack-shaped env hash and dispatches; the rack gem is not required
# at runtime because the env-hash invocation pattern is standalone.
# Rack middleware fixture. The harness builds the env through
# Rack::MockRequest before dispatching the middleware.
gem 'rack'

View file

@ -1,7 +1,5 @@
source 'https://rubygems.org'
# Phase 15 fixture — Rails action shape. The harness instantiates
# the controller via .new and calls the action through reflection;
# the rails gem is not actually required at runtime. The Gemfile is
# informational so cargo-side fixture pickup sees a non-empty manifest.
gem 'rails'
# ActionController fixture. The harness calls the controller's Rack
# endpoint with Rack::MockRequest.
gem 'actionpack'

View file

@ -1,24 +1,21 @@
# Phase 15 — Rails-style controller action, benign.
# Ruby ActionController action, benign.
class ApplicationController
def initialize; end
require 'action_controller'
class ApplicationController < ActionController::Base
self.view_paths = []
end
class UsersController < ApplicationController
def initialize
super
@__nyx_payload = nil
@__nyx_request = nil
end
def index
payload = @__nyx_payload || ENV['NYX_PAYLOAD'] || ''
payload = params[:payload].to_s
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
return "invalid"
render plain: "invalid"
return
end
out = `echo hello`
STDOUT.print(out)
out
render plain: out
end
end

View file

@ -1,23 +1,18 @@
# Phase 15 — Rails-style controller action, vulnerable.
# Controller inherits the conventional ApplicationController name so
# RubyShape::detect picks RailsAction.
# Ruby ActionController action, vulnerable.
# The harness drives UsersController.action(:index) through Rack.
class ApplicationController
def initialize; end
require 'action_controller'
class ApplicationController < ActionController::Base
self.view_paths = []
end
class UsersController < ApplicationController
def initialize
super
@__nyx_payload = nil
@__nyx_request = nil
end
def index
STDOUT.print("__NYX_SINK_HIT__\n")
payload = @__nyx_payload || ENV['NYX_PAYLOAD'] || ''
payload = params[:payload].to_s
out = `echo hello #{payload}`
STDOUT.print(out)
out
render plain: out
end
end

View file

@ -1,6 +1,5 @@
source 'https://rubygems.org'
# Phase 15 fixture — Sinatra route shape. The harness emits its own
# route registry shim so the real sinatra gem is not required at
# runtime; the Gemfile is informational for cargo-side fixture pickup.
# Sinatra route fixture. The harness replays a Rack request through the
# real Sinatra app class.
gem 'sinatra'

View file

@ -1,13 +1,20 @@
# Phase 15 — Sinatra route, benign.
# Validates payload then runs a fixed echo.
# Ruby Sinatra route, benign.
# Validates the real path-capture parameter before running a fixed echo.
# nyx-shape: sinatra
get '/run' do |payload|
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
next "invalid"
require 'sinatra/base'
class NyxSinatraApp < Sinatra::Base
set :environment, :test
disable :run
get '/run/:payload' do |payload|
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
"invalid"
else
out = `echo hello`
STDOUT.print(out)
out
end
end
out = `echo hello`
STDOUT.print(out)
out
end

View file

@ -1,11 +1,16 @@
# Phase 15 — Sinatra route, vulnerable.
# Reads payload (passed by harness via block argument) and pipes through /bin/sh.
# Entry: route block Cap: CODE_EXEC
# Ruby Sinatra route, vulnerable.
# Reads a real path-capture parameter from Sinatra and pipes it through /bin/sh.
# nyx-shape: sinatra
get '/run' do |payload|
STDOUT.print("__NYX_SINK_HIT__\n")
out = `echo hello #{payload}`
STDOUT.print(out)
out
require 'sinatra/base'
class NyxSinatraApp < Sinatra::Base
set :environment, :test
disable :run
get '/run/:payload' do |payload|
STDOUT.print("__NYX_SINK_HIT__\n")
out = `echo hello #{payload}`
STDOUT.print(out)
out
end
end