HIGH xml external entitiesrailsmutual tls

Xml External Entities in Rails with Mutual Tls

Xml External Entities in Rails with Mutual Tls — how this specific combination creates or exposes the vulnerability

XML External Entity (XXE) is a class of injection flaw that occurs when an application processes XML input containing references to external entities. In Ruby on Rails, this typically surfaces when developers use libraries such as Nokogiri to parse XML without disabling external entity resolution. A Rails app that also enforces Mutual TLS (mTLS) introduces a distinct interaction: mTLS ensures that only clients presenting a valid client certificate can reach the endpoint, but it does not inherently limit what the server does with the XML body once the TLS handshake completes.

When mTLS is in place, the effective attack surface can shift rather than shrink. An authenticated client with a valid certificate can send crafted XML that defines external entities, potentially causing the parser to read files on the server, trigger SSRF to internal services, or consume excessive resources. Because mTLS narrows access to a smaller set of trusted clients, developers may mistakenly assume the XML they receive is safe, leading to weaker controls around XML parsing. In practice, this means XXE becomes an authorization-path issue: the vulnerability exists inside an already authenticated and encrypted channel, which can make detection less obvious in logs focused primarily on transport security.

The combination also interacts with Rails’ own behavior. For example, if a Rails controller accepts XML via request.env["CONTENT_TYPE"] and passes the raw body to a parser without explicit safeguards, the server may follow external DTD references even when hosted behind mTLS-terminating infrastructure. Common triggers include using Nokogiri::XML without disabling DTD loading, or configuring an XML-RPC endpoint that does not set Nokogiri::XML::ParseOptions::NOENT and related safe options. An attacker who can present a valid client certificate might probe internal networks via parameterized external entities, turning mTLS from a boundary into a tunnel rather than a filter.

Because mTLS is often deployed in regulated or high-assurance environments, the impact of an XXE flaw can be more severe: sensitive configuration files, metadata service credentials, or internal service endpoints may be exposed. This is why scanning with a tool like middleBrick is valuable: it checks the unauthenticated attack surface where possible and, when combined with authenticated testing strategies, can surface XML parsing behavior that remains risky even when mTLS is used. Remediation focuses on parser configuration and input handling rather than transport-layer controls alone.

Mutual Tls-Specific Remediation in Rails — concrete code fixes

Securing Rails against XXE in an mTLS environment requires both transport configuration and safe XML parsing. On the mTLS side, Rails must be configured to require and validate client certificates without assuming safety inside the TLS channel. Below are concrete, working examples showing how to set up mTLS and how to parse XML safely.

mTLS setup in Rails

In config/application.rb or an environment-specific file such as config/environments/production.rb, enforce client certificate verification at the Rails level when using a reverse proxy or application-level termination. If termination happens at the load balancer or ingress, ensure it sets trusted headers only after validation and that Rails still enforces strict certificate checks for sensitive endpoints.

# config/initializers/ssl.rb — example for application-level mTLS enforcement
require 'openssl'

ssl_certificate = File.read('/path/to/ca_bundle.pem')
ssl_verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT

Rails.application.configure do
  config.middleware.use ActionDispatch::SSL,
    hsts: true,
    redirect: false,
    ssl_certificate: ssl_certificate,
    ssl_verify_mode: ssl_verify_mode
end

When using Puma directly with mTLS, configure the SSL context to require client authentication:

# config/puma.rb
ssl_bind '0.0.0.0', '8443', {
  key: '/path/to/server.key',
  cert: '/path/to/server.crt',
  ca_file: '/path/to/ca_bundle.pem',
  verify_mode: 'verify_peer'
}

Safe XML parsing in Rails

Always disable external entity processing when parsing XML. With Nokogiri, use the NOENT and NONET options together and avoid passing raw external subsets.

# app/services/safe_xml_parser.rb
require 'nokogiri'

module SafeXmlParser
  def self.parse(xml_string)
    # Disables DTD load, external entities, and network access during parsing
    Nokogiri::XML(xml_string) do |config|
      config.options = Nokogiri::XML::ParseOptions::NOENT | Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::DTDLOAD
    end
  end
end

If your Rails app consumes XML-RPC, configure the underlying transport to reject external entities:

# app/services/xml_rpc_client.rb
require 'xmlrpc/client'

module XmlRpcClient
  def self.safe_proxy(uri)
    # Use a custom parser that disables dangerous features
    parser = XMLRPC::Config::DEFAULT_PARSER.dup
    parser.default_param = parser.default_param.map do |param|
      if param.is_a?(XMLRPC::XMLParser::ExpatParser::Param)
        # Ensure the parser enforces safe options
        XMLRPC::XMLParser::ExpatParser::Param.new(
          parser: parser,
          options: XMLRPC::XMLParser::ExpatParser::Options::NOENT
        )
      else
        param
      end
    end
    XMLRPC::Client.new2(uri, parser: parser)
  end
end

Combine these practices with runtime scanning via middleBrick to validate that your endpoints remain secure under both authenticated mTLS sessions and unexpected XML payloads. Continuous monitoring and CI/CD gates in the Pro plan can help catch regressions before they reach production.

Frequently Asked Questions

Does mTLS alone prevent XXE attacks in Rails APIs?
No. Mutual TLS secures the transport and ensures only authenticated clients can connect, but it does not change how the server parses XML. If the XML parser resolves external entities, an authenticated client can still trigger XXE. You must explicitly disable DTD and external entity processing in your XML parser.
How can I test my Rails API for XXE when mTLS is enforced?
Use a combination of authenticated test requests with malicious XML bodies and runtime scanning. Tools that support authenticated scans can validate that XXE is not exploitable over mTLS. For example, configure curl with a client certificate and send an XML payload containing external entity references, then verify that the server does not follow external DTDs or disclose unintended resources.