HIGH command injectionrailsapi keys

Command Injection in Rails with Api Keys

Command Injection in Rails with Api Keys — how this specific combination creates or exposes the vulnerability

Command Injection occurs when an application passes untrusted input directly to a system shell or to an OS command builder. In Ruby on Rails, using API keys in a way that feeds user-controlled data into command execution is a common root cause. For example, reading an API key from environment variables is safe, but concatenating that key into a shell command string with user input creates a path for injection.

Consider a Rails service object that calls an external API client. If the implementation builds a shell command using string interpolation and passes along an API key taken from ENV alongside untrusted parameters, an attacker can manipulate the command line. A vulnerable pattern might look like:

api_key = ENV['EXTERNAL_API_KEY']
system("curl -H 'Authorization: Bearer #{api_key}' https://api.example.com/v1/search?q=#{params[:query]}")

An attacker providing query as test; cat /etc/passwd can cause the shell to execute additional commands. Even if the API key itself is not secret in this context (e.g., it is treated as a bearer token), injection can alter the intended request, leak data, or run arbitrary commands with the privileges of the Rails process.

A second variant involves using backticks or %x with interpolated API keys and user input. For instance:

output = `#{ENV['EXTERNAL_API_KEY']} --query "#{params[:query]}"`

Here, the API key is used as part of the command executable or flag, and the query is interpolated without sanitization. This demonstrates how an API key does not need to be secret to contribute to a command injection flaw; its presence in a constructed shell command alongside untrusted input is enough to create the vulnerability.

Additionally, Rails developers may use libraries or wrappers that shell out for API interactions. If these libraries are invoked with interpolated strings that include API keys and uncontrolled data, the injection surface is effectively the same as raw system calls. The risk is not about exposing the key itself, but about allowing an attacker to change the command structure, potentially escalating impact by leveraging the permissions of the Rails runtime.

Api Keys-Specific Remediation in Rails — concrete code fixes

Secure handling of API keys in Rails requires avoiding shell construction entirely and using safe HTTP clients. The core remediation is to never interpolate API keys or any external data into shell commands. Instead, use language-native HTTP libraries or well-maintained gems that do not rely on shell execution.

Replace system or backtick calls with a Ruby HTTP client such as Net::HTTP or Faraday. This eliminates the shell as an intermediary and removes injection risk. For example, using Net::HTTP:

require 'net/http'
require 'uri'

api_key = ENV['EXTERNAL_API_KEY']
uri = URI('https://api.example.com/v1/search')
uri.query = URI.encode_www_form(q: params[:query])

request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{api_key}"

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end
puts response.body

With Faraday, the code is even cleaner and more configurable:

conn = Faraday.new(url: 'https://api.example.com') do |faraday|
  faraday.request  :url_encoded
  faraday.response :logger
  faraday.adapter  :net_http
end

api_key = ENV['EXTERNAL_API_KEY']
resp = conn.get do |req|
  req.url '/v1/search'
  req.params[:q] = params[:query]
  req.headers['Authorization'] = "Bearer #{api_key}"
end
puts resp.body

These approaches keep the API key in HTTP headers or parameters managed by the library, avoiding any shell interpolation. They also provide better error handling, timeouts, and SSL management compared with manual shell invocation.

If you must construct command-like invocations (for example, calling a local binary that requires an API key as an argument), use Ruby’s spawn with an argument array and pass the key as a separate element, bypassing shell interpretation:

api_key = ENV['EXTERNAL_API_KEY']
pid = spawn(['/usr/bin/curl', '--header', "Authorization: Bearer #{api_key}", 'https://api.example.com/v1/search'], out: '/tmp/curl.out')
Process.wait(pid)

Even here, prefer passing data via environment variables or configuration rather than embedding sensitive values in argument strings where possible. For continuous scanning in development and CI/CD, integrate middleBrick to detect command injection patterns; you can add API security checks to your CI/CD pipeline with the GitHub Action and fail builds if risky patterns are found.

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Is using ENV for API keys safe from command injection?
Using ENV for API keys is safe from injection only if the key is never concatenated into shell commands. Injection depends on how the key is used; interpolating it into shell strings remains dangerous.
How does middleBrick help detect command injection in Rails APIs?
middleBrick scans API endpoints and can identify risky patterns such as shell command construction with interpolated API keys. You can integrate middleBrick into CI/CD with the GitHub Action to catch issues before deployment.