Api Key Exposure in Sinatra with Saml
Api Key Exposure in Sinatra with Saml — how this specific combination creates or exposes the vulnerability
When a Sinatra application uses SAML for authentication, developers often focus on asserting user identity and roles and may inadvertently expose application-level secrets such as API keys. Api key exposure occurs when keys are embedded in views, passed to the browser, logged in plain text, or made reachable through misconfigured SAML endpoints or session handling.
In a Sinatra app, routes might load an API key from environment variables and pass it directly to a helper or template. If the route is accessible while the SAML session is not properly validated on every request, unauthenticated or insufficiently authenticated access can reveal the key. For example, a route like /settings that renders a key for client-side JavaScript can be reached if SAML session checks are missing or bypassed via a crafted SAML response or an unauthenticated endpoint that shares session state insecurely.
SAML-specific risks arise during authentication flows. If the Service Provider (SP) configuration in Sinatra does not validate signatures or properly handle the SAML response state, an attacker might force a login redirect or inject a crafted response that results in the app exposing sensitive data to the browser or logs. Middleware or helper methods that store API keys in the session without encryption, or that serialize keys into SAML attributes or NameID mappings, can inadvertently surface those keys in assertions or logs.
Additional exposure pathways include verbose logging of SAML responses where API keys are mistakenly included, or insecure use of callbacks where the Sinatra app returns or echoes tokens. Since SAML often introduces multiple identity assertions and attribute mappings, keys stored alongside user attributes in the session or in transient variables can be copied into browser-accessible contexts, such as JSON endpoints rendered without access controls.
Saml-Specific Remediation in Sinatra — concrete code fixes
Secure Sinatra applications using SAML by isolating secrets from identity flows, enforcing strict session validation, and ensuring SAML responses never expose sensitive values. Below are concrete, working examples that demonstrate safe patterns.
1. Keep API keys out of views and SAML assertions
Never render API keys in templates or include them in SAML attributes. Store keys in environment variables and access them only in server-side code that is protected by proper authentication and authorization checks.
# config/initializers/secrets.rb
ENV['API_KEY'] = ENV.fetch('API_KEY') { raise 'API_KEY is missing' }
# helpers/auth.rb
helpers do
def current_user
env['warden'].user
end
def require_saml_session!
redirect to('/login') unless current_user&.saml_session_valid?
end
end
# routes/settings.rb
require_relative '../helpers/auth'
before do
require_saml_session!
end
get '/settings' do
# Server-side only; key is never exposed to the browser
{ api_key: ENV['API_KEY'] }.to_json
end
2. Validate SAML responses and control attribute mapping
Ensure your SP configuration validates signatures and does not map sensitive values into SAML attributes. Use the saml gems with strict settings and avoid passing API keys through NameID or attributes.
# app.rb
require 'sinatra'
require 'saml' # e.g., ruby-saml
require 'securerandom'
SAML_SETTINGS = {
idp_cert_fingerprint: ENV.fetch('IDP_CERT_FINGERPRINT'),
issuer: ENV.fetch('SP_ENTITY_ID'),
assertion_consumer_service_url: '/saml/callback',
want_assertions_signed: true,
want_name_id: false,
name_id_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
}
get '/saml/login' do
request_id = SecureRandom.uuid
redirect Saml::Idp::AuthnRequest.new(SAML_SETTINGS).request_url('https://idp.example.com/sso', request_id)
end
post '/saml/callback' do
response = Saml::Response.new(params[:SAMLResponse], SAML_SETTINGS.merge(keep_assertion: false))
halt 401 unless response.success?
# Map only identity attributes, never API keys
session[:user_email] = response.name_id
session[:roles] = response.attributes['roles'] || []
redirect to('/dashboard')
end
3. Avoid logging or echoing SAML data that may contain references to secrets
Configure logging to exclude SAML responses and ensure any debug endpoints do not echo tokens or keys. Treat SAML responses as opaque and validate before use.
# config/initializers/logging.rb
configure :production do
configure do |config|
config.log = lambda do |message|
# Scrub SAML responses from logs
ScrubSaml.call(message) unless message.to_s.include?('SAMLResponse')
end
end
end
# Middleware to prevent accidental exposure
class SamlScrubber
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
if request.params['SAMLResponse']
# Do not log or store raw responses
env['rack.request.form_hash'] = {}
end
@app.call(env)
end
end
use SamlScrubber
4. Enforce per-request authorization for sensitive endpoints
Even when authenticated via SAML, explicitly check permissions before exposing any sensitive data or operations. Do not rely solely on SAML session presence for authorization to access API keys or admin functions.
# routes/admin.rb
before '/admin/*' do
require_saml_session!
redirect to('/') unless current_user&.admin?
end
get '/admin/api_key' do
# Only accessible to admins; key remains server-side
{ rotate: true }.to_json
end