Api Key Exposure in Sinatra
How Api Key Exposure Manifests in Sinatra
Api Key Exposure in Sinatra applications typically occurs through several Sinatra-specific patterns that developers might not recognize as security vulnerabilities. The most common manifestation is hardcoded API keys in route handlers, often seen in small Sinatra apps where configuration management is overlooked:
require 'sinatra'
require 'stripe'
# Hardcoded API key - critical vulnerability
Stripe.api_key = 'sk_test_1234567890'
get '/charge' do
# Key is exposed to anyone who can read the source
Stripe::Charge.create(amount: 1000, currency: 'usd')
end
Another Sinatra-specific pattern involves using environment variables without proper validation. Developers often assume environment variables are secure, but in containerized deployments or shared hosting environments, these can be exposed:
get '/api/data' do
api_key = ENV['API_KEY'] # No validation or error handling
headers 'X-API-Key' => api_key # Key sent in response headers
# ...
end
Configuration files in Sinatra apps frequently contain exposed credentials. Unlike Rails with its structured configuration, Sinatra developers often create ad-hoc configuration patterns:
configure do
set :api_credentials, {
google: 'AIza-...',
aws: 'AKIA1234...',
github: 'ghp_abc123...'
}
end
get '/config' do
settings.api_credentials.to_json # Exposes all keys
end
Logging middleware in Sinatra can inadvertently log sensitive headers or request parameters containing API keys:
use Rack::CommonLogger
get '/search' do
# API key in query parameter gets logged
api_key = params[:api_key]
query = params[:q]
# CommonLogger logs: GET /search?api_key=...&q=...
end
Middleware configuration is another Sinatra-specific vulnerability vector. Developers often configure middleware with sensitive data directly:
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', ENV['ADMIN_PASS']]
end
# Admin password exposed through basic auth
Sinatra-Specific Detection
Static Analysis Patterns
# Search for hardcoded credentials
grep -r "sk_\|AKIA\|ghp_\|AIza-" app.rb config.ru
# Find environment variable usage without validation
grep -n "ENV\['" app.rb
# Check for exposed configuration endpoints
grep -n "settings\." app.rb
Runtime Detection with middleBrick
middleBrick's Sinatra-specific detection identifies API key exposure through several mechanisms:
1. Configuration File Analysis
middleBrick scans Sinatra configuration files for hardcoded credentials and insecure patterns:
{
"findings": [
{
"category": "Data Exposure",
"severity": "critical",
"title": "Hardcoded Stripe API Key",
"location": "app.rb:3",
"description": "API key found in source code: sk_test_1234567890",
"remediation": "Move to environment variables with validation"
}
]
}
2. Runtime Header Analysis
middleBrick's black-box scanning detects API keys in response headers:
# middleBrick CLI scan
middlebrick scan https://api.example.com --format=json
# Output showing header exposure
{
"headers": {
"X-API-Key": "sk_test_1234567890",
"Authorization": "Bearer invalid"
},
"risk_score": 25
}
3. Middleware Inspection
middleBrick analyzes Sinatra middleware chains for insecure configurations:
# Check for vulnerable middleware
require 'sinatra/base'
class App < Sinatra::Base
use Rack::Auth::Basic do |u, p|
# middleBrick flags weak auth
u == 'admin' && p == 'password'
end
end
4. OpenAPI Spec Analysis
When Sinatra apps expose OpenAPI specs, middleBrick cross-references them with runtime findings:
openapi: 3.0.0
paths:
/api/data:
get:
security:
- ApiKeyAuth: []
# middleBrick checks if this matches actual implementation
Sinatra-Specific Remediation
Securing API keys in Sinatra applications requires Sinatra-specific approaches that leverage the framework's capabilities while following security best practices:
1. Environment Variable Management with Validation
require 'sinatra'
require 'stripe'
# Sinatra configure block with validation
configure do
set :stripe_api_key, ENV.fetch('STRIPE_API_KEY') do
raise "STRIPE_API_KEY environment variable missing"
end
set :aws_access_key, ENV['AWS_ACCESS_KEY_ID']
set :aws_secret_key, ENV['AWS_SECRET_ACCESS_KEY']
end
# Validate required keys at startup
before do
required_keys = [:stripe_api_key, :aws_access_key, :aws_secret_key]
missing = required_keys.select { |k| settings.send(k).nil? }
if missing.any?
halt 500, "Missing required API keys: #{missing.join(', ')}"
end
end
get '/charge' do
Stripe.api_key = settings.stripe_api_key
Stripe::Charge.create(amount: 1000, currency: 'usd')
"Charge created"
end
2. Secure Configuration Module
# config/api_keys.rb
module APIKeys
class Config
def self.load!
@config ||= {
stripe: ENV.fetch('STRIPE_API_KEY') { raise 'Missing STRIPE_API_KEY' },
aws: {
access_key: ENV['AWS_ACCESS_KEY_ID'],
secret_key: ENV['AWS_SECRET_ACCESS_KEY']
},
github: ENV['GITHUB_TOKEN']
}
end
def self.get(service)
load! unless @config
@config[service.to_sym]
end
end
end
# Usage in Sinatra app
require_relative 'config/api_keys'
get '/secure' do
stripe_key = APIKeys::Config.get(:stripe)
# stripe_key is validated and available
end
3. Middleware for Key Protection
# lib/middleware/api_key_protection.rb
class APIKeyProtection
def initialize(app)
@app = app
end
def call(env)
# Remove sensitive headers from response
status, headers, body = @app.call(env)
# Remove API keys from headers
headers.delete('X-API-Key') if headers['X-API-Key']
headers.delete('Authorization') if headers['Authorization']
[status, headers, body]
end
end
# Use in Sinatra app
require_relative 'lib/middleware/api_key_protection'
use APIKeyProtection
get '/protected' do
# API keys not exposed in response
end
4. Secure Logging Configuration
# Disable sensitive parameter logging
configure do
disable :logging # Use custom logger
end
# Custom logger that filters sensitive data
class SecureLogger
SENSITIVE_PATTERNS = [
/api_key=([^&]+)/,
/key=([^&]+)/,
/password=([^&]+)/
]
def self.log(message)
filtered = SENSITIVE_PATTERNS.reduce(message) do |msg, pattern|
msg.gsub(pattern, '[FILTERED]')
end
puts "[#{Time.now}] #{filtered}"
end
end
# Use in routes
get '/search' do
query = params[:q]
SecureLogger.log "Search: #{query}"
# API keys not logged
end
5. Runtime Security Checks
# lib/security_checks.rb
module SecurityChecks
def self.check_api_keys!
# Check for common API key patterns in memory
ObjectSpace.each_object(String) do |str|
if str.match?(/sk_test_|AKIA[0-9A-Z]{16}|ghp_[a-zA-Z0-9_]{36}/)
warn "Potential API key exposure detected: #{str[0..10]}..."
end
end
end
end
# Run checks before each request
before do
SecurityChecks.check_api_keys!
end