Ruby on Rails
Add Nyoxis threat detection to a Rails application using a Rack middleware class, compatible with Rails 6 and later.
Prerequisites
- Ruby 3.0 or later
- Rails 6 or later
- A Nyoxis workspace API key — get one here
Install
bash
bundle add faradaynet/http from the Ruby standard library also works — see the alternative section at the bottom.
Middleware
Create app/middleware/nyoxis_waf.rb:
ruby
require "faraday"
require "json"
class NyoxisWAF
NYO_API = "https://api.nyoxis.com"
# @param app [#call] The Rack app to wrap
# @param api_key [String] Your Nyoxis workspace token
# @param block_on_high [Boolean] Return 403 when risk == "high"
# @param on_error [Symbol] :open (default) or :closed
def initialize(app, api_key:, block_on_high: false, on_error: :open)
raise ArgumentError, "api_key is required" if api_key.blank?
@app = app
@api_key = api_key
@block_on_high = block_on_high
@on_error = on_error
@conn = Faraday.new(url: NYO_API) do |f|
f.request :json
f.response :json
f.options.timeout = 3
end
end
def call(env)
req = Rack::Request.new(env)
payload = {
method: req.request_method,
path: req.path,
query: req.query_string.presence,
ip_addr: req.ip,
body: extract_body(env),
headers: extract_headers(env)
}.compact
response = @conn.post(
response = @conn.post(
"/v0/predict?api_key=#{@api_key}",
payload
)
verdict = response.body
env["nyoxis.verdict"] = verdict
if @block_on_high && dig(verdict, "prediction", "risk") == "high"
return [403, { "Content-Type" => "application/json" }, ['{"error":"Forbidden"}']]
end
@app.call(env)
rescue => e
Rails.logger.warn("[nyoxis] prediction error: #{e.message}")
if @on_error == :closed
[503, { "Content-Type" => "application/json" }, ['{"error":"Service unavailable"}']]
else
@app.call(env)
end
end
private
def extract_body(env)
input = env["rack.input"]
return nil unless input
body = input.read
input.rewind
body.presence
end
def extract_headers(env)
env.each_with_object({}) do |(k, v), h|
h[k.sub(/^HTTP_/, "").downcase.tr("_", "-")] = v if k.start_with?("HTTP_")
end
end
def dig(hash, *keys)
keys.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
end
endRegister in application.rb
ruby
# config/application.rb
require_relative "../app/middleware/nyoxis_waf"
module MyApp
class Application < Rails::Application
# Insert near the top of the middleware stack
config.middleware.insert_after(
ActionDispatch::RequestId,
NyoxisWAF,
api_key: ENV.fetch("NYOXIS_API_KEY"),
block_on_high: true,
)
end
endActing on the verdict
The verdict is stored in the Rack environment as nyoxis.verdict:
ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
before_action :check_nyoxis_verdict
private
def nyoxis_verdict
request.env["nyoxis.verdict"]
end
def check_nyoxis_verdict
prediction = nyoxis_verdict&.dig("prediction")
return unless prediction
if %w[medium high].include?(prediction["risk"])
render json: { error: "Forbidden" }, status: :forbidden
end
end
endAccess attack details in a specific controller:
ruby
class UsersController < ApplicationController
def search
prediction = nyoxis_verdict&.dig("prediction") || {}
attacks = prediction["attacks"] || []
if attacks.any? { |a| a["kind"] == "sql_injection" && a["confidence"].to_f > 0.8 }
Rails.logger.warn "[security] SQL injection signal on #{request.path}"
end
render json: { users: User.search(params[:q]) }
end
endAlternative: pure net/http (no Faraday)
ruby
require "net/http"
require "json"
require "uri"
def call_predict(payload, api_key)
uri = URI("https://api.nyoxis.com/v0/predict?api_key=#{api_key}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 3
http.read_timeout = 3
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = payload.to_json
JSON.parse(http.request(req).body)
rescue => e
Rails.logger.warn("[nyoxis] error: #{e.message}")
nil
endNext steps
- API Reference — complete field descriptions and status codes.
- Overview — how the classifier and redaction pipeline work.