levo_rails_middleware 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d6f38f672ca75adc4ccb2104c649e20f91866d6965cedc582c21ba5d1388221c
4
+ data.tar.gz: f0a9fbd1c9dd64ef5a6a11122861cb6ebce9070206f6247bdca3bc2894fb3ff8
5
+ SHA512:
6
+ metadata.gz: 04ac860aae7b6d04f95123988d855d5af0067dbff89e9d29da58010dd3b894d2e8607d0779d553e6a29bdc758a472aed5b9e1f0a66d768b671ffb53e6bcbb658
7
+ data.tar.gz: 0cbab4426082bc1eb894472667e184f46274fc58014efad51d737f58278fea9f84cfb92e451f7e678a09c2d9871a99b7b2a1a9979eae245bc6fcc587c70b4477
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Levo Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+
2
+ # Levo Rails Traffic Middleware
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/levo-rails-traffic-middleware.svg)](https://badge.fury.io/rb/levo-rails-traffic-middleware)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ A lightweight, high-performance middleware for mirroring your Rails application API traffic to the Levo.ai platform for API security analysis.
8
+
9
+ ## Overview
10
+
11
+ The Levo Rails Traffic Middleware captures HTTP requests and responses from your Rails application and securely sends them to the Levo.ai platform. This enables Levo.ai to provide API security analysis, identify vulnerabilities, and help protect your applications without requiring code changes or impacting performance.
12
+
13
+ Key features:
14
+ - **Zero-impact traffic mirroring**: Asynchronously sends data without affecting your application's response time
15
+ - **Configurable sampling rate**: Control how much traffic is mirrored
16
+ - **Sensitive data filtering**: Automatically filter confidential information
17
+ - **Path exclusion**: Skip static assets and health check endpoints
18
+ - **Size limits**: Prevent excessive data transmission for large payloads
19
+ - **Production-ready**: Built for high-throughput environments
20
+
21
+ ## Requirements
22
+
23
+ - Ruby 2.6 or later
24
+ - Rails 5.0 or later
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'levo_rails_middleware', git: 'https://github.com/levoai/levo-rails-traffic-middleware.git'
32
+ ```
33
+
34
+ Then execute:
35
+
36
+ ```bash
37
+ $ bundle update
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ After installing the gem, you need to:
43
+
44
+ 1. Configure the middleware
45
+ 2. Add it to your Rails application
46
+
47
+ ### Configuration
48
+
49
+ Create an initializer file at `config/initializers/levo_middleware.rb`:
50
+
51
+ ```ruby
52
+ require 'levo_rails_middleware'
53
+
54
+ LevoRailsMiddleware.configure do |config|
55
+ # Required: URL for the Levo.ai traffic collector
56
+ config.remote_url = ENV['LEVO_SATELLITE_URL']
57
+
58
+ # Optional configuration with defaults shown
59
+ config.sampling_rate = 1.0 # 100% of traffic
60
+ config.exclude_paths = ['/assets/', '/packs/', '/health']
61
+ config.filter_params = ['password', 'token', 'api_key', 'secret']
62
+ config.size_threshold_kb = 1024 # Skip bodies larger than 1MB config.timeout_seconds = 3
63
+ config.enabled = true
64
+ end
65
+
66
+ # Add the middleware to the Rails stack
67
+ LevoRailsmiddleware.instrument(Rails.application.config)
68
+ ```
69
+
70
+ ### Adding the Middleware
71
+
72
+ In your `config/application.rb` file, add:
73
+
74
+ ```ruby
75
+ module YourApp
76
+ class Application < Rails::Application
77
+ # ... other configurations ...
78
+
79
+ # Add the Levo.ai traffic mirroring middleware
80
+ require 'levo_rails_middleware'
81
+ LevoRailsmiddleware.instrument(config)
82
+ end
83
+ end
84
+ ```
85
+
86
+ ## Heroku Deployment
87
+
88
+ For Heroku applications, you'll need to set the environment variable for the Levo middleware URL:
89
+
90
+ ```bash
91
+ heroku config:set LEVO_SATELLITE_URL='https://collector.levo.ai (Replace with your own Satellite url'
92
+ heroku config:set LEVOAI_ORG_ID='your-org-id'
93
+ heroku config:set LEVO_ENV='your-environment-name, like Production or Staging'
94
+
95
+ ```
96
+
97
+ ## Configuration Options
98
+
99
+ | Option | Description | Default |
100
+ | ------ | ----------- | ------- |
101
+ | `remote_url` | The URL to send mirrored traffic to | `ENV['LEVO_SATELLITE_URL']` |
102
+ | `sampling_rate` | Percentage of requests to mirror (0.0 to 1.0) | `1.0` (100%) |
103
+ | `exclude_paths` | Array of path prefixes to exclude from mirroring | `['/assets/', '/packs/', '/health']` |
104
+ | `filter_params` | Array of parameter names to filter (sensitive data) | `['password', 'token', 'api_key', 'secret']` |
105
+ | `size_threshold_kb` | Maximum size (KB) for request/response bodies | `1024` (1MB) |
106
+ | `timeout_seconds` | Timeout for sending data to Levo.ai | `3` |
107
+ | `enabled` | Toggle to enable/disable the middleware | `true` |
108
+
109
+ ## Advanced Usage
110
+
111
+ ### Conditional Enabling
112
+
113
+ You may want to enable the middleware only in certain environments:
114
+
115
+ ```ruby
116
+ # In config/initializers/levo_middleware.rb
117
+ LevoRailsMiddleware.configure do |config|
118
+ config.remote_url = ENV['LEVO_SATELLITE_URL']
119
+ config.enabled = Rails.env.production? || Rails.env.staging?
120
+ end
121
+ ```
122
+
123
+ ### Custom Parameter Filtering
124
+
125
+ You can specify additional sensitive parameters to filter:
126
+
127
+ ```ruby
128
+ LevoRailsMiddleware.configure do |config|
129
+ config.filter_params = ['password', 'token', 'api_key', 'secret', 'ssn', 'credit_card']
130
+ end
131
+ ```
132
+
133
+ ### Traffic Sampling
134
+
135
+ For high-traffic applications, you can reduce the sampling rate:
136
+
137
+ ```ruby
138
+ LevoRailsMiddleware.configure do |config|
139
+ # Mirror only 10% of traffic
140
+ config.sampling_rate = 0.1
141
+ end
142
+ ```
143
+
144
+ ## Troubleshooting
145
+
146
+ ### Verifying Installation
147
+
148
+ To verify the middleware is properly installed, check your logs for entries containing `LEVO_MIRROR` when your application receives traffic.
149
+
150
+ ### Common Issues
151
+
152
+ **No data appearing in Levo.ai dashboard**
153
+
154
+ 1. Verify the `LEVO_SATELLITE_URL` is correct
155
+ 2. Check your application logs for any errors containing `LEVO_MIRROR`
156
+ 3. Ensure the middleware is enabled and the sampling rate is > 0
157
+ 4. Confirm your network allows outbound connections to the Levo.ai service
158
+
159
+ **Performance Impact**
160
+
161
+ The middleware is designed to have minimal impact on your application's performance. If you notice any impact:
162
+
163
+ 1. Lower the sampling rate to reduce the number of mirrored requests
164
+ 2. Increase the `exclude_paths` list to skip more endpoints
165
+ 3. Reduce the `size_threshold_kb` to skip large payloads
166
+
167
+ ## Support
168
+
169
+ For questions or issues, contact Levo.ai support at support@levo.ai or visit [help.levo.ai](https://help.levo.ai).
170
+
171
+ ## License
172
+
173
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LevoRailsmiddleware
4
+ class Configuration
5
+ attr_accessor :remote_url, :sampling_rate, :exclude_paths, :filter_params
6
+ attr_accessor :size_threshold_kb, :timeout_seconds, :enabled
7
+ attr_accessor :organization_id, :environment_name, :service_name, :host_name
8
+ attr_accessor :max_retries
9
+
10
+ def initialize
11
+ @remote_url = ENV['LEVO_SATELLITE_URL'] || 'https://collector.levo.ai'
12
+ @sampling_rate = 1.0 # 100% by default
13
+ @exclude_paths = ['/assets/', '/packs/', '/health']
14
+ @filter_params = ['password', 'token', 'api_key', 'secret']
15
+ @size_threshold_kb = 1024 # Skip bodies larger than 1MB
16
+ @timeout_seconds = 3
17
+ @enabled = true
18
+
19
+ # Levo Satellite specific configuration
20
+ @organization_id = ENV['LEVOAI_ORG_ID']
21
+ @environment_name = ENV['LEVO_ENV']
22
+ @service_name = ENV['LEVO_SERVICE_NAME'] || 'rails-application'
23
+ @host_name = get_hostname
24
+ @max_retries = 3
25
+ end
26
+
27
+ private
28
+
29
+ def get_hostname
30
+ # Try to get the hostname, fallback to 'unknown-host'
31
+ begin
32
+ Socket.gethostname
33
+ rescue
34
+ 'unknown-host'
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ module LevoRailsmiddleware
5
+ class Entry
6
+ attr_reader :timestamp, :duration_ms, :request, :response
7
+
8
+ def initialize(start_time, duration_ms, env, status, headers, body)
9
+ @timestamp = start_time
10
+ @duration_ms = duration_ms
11
+
12
+ # Create Rack request object
13
+ rack_request = Rack::Request.new(env)
14
+
15
+ # Extract request data
16
+ @request = {
17
+ method: rack_request.request_method,
18
+ path: rack_request.path,
19
+ query_string: rack_request.query_string,
20
+ headers: extract_headers(env),
21
+ body: extract_body(rack_request.body),
22
+ remote_ip: rack_request.ip,
23
+ request_id: env['HTTP_X_REQUEST_ID'] || env['action_dispatch.request_id']
24
+ }
25
+
26
+ # Extract response data
27
+ response_body = extract_response_body(body)
28
+
29
+ @response = {
30
+ status: status,
31
+ headers: headers_to_hash(headers),
32
+ body: response_body,
33
+ size: response_body.bytesize
34
+ }
35
+ end
36
+
37
+ def to_json
38
+ {
39
+ timestamp: @timestamp.iso8601,
40
+ duration_ms: @duration_ms,
41
+ request: @request,
42
+ response: @response,
43
+ environment: Rails.env
44
+ }.to_json
45
+ end
46
+
47
+ private
48
+
49
+ def extract_headers(env)
50
+ headers = {}
51
+ env.each do |key, value|
52
+ if key.start_with?('HTTP_')
53
+ header_name = key[5..-1].gsub('_', '-').downcase
54
+ headers[header_name] = value
55
+ end
56
+ end
57
+ headers
58
+ end
59
+
60
+ def extract_body(body)
61
+ return "".dup unless body # Return unfrozen empty string
62
+
63
+ body.rewind
64
+ content = body.read.to_s.dup # Force string conversion and unfreeze
65
+ body.rewind
66
+
67
+ # Check size threshold
68
+ if content.bytesize > LevoRailsmiddleware.configuration.size_threshold_kb * 1024
69
+ "[CONTENT TOO LARGE]"
70
+ else
71
+ filter_sensitive_data(content)
72
+ end
73
+ end
74
+
75
+ def extract_response_body(body)
76
+ # Start with unfrozen empty string
77
+ content = "".dup
78
+
79
+ # Safely collect response body parts
80
+ if body.respond_to?(:each)
81
+ body.each do |part|
82
+ # Create a duplicate of the string to avoid frozen string issues
83
+ content << part.to_s.dup
84
+ end
85
+ else
86
+ # Handle case where body might be a string or other object
87
+ content = body.to_s.dup
88
+ end
89
+
90
+ if content.bytesize > LevoRailsmiddleware.configuration.size_threshold_kb * 1024
91
+ "[CONTENT TOO LARGE]"
92
+ else
93
+ filter_sensitive_data(content)
94
+ end
95
+ end
96
+
97
+ def headers_to_hash(headers)
98
+ hash = {}
99
+ headers.each do |key, value|
100
+ hash[key.to_s] = value
101
+ end
102
+ hash
103
+ end
104
+
105
+ def filter_sensitive_data(content)
106
+ # Start with fresh unfrozen copy
107
+ filtered = content.to_s.dup
108
+ LevoRailsmiddleware.configuration.filter_params.each do |param|
109
+ # Simple regex to find and replace sensitive data
110
+ filtered.gsub!(/["']?#{param}["']?\s*[=:]\s*["']?[^"' &,\}]+["']?/, "#{param}=[FILTERED]")
111
+ end
112
+ filtered
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LevoRailsmiddleware
4
+ class Middleware
5
+ def initialize(app)
6
+ @app = app
7
+ @sender = Sender.new(LevoRailsmiddleware.configuration.remote_url)
8
+ rescue => e
9
+ LevoRailsmiddleware.log_exception("initializing middleware", e)
10
+ @initialization_failed = true
11
+ end
12
+
13
+ def call(env)
14
+ # Skip processing if initialization failed or middleware is disabled
15
+ return @app.call(env) if @initialization_failed || !LevoRailsmiddleware.configuration.enabled
16
+
17
+ # Skip excluded paths
18
+ path = env['PATH_INFO'] || ""
19
+ return @app.call(env) if should_skip?(path)
20
+
21
+ # Skip based on sampling rate
22
+ return @app.call(env) if rand > LevoRailsmiddleware.configuration.sampling_rate
23
+
24
+ start_time = Time.now
25
+
26
+ # Preserve the original request body
27
+ request_body = env['rack.input'].read
28
+ env['rack.input'].rewind
29
+
30
+ # Process the request through the app
31
+ status, headers, body = @app.call(env)
32
+ end_time = Time.now
33
+
34
+ # Calculate the request duration
35
+ duration_ms = ((end_time.to_f - start_time.to_f) * 1000).round
36
+
37
+ # Save the original input stream
38
+ saved_input = env['rack.input']
39
+ env['rack.input'] = StringIO.new(request_body)
40
+
41
+ # Create the request/response entry and send it
42
+ begin
43
+ entry = Entry.new(start_time, duration_ms, env, status, headers, body)
44
+ @sender.send_async(entry)
45
+ rescue => e
46
+ LevoRailsmiddleware.log_exception("processing request", e)
47
+ ensure
48
+ # Restore the original input stream
49
+ env['rack.input'] = saved_input
50
+ end
51
+
52
+ [status, headers, body]
53
+ rescue => e
54
+ LevoRailsmiddleware.log_exception("middleware execution", e)
55
+ @app.call(env) # Ensure we still call the app even if our middleware fails
56
+ end
57
+
58
+ private
59
+
60
+ def should_skip?(path)
61
+ LevoRailsmiddleware.configuration.exclude_paths.any? do |excluded|
62
+ path.start_with?(excluded)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'json'
5
+ require 'securerandom'
6
+
7
+ module LevoRailsmiddleware
8
+ class Sender
9
+ API_ENDPOINT = "/1.0/ebpf/traces"
10
+
11
+ def initialize(remote_url)
12
+ @remote_url = remote_url
13
+ @uri = URI.parse(remote_url) if remote_url
14
+ Rails.logger.info "LEVO_MIRROR: Initialized with URL #{@remote_url}" if defined?(Rails)
15
+ end
16
+
17
+ def send_async(entry)
18
+ return if @remote_url.nil?
19
+
20
+ # Use a separate thread to avoid blocking the request
21
+ Thread.new do
22
+ begin
23
+ send_data(entry)
24
+ rescue => e
25
+ LevoRailsmiddleware.log_exception("sending data", e)
26
+ ensure
27
+ # Ensure database connection is released
28
+ ActiveRecord::Base.connection_pool.release_connection if defined?(ActiveRecord)
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def send_data(entry)
36
+ # Convert entry to Levo Satellite format
37
+ trace = convert_to_satellite_format(entry)
38
+ json_data = JSON.generate([trace])
39
+
40
+ Rails.logger.debug "LEVO_MIRROR: JSON payload preview (first 200 chars): #{json_data[0..200]}..." if defined?(Rails)
41
+
42
+ # Create HTTP client
43
+ http = Net::HTTP.new(@uri.host, @uri.port)
44
+
45
+ # Configure HTTPS if needed
46
+ if @uri.scheme == 'https'
47
+ http.use_ssl = true
48
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
49
+ end
50
+
51
+ # Set timeouts
52
+ timeout = LevoRailsmiddleware.configuration.timeout_seconds
53
+ http.open_timeout = timeout
54
+ http.read_timeout = timeout
55
+
56
+ # Construct API path correctly
57
+ endpoint_path = API_ENDPOINT
58
+ base_path = @uri.path.to_s
59
+
60
+ # Ensure the path is properly formed
61
+ if base_path.empty?
62
+ full_path = endpoint_path
63
+ else
64
+ # Handle trailing slashes correctly
65
+ if base_path.end_with?('/') && endpoint_path.start_with?('/')
66
+ full_path = base_path + endpoint_path[1..-1]
67
+ elsif !base_path.end_with?('/') && !endpoint_path.start_with?('/')
68
+ full_path = "#{base_path}/#{endpoint_path}"
69
+ else
70
+ full_path = base_path + endpoint_path
71
+ end
72
+ end
73
+
74
+ # Create request with the properly formed path
75
+ request = Net::HTTP::Post.new(full_path)
76
+ request.content_type = 'application/json'
77
+
78
+ # Add Levo organization ID header
79
+ if LevoRailsmiddleware.configuration.organization_id
80
+ request['x-levo-organization-id'] = LevoRailsmiddleware.configuration.organization_id
81
+ end
82
+
83
+ # Add request ID header if available
84
+ request_id = entry.request[:request_id]
85
+ request['X-Request-ID'] = request_id if request_id
86
+
87
+ # Set the request body
88
+ request.body = json_data
89
+
90
+ # Send request with retry logic
91
+ response = nil
92
+ success = false
93
+
94
+ # Retry logic
95
+ max_retries = LevoRailsmiddleware.configuration.max_retries
96
+
97
+ for attempt in 0...max_retries
98
+ if attempt > 0
99
+ # Exponential backoff with jitter
100
+ backoff_ms = (2 ** attempt) * 100 + rand(1000)
101
+ sleep(backoff_ms / 1000.0)
102
+ Rails.logger.info "LEVO_MIRROR: Retry attempt #{attempt+1} for sending trace data" if defined?(Rails)
103
+ end
104
+
105
+ begin
106
+ response = http.request(request)
107
+
108
+ # Check status code
109
+ if response.code.to_i >= 200 && response.code.to_i < 300
110
+ success = true
111
+ break
112
+ else
113
+ Rails.logger.error "LEVO_MIRROR: Failed to send data. Status: #{response.code}" if defined?(Rails)
114
+ end
115
+ rescue => e
116
+ Rails.logger.error "LEVO_MIRROR: Error during HTTP request: #{e.message}" if defined?(Rails)
117
+ end
118
+ end
119
+
120
+ if success
121
+ Rails.logger.info "LEVO_MIRROR: Successfully sent trace data, status: #{response.code}" if defined?(Rails)
122
+ else
123
+ Rails.logger.error "LEVO_MIRROR: Failed to send trace after #{max_retries} attempts" if defined?(Rails)
124
+ end
125
+ end
126
+
127
+ def convert_to_satellite_format(entry)
128
+ # Generate trace and span IDs similar to the C++ implementation
129
+ trace_id = SecureRandom.uuid
130
+ span_id = SecureRandom.uuid
131
+
132
+ # Calculate duration in nanoseconds
133
+ duration_ns = entry.duration_ms * 1_000_000
134
+
135
+ # Get current time in nanoseconds
136
+ request_time_ns = (Time.now.to_f * 1_000_000_000).to_i
137
+
138
+ # Extract request method and path
139
+ method = entry.request[:method]
140
+ path = entry.request[:path]
141
+
142
+ # Convert headers to the expected format
143
+ request_headers = {}
144
+ entry.request[:headers].each do |name, value|
145
+ request_headers[name.downcase] = value
146
+ end
147
+
148
+ # Add special headers that the backend expects
149
+ request_headers[":path"] = path
150
+ request_headers[":method"] = method
151
+ request_headers[":authority"] = request_headers["host"] if request_headers["host"]
152
+
153
+ response_headers = {}
154
+ entry.response[:headers].each do |name, value|
155
+ response_headers[name.downcase] = value
156
+ end
157
+
158
+ # Add special response headers
159
+ response_headers[":status"] = entry.response[:status].to_s
160
+
161
+ # Build the trace structure
162
+ {
163
+ "http_scheme" => (request_headers['x-forwarded-proto'] || 'http'),
164
+ "request" => {
165
+ "headers" => request_headers,
166
+ "body" => Base64.strict_encode64(entry.request[:body].to_s),
167
+ "truncated" => false
168
+ },
169
+ "response" => {
170
+ "headers" => response_headers,
171
+ "body" => Base64.strict_encode64(entry.response[:body].to_s),
172
+ "truncated" => false,
173
+ "status_code" => entry.response[:status]
174
+ },
175
+ "resource" => {
176
+ "service_name" => LevoRailsmiddleware.configuration.service_name,
177
+ "host_name" => LevoRailsmiddleware.configuration.host_name,
178
+ "telemetry_sdk_language" => "ruby",
179
+ "telemetry_sdk_name" => "levo_rails_middleware",
180
+ "telemetry_sdk_version" => LevoRailsmiddleware::VERSION,
181
+ "levo_env" => LevoRailsmiddleware.configuration.environment_name
182
+ },
183
+ "duration_ns" => duration_ns,
184
+ "request_time_ns" => request_time_ns,
185
+ "trace_id" => trace_id,
186
+ "span_id" => span_id,
187
+ "span_kind" => "SERVER",
188
+ "path" => path,
189
+ "method" => method,
190
+ "client_ip" => entry.request[:remote_ip]
191
+ }
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LevoRailsmiddleware
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'levo_rails_middleware/version'
4
+ require 'levo_rails_middleware/configuration'
5
+ require 'levo_rails_middleware/middleware'
6
+ require 'levo_rails_middleware/entry'
7
+ require 'levo_rails_middleware/sender'
8
+
9
+ module LevoRailsmiddleware
10
+ class << self
11
+ attr_writer :configuration
12
+
13
+ # Access the configuration
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ # Configure the middleware
19
+ def configure
20
+ yield(configuration) if block_given?
21
+ end
22
+
23
+ # Add middleware to Rails application
24
+ def instrument(app_config)
25
+ # Use the class constant instead of a string
26
+ app_config.middleware.use LevoRailsmiddleware::Middleware # Changed from insert_before with string
27
+ end
28
+
29
+ # Log exceptions
30
+ def log_exception(context, exception)
31
+ Rails.logger.error "LEVO_middleware: Exception while #{context}: #{exception.message} (#{exception.class.name})"
32
+ exception.backtrace&.each do |line|
33
+ Rails.logger.error "LEVO_middleware: #{line}"
34
+ end
35
+ end
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: levo_rails_middleware
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Levo.ai Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: A Rails middleware for Levo.ai customers that captures HTTP requests
70
+ and responses and sends them to Levo.ai for API security analysis
71
+ email:
72
+ - support@levo.ai
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - LICENSE
78
+ - README.md
79
+ - lib/levo_rails_middleware.rb
80
+ - lib/levo_rails_middleware/configuration.rb
81
+ - lib/levo_rails_middleware/entry.rb
82
+ - lib/levo_rails_middleware/middleware.rb
83
+ - lib/levo_rails_middleware/sender.rb
84
+ - lib/levo_rails_middleware/version.rb
85
+ homepage: https://github.com/levoai/levo-rails-middleware
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.4.19
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: API traffic middlewareing middleware for Rails applications
108
+ test_files: []