Container Escape in Hanami with Mutual Tls
Container Escape in Hanami with Mutual Tls — how this specific combination creates or exposes the vulnerability
A container escape in Hanami combined with mutual TLS (mTLS) occurs when an attacker who has compromised a service identity can exploit the application runtime and the mTLS verification logic to move laterally or break out of the container boundaries. Hanami is a Ruby web framework that loads components at runtime; if it dynamically requires code or spawns subprocesses based on unchecked inputs, an attacker may leverage mTLS-authenticated requests to trigger code paths that load malicious files or execute shell commands.
mTLS ensures both client and server present valid certificates, but it does not inherently restrict what a verified client can request the server to do. In Hanami, if endpoints accept paths, commands, or gem names from authenticated mTLS clients without strict allowlisting, an attacker can supply crafted input that leads to arbitrary file reads or command execution. For example, an endpoint that loads a view template using user input concatenated with a directory derived from certificate fields could permit reading arbitrary files on the host, including sensitive mounted configuration or binary files that facilitate container breakout.
Additionally, Hanami’s dependency graph and runtime constant loading can be abused when mTLS-authenticated requests invoke actions that spawn processes. If the application uses system or backticks with inputs influenced by certificate attributes (e.g., common name used to derive a container ID or volume path), an attacker can inject shell metacharacters to execute arbitrary commands inside the container. Because mTLS provides strong identity, the server may trust the request and skip authorization checks, effectively allowing the attacker to leverage verified credentials to run code at the container host level.
Another scenario involves file mounts and volume mappings. If Hanami reads configuration from mounted paths and uses mTLS client details to select which configuration to load, an attacker who compromises a peer certificate can supply paths that traverse mount boundaries (e.g., /host/ paths on Docker for containerd runtime directories). This can expose host filesystems or binary executables that enable privilege escalation or lateral movement across containers sharing the same network but isolated by mTLS policies.
Even when mTLS is enforced at the ingress, Hanami must still validate and sanitize all inputs independently. Relying solely on transport-layer identity can create a false sense of security, because the framework’s runtime behavior remains susceptible to path traversal, command injection, and unsafe file operations when identity is conflated with authorization.
Mutual Tls-Specific Remediation in Hanami — concrete code fixes
To mitigate container escape risks when using mutual TLS in Hanami, treat mTLS identities as attributes that must be validated and constrained, not as automatic authorization. Apply strict input validation, avoid dynamic code loading, and decouple identity from file system or command operations.
1. Validate and sanitize all inputs, independent of mTLS
Never construct file paths or command arguments by interpolating values derived from the TLS certificate or request parameters. Use allowlists and canonicalization.
# config/initializers/hanami.rb
module MyApp
class Configuration < Hanami::Configuration
# Example: restrict allowed origins by certificate common name
ALLOWED_ISSUERS = %w[CN=api-client-a O=MyOrg CN=api-client-b O=MyOrg].freeze
def verify_peer_certificate(cert)
issuer = cert.issuer.to_s
unless ALLOWED_ISSUERS.include?(issuer)
raise SecurityError, "Untrusted issuer: #{issuer}"
end
end
end
end
2. Avoid using certificate-derived values in system commands
If you must use certificate attributes, treat them as untrusted data. Do not pass them directly to system, `, or %x(). Instead, map them to predefined, safe operations.
# app/actions/containers/deploy.rb
class Containers::Deploy < Hanami::Action
def handle_request(params)
container_name = params[:container_name]
# Strict allowlist mapping instead of using CN directly
allowed = { 'worker' => 'worker:latest', 'api' => 'api:stable' }
image = allowed[container_name] or raise ArgumentError, 'Invalid container'
# Safe: no user input in command
success = system('docker', 'run', '--rm', image)
{ success: success }
end
end
3. Isolate file operations from identity-based routing
When serving files or templates, resolve paths through a controlled router, not via concatenation with certificate fields. Use Pathname and absolute base directories to prevent directory traversal.
# app/actions/assets/show.rb
require 'pathname'
class Assets::Show < Hanami::Action
BASE = Pathname.new('/var/app/public').expand_path.freeze
def handle_request(params)
requested = Pathname.new(params[:filename]).cleanpath
# Prevent traversal outside BASE
full_path = BASE.join(requested).cleanpath
raise SecurityError unless full_path.to_s.start_with?(BASE.to_s)
self.body = File.read(full_path)
self.content_type = 'application/octet-stream'
end
end
4. Enforce authorization separately from mTLS verification
Use role or scope claims from the client certificate (if present) to drive an authorization layer, not filesystem or command decisions directly. Validate permissions against a policy store.
# app/policies/container_policy.rb
class ContainerPolicy
def initialize(certificate_subject)
@subject = certificate_subject
end
def can_deploy?(container_name)
return false unless @subject.include?('CN=operator')
%w[worker api].include?(container_name)
end
end
# In an action
class Containers::Update < Hanami::Action
def handle_request(params)
policy = ContainerPolicy.new(certificate_subject)
raise SecurityError unless policy.can_deploy?(params[:container])
# proceed with safe deployment logic
end
end
5. Harden runtime and file system interactions
Ensure Hanami does not follow symlinks to sensitive host paths and that uploaded or referenced files are stored in isolated directories with no execute bits. Combine mTLS with filesystem permissions that restrict the container user’s capabilities.
# config/initializers/hanami/file_store.rb
module MyApp
module FileStore
UPLOAD_ROOT = Pathname.new('/var/app/uploads').expand_path
UPLOAD_ROOT.mkpath unless UPLOAD_ROOT.exist?
def self.safe_path(original_name)
Digest::SHA256.hexdigest(original_name) + File.extname(original_name)
end
end
end