HIGH container escaperailsmutual tls

Container Escape in Rails with Mutual Tls

Container Escape in Rails with Mutual Tls — how this specific combination creates or exposes the vulnerability

A container escape in a Rails application that uses mutual TLS (mTLS) typically occurs when runtime protections are misaligned with the strict transport security that mTLS enforces. mTLS ensures both client and server present valid certificates, which reduces the risk of on-path attacks but does not inherently limit what a compromised request can do inside the container. If the Rails process runs with elevated privileges or has access to the host filesystem, an attacker who tricks the application into forwarding or relaying traffic can exploit Rails components or system calls to break out of the container boundary.

For example, an attacker might leverage an unrestricted Net::HTTP call or an unvalidated redirect to reach the container’s host metadata service (e.g., 169.254.169.254), which is often accessible from within containers. Even with mTLS verifying inbound connections, Rails code that initiates outbound requests without network segmentation can become a channel for escape. Similarly, if the container’s volume mounts expose sensitive host paths, Rails code that reads or writes those paths may inadvertently provide an avenue to reach host binaries or configuration files. Without network policies that restrict egress and runtime controls that limit filesystem exposure, mTLS secures the API channel but does not prevent an attacker from leveraging Rails internals or system utilities to move laterally or escalate within the host environment.

In this context, middleBrick’s scans are valuable because they test the unauthenticated attack surface and include checks such as SSRF and Unsafe Consumption. An OpenAPI/Swagger spec analyzed by middleBrick can reveal outbound HTTP client definitions or permissive proxy settings that, when combined with runtime behaviors, may indicate a path toward container escape. By cross-referencing spec definitions with runtime findings, middleBrick can highlight endpoints that perform external requests or interact with cloud metadata, helping you identify risky integrations before they are exercised in production.

Consider a scenario where a Rails controller uses mTLS for inbound requests but then calls an external service without constraining the target URL. If an attacker can control a parameter that influences the request target, they may direct the Rails app to the container host or metadata service. Even with client certificates enforced on incoming connections, the outbound call may lack certificate pinning or network segmentation, allowing the container to reach internal endpoints that should remain isolated. This illustrates how mTLS on ingress does not automatically protect egress, and how Rails code must explicitly limit destinations and validate responses to reduce the container escape risk.

Mutual Tls-Specific Remediation in Rails — concrete code fixes

To harden a Rails app using mutual TLS, focus on strict certificate validation, controlled egress, and least-privilege runtime configurations. Below are concrete code examples that demonstrate how to implement mTLS correctly and reduce the container escape surface.

1. Enforce client and server certificate validation in Rails

Configure Rails to require and verify client certificates for incoming requests. This example uses a Rack middleware approach to validate the client certificate against a trusted CA before the request reaches the controller layer.

# config/initializers/middleware.rb
class MtlsVerification
  def initialize(app)
    @app = app
  end

  def call(env)
    cert = env["SSL_CLIENT_CERT"]
    unless cert&.present?
      return [403, { "Content-Type" => "text/plain" }, ["Client certificate required"]]
    end

    store = OpenSSL::X509::Store.new
    store.add_file(Rails.root.join("certs", "ca.pem").to_s)
    store.verify_flags = OpenSSL::X509::V_FLAG_PARTIAL_CHAIN

    begin
      cert_store = OpenSSL::X509::Store.new
      cert_store.add_cert(cert)
      unless cert_store.verify(store).any?
        return [403, { "Content-Type" => "text/plain" }, ["Certificate verification failed"]]
      end
    rescue OpenSSL::X509::StoreError => e
      return [403, { "Content-Type" => "text/plain" }, ["Certificate error: #{e.message}"]]
    end

    @app.call(env)
  end
end

Rails.application.config.middleware.use MtlsVerification

2. Secure outbound HTTP calls with pinned certificates and restricted targets

When Rails makes outbound requests (for example to integrate with other services), enforce certificate pinning and avoid passing unchecked user input into the URL. This example uses Net::HTTP with explicit CA verification and a strict whitelist of allowed hosts.

# app/services/secure_client.rb
class SecureClient
  ALLOWED_HOSTS = ["api.trusted.example.com", "internal.service.local"]

  def self.get(path, client_cert_path, client_key_path)
    uri = URI.parse(path)
    raise "Host not allowed" unless ALLOWED_HOSTS.include?(uri.host)

    cert = OpenSSL::X509::Certificate.new(File.read(client_cert_path))
    key = OpenSSL::PKey::RSA.new(File.read(client_key_path))

    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.cert = cert
    http.key = key
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
    http.ca_file = Rails.root.join("certs", "ca.pem").to_s
    http.ssl_version = "TLSv1_2"

    request = Net::HTTP::Get.new(uri.request_uri)
    http.request(request)
  end
end

3. Limit filesystem and host access in the container

Even with mTLS enforced, ensure the Rails container does not mount sensitive host paths and does not run as root. Use read-only filesystems where possible and drop Linux capabilities. In your container definition, restrict egress to known service endpoints and block access to the host metadata service. These runtime controls complement mTLS by reducing what an attacker can reach if Rails is compromised.

4. Validate and sanitize inputs that influence external interactions

Never allow user-controlled data to directly form URLs or command arguments. Use strong parameter validation and avoid methods that concatenate raw input into system commands or HTTP calls. This prevents SSRF-style techniques that could be used to pivot from the Rails app to the container host.

# app/controllers/api_proxies_controller.rb
class ApiProxiesController < ApplicationController
  before_action :validate_target_url

  def show
    response = SecureClient.get(params[:url], "/path/to/client.pem", "/path/to/client.key")
    render plain: response.body
  end

  private

  def validate_target_url
    uri = URI.parse(params[:url])
    unless uri.host&.end_with?("trusted.example.com")
      render plain: "Invalid target", status: :bad_request
      throw(:abort)
    end
  rescue URI::InvalidURIError
    render plain: "Invalid URL", status: :bad_request
    throw(:abort)
  end
end

Frequently Asked Questions

Does mutual TLS alone prevent container escape in Rails?
No. Mutual TLS secures the API channel but does not restrict what a compromised Rails process can do inside the container. You still need egress controls, filesystem restrictions, and careful outbound HTTP practices to reduce escape risk.
How can middleBrick help detect risks related to container escape in Rails with mTLS?
middleBrick scans the unauthenticated attack surface and includes checks such as SSRF and Unsafe Consumption. By analyzing your OpenAPI/Swagger spec and cross-referencing definitions with runtime findings, it can highlight endpoints that perform external requests or interact with cloud metadata, helping you identify risky integrations.