Command Injection in Fastapi with Api Keys
Command Injection in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability
Command Injection occurs when an attacker can control input that is passed to a shell or system-level command executed by the application. In FastAPI, this risk can be amplified when API keys are used to gate functionality but are not rigorously validated before being included in command construction. If an API key influences a subprocess call—such as being interpolated into a filename, a command argument, or a system path—an attacker who can indirectly affect the key’s usage may inject shell metacharacters to execute arbitrary commands.
Consider a scenario where an API key is read from headers and concatenated into a system command without sanitization:
import subprocess
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
@app.get("/run/{operation}")
async def run_operation(operation: str, x_api_key: str = Header(...)):
# Dangerous: directly embedding API key and user input into a shell command
cmd = f"echo API key {x_api_key} and executing {operation}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return {"output": result.stdout}
In this pattern, the API key and the operation parameter are embedded into a shell command via string interpolation. If an attacker can influence either value—perhaps by leveraging a leaked key or an insecure upstream service—they can escape the intended argument boundaries using shell metacharacters such as ;, &&, or backticks to achieve Command Injection. Even though API keys are often treated as secrets, their misuse in shell contexts can expose execution paths that lead to unauthorized command execution, data exfiltration, or system compromise.
The vulnerability is not inherent to API keys themselves, but to how they are handled when combined with dynamic command construction. For example, if logs or error messages inadvertently expose the key, or if a key is accidentally shared across services with differing trust boundaries, the attack surface grows. Furthermore, if the FastAPI application spawns subprocesses to perform administrative tasks—such as invoking external tools for data processing or integration—and incorporates API keys or derived values into those invocations without strict input validation, the risk of Command Injection increases. This pattern is especially dangerous when the subprocess runs with elevated privileges or when shell expansion is enabled (shell=True).
An attacker might probe for such weaknesses by sending crafted headers with metacharacters to observe abnormal behavior, error messages, or side effects. Because the API key is expected to be opaque, developers may overlook the need to treat it as untrusted input when it participates in command assembly. Security checks that include subprocess command construction and header-derived values—such as those performed by middleBrick—can surface these issues by correlating runtime behavior with the API specification and flagging dangerous patterns like shell injection points and unsafe consumption of headers.
Api Keys-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on preventing untrusted data from reaching shell commands, validating and restricting inputs, and avoiding shell interpretation entirely. The safest approach is to avoid shell=True and pass commands as argument lists, which disables shell metacharacter processing. Additionally, treat API keys as opaque secrets and never incorporate them into commands or logs.
Below is a secure rewrite of the earlier example using argument lists and strict validation:
import subprocess
from fastapi import FastAPI, Header, HTTPException, Depends
from pydantic import BaseModel
import shlex
app = FastAPI()
# Define allowed operations explicitly
ALLOWED_OPERATIONS = {"status", "health", "version"}
class CommandRequest(BaseModel):
operation: str
def validate_operation(operation: str):
if operation not in ALLOWED_OPERATIONS:
raise HTTPException(status_code=400, detail="Operation not allowed")
@app.post("/run")
async def run_operation(body: CommandRequest, x_api_key: str = Header(...)):
# Validate operation strictly
validate_operation(body.operation)
# Do NOT use the API key in the command; treat it as a secret for auth only
# Use a list to avoid shell injection
cmd = ["echo", "executing", body.operation]
result = subprocess.run(cmd, capture_output=True, text=True)
return {"output": result.stdout}
Key practices for API key handling in FastAPI:
- Never concatenate API keys or user-controlled values into shell commands.
- Use subprocess with a list of arguments and
shell=False(the default) to prevent metacharacter interpretation. - Validate all user-supplied inputs against an allowlist, especially when they map to system operations.
- Keep API keys out of logs, error messages, and process arguments to avoid accidental exposure.
- Leverage FastAPI dependencies for key validation and scope enforcement, and integrate with secret management solutions rather than passing keys to external processes.
Tools like middleBrick can support secure development by scanning for risky patterns such as shell injection points and unsafe consumption of headers, providing prioritized findings and remediation guidance aligned with frameworks like OWASP API Top 10.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |