RAPID INCIDENT TRIAGE & ACTION PLAN•Security
01. Error Signature
Terraform Git SSH Module Download Fixer
02. Root Cause Vector

Terraform fails to download Git-sourced modules via SSH due to missing or misconfigured SSH keys, known_hosts entries, or Git credential ...

03. Rapid Prescription

Refresh remote state lock, ensure provider version constraints, and audit state drift.

Zero-Trust Client IDE/ Security

Terraform Git SSH Module Download Fixer

Terraform fails to download Git-sourced modules via SSH due to missing or misconfigured SSH keys, known_hosts entries, or Git credential helpers blocking the handshake.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(43L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# main.tf - Legacy PCI Zone Module Integration
# TODO: This is a temporary workaround until the new GitLab migration completes.
# WARNING: SSH keys are rotated quarterly; update the module references accordingly.

provider "aws" {
  region = "us-east-1"
  # VULNERABILITY: Hardcoded credentials in a multi-account setup can cause module fetch failures.
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

# Legacy module fetching via internal Git over SSH (deprecated)
module "legacy_pci_api" {
  source = "git::ssh://git@my-registry.internal/legacy/pci-secure-zone.git?ref=v2.1.3"
  # TODO: fix this workaround after the Terraform 0.14 upgrade
  # old_source = "git::https://gitlab.example.com/old-repo.git"
  
  # Synthetic secrets injected as variables (simulating leaked config)
  stripe_api_token = "sk_live_4eC39HqLyjWDarjtT1zdp7dc"
  db_uri          = "postgresql://admin:SuperSecret123!@pci-db.internal:5432/legacy_db"
  
  # Multi-line dummy RSA key for SSH auth (embedded in variable)
  ssh_private_key = <<-EOT
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArX8bBQ7HjNl4uKz6K8q7vJ7mGtHfKwYx9W8LpPq2ZvRtT
EXAMPLEKEYMOREKEYHERE1234567890abcdefghijklmnopqrstuvwxyzABCDEF
-----END RSA PRIVATE KEY-----
EOT
}

# Redundant configuration from the old DevOps team (ignore)
# module "backup_pci" {
#   source = "git::ssh://git@my-registry.internal/legacy/pci-secure-zone.git?ref=v1.5.2"
# }

# VULNERABILITY: The module source uses SSH, but the Terraform init environment lacks SSH agent forwarding or proper key configuration, causing 'failed to download module'.
# This is implicit due to missing ssh-agent setup in CI/CD, conflicting with the embedded key above.

# Additional messy legacy variable (unused)
variable "old_api_endpoint" {
  default = "https://legacy-api.internal:8443/v1"
  description = "Deprecated endpoint; do not use in new deployments."
}
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Terraform 'Error: failed to download module' via Git SSH (Complete Debugging Guide)

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10–20 mins

TL;DR

  • What broke: Terraform's go-getter library cannot complete an SSH handshake to your Git remote when downloading a source module — caused by a missing SSH key, unverified host fingerprint, wrong URL scheme, or a blocking Git credential helper.
  • How to fix it: Verify your SSH key is loaded in the agent, the remote host is in ~/.ssh/known_hosts, and your module source uses the correct git::ssh:// or git@ syntax with a deploy key that has repo read access.
  • Shortcut: Use the Client-Side Sandbox above to paste your failing module {} block and SSH config — it auto-refactors the source URL and generates the corrected git config and ssh-add commands without sending your keys anywhere.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What does the error mean?)

Raw error output from terraform init:

Initializing modules...
╷
│ Error: Failed to download module
│
│   on main.tf line 12, in module "vpc":
│   12:   source = "git@github.com:corp/private-infra-modules.git//vpc?ref=v2.1.0"
│
│ Could not download module "vpc" (main.tf:12) source code from
│ "git::ssh://git@github.com/corp/private-infra-modules.git//vpc?ref=v2.1.0":
│ error downloading 'ssh://git@github.com/corp/private-infra-modules.git//vpc?ref=v2.1.0':
│ /usr/bin/git exited with 128: Host key verification failed.
│ fatal: Could not read from remote repository.
│
│ Please make sure you have the correct access rights
│ and the repository exists.
╵

Immediate consequence: terraform init exits non-zero. No modules are installed. Every subsequent terraform plan or terraform apply in this workspace — including your CI/CD pipeline — is completely blocked. If this is a shared module registry pattern, every downstream consumer of this root module is dead.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not just a developer annoyance. The failure surface is wider than it looks:

1. CI/CD Pipeline Full Stop Any pipeline runner (GitHub Actions, GitLab CI, Jenkins agent, Atlantis) that doesn't have the SSH key injected as an environment secret or mounted volume will fail at terraform init. If your pipeline auto-approves on merge, this breaks your entire infrastructure deployment chain.

2. Hardcoded git@ vs git::ssh:// Scheme Confusion Terraform's go-getter internally rewrites [email protected]:org/repo.git to git::ssh://[email protected]/org/repo.git. If your ~/.gitconfig has a insteadOf rewrite rule (common in corporate environments that force HTTPS), go-getter and Git fight over the URL scheme — go-getter wins the rewrite but Git then fails authentication because the HTTPS credential helper has no SSH fallback.

3. Host key verification failed = TOFU Bypass Risk The Host key verification failed error means github.com (or your GitLab/Bitbucket host) is not in ~/.ssh/known_hosts on the runner. The naive "fix" engineers reach for is StrictHostKeyChecking no in ~/.ssh/config. This is a critical security misconfiguration. It opens your pipeline runner to SSH MITM attacks — an adversary on the network path can intercept the Git clone, inject malicious Terraform module code, and your terraform apply will execute it against production infrastructure. Never do this.

4. Deploy Key Scope Creep Using a personal SSH key (your ~/.ssh/id_rsa) instead of a scoped deploy key means rotating or revoking your personal key breaks all pipelines. It also grants the pipeline identity access to every repo your personal account can reach — a significant blast radius if the runner is compromised.


How to Fix It (The Solution)

Diagnosis Checklist — Run These First

# 1. Is the SSH agent running and is your key loaded?
ssh-add -l

# 2. Can you reach the Git host at all?
ssh -T git@github.com
# Expected: "Hi username! You've successfully authenticated..."

# 3. Is the host in known_hosts?
ssh-keygen -F github.com

# 4. Check for conflicting gitconfig rewrites
git config --global --list | grep insteadOf

Fix 1: Host Key Not in known_hosts (Most Common)

The error: Host key verification failed

# Securely add the host fingerprint — verify it matches GitHub's published fingerprint
# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
ssh-keyscan -H github.com >> ~/.ssh/known_hosts

# Verify the fingerprint BEFORE trusting it
ssh-keygen -lf ~/.ssh/known_hosts | grep github.com
# Must match: SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s (RSA)

For CI runners (GitHub Actions):

- # No known_hosts setup — runner fails on first SSH contact
+ - name: Add GitHub to known_hosts
+   run: |
+     mkdir -p ~/.ssh
+     ssh-keyscan -H github.com >> ~/.ssh/known_hosts
+     chmod 600 ~/.ssh/known_hosts

Fix 2: SSH Key Not Loaded in Agent

- # Key exists on disk but ssh-add was never called
- # ssh-add -l returns: "The agent has no identities."

+ eval "$(ssh-agent -s)"
+ ssh-add ~/.ssh/id_ed25519_terraform_deploy
+ # Verify
+ ssh-add -l

Fix 3: Incorrect Module Source URL Scheme

Terraform go-getter requires a specific syntax. The [email protected]:org/repo.git SCP-style shorthand works in native Git but can break go-getter's URL parser in edge cases.

  module "vpc" {
-   source = "git@github.com:corp/private-infra-modules.git//vpc?ref=v2.1.0"
+   source = "git::ssh://git@github.com/corp/private-infra-modules.git//vpc?ref=v2.1.0"
    version = "~> 2.1"
  }

Note: The double-slash // is the go-getter subdirectory separator. It is not a typo.


Fix 4: Corporate gitconfig `insteadOf` Rewrite Conflict

If your org forces HTTPS for all Git operations:

# This is the killer — it rewrites ssh:// to https:// and breaks go-getter
git config --global url."https://".insteadOf git://
# Option A: Remove the global rewrite (if you control the machine)
- [url "https://"]
-     insteadOf = git://

# Option B: Add a scoped override just for your Git host
+ [url "git::ssh://git@github.com/"]
+     insteadOf = git@github.com:

Or set this per-repo in your pipeline environment:

- # No git config override — corporate insteadOf breaks go-getter
+ - name: Override git insteadOf for Terraform SSH modules
+   run: git config --global url."git::ssh://git@github.com/".insteadOf "git@github.com:"

Enterprise Best Practice: Scoped Deploy Key + SSH Config File

Never use a personal SSH key in CI. Use a read-only deploy key scoped to the specific repository.

Step 1: Generate a dedicated deploy key

ssh-keygen -t ed25519 -C "terraform-ci-deploy-key" -f ~/.ssh/terraform_deploy_ed25519 -N ""
# Add the PUBLIC key to GitHub repo → Settings → Deploy Keys → Read Only
cat ~/.ssh/terraform_deploy_ed25519.pub

Step 2: Configure SSH to use it only for this host alias

# ~/.ssh/config
+ Host github-terraform
+   HostName github.com
+   User git
+   IdentityFile ~/.ssh/terraform_deploy_ed25519
+   IdentitiesOnly yes
+   StrictHostKeyChecking yes

Step 3: Update the module source to use the alias

  module "vpc" {
-   source = "git::ssh://git@github.com/corp/private-infra-modules.git//vpc?ref=v2.1.0"
+   source = "git::ssh://git@github-terraform/corp/private-infra-modules.git//vpc?ref=v2.1.0"
  }

Step 4: Inject the private key in CI as a secret (GitHub Actions)

+ - name: Configure SSH deploy key for Terraform modules
+   env:
+     SSH_PRIVATE_KEY: ${{ secrets.TERRAFORM_MODULE_DEPLOY_KEY }}
+   run: |
+     mkdir -p ~/.ssh
+     echo "$SSH_PRIVATE_KEY" > ~/.ssh/terraform_deploy_ed25519
+     chmod 600 ~/.ssh/terraform_deploy_ed25519
+     ssh-keyscan -H github.com >> ~/.ssh/known_hosts
+     chmod 600 ~/.ssh/known_hosts

💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

1. Checkov — Detect Hardcoded SSH Key Paths

pip install checkov
checkov -d . --check CKV_GIT_1

Add a custom Checkov policy to flag non-deploy-key SSH sources:

# checkov/custom/terraform_ssh_source_check.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck

class TerraformModuleSSHSourceCheck(BaseResourceCheck):
    def __init__(self):
        name = "Ensure Terraform module SSH sources use deploy key host aliases"
        id = "CKV_CUSTOM_TF_SSH_001"
        ...

2. Pre-commit Hook — Validate SSH Connectivity Before Push

# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: terraform-ssh-check
      name: Validate Terraform SSH module connectivity
      entry: bash -c 'ssh -T git@github.com 2>&1 | grep -q "successfully authenticated" || (echo "SSH auth to GitHub failed. Run: ssh-add ~/.ssh/terraform_deploy_ed25519" && exit 1)'
      language: system
      pass_filenames: false

3. Terraform Cloud / Atlantis — Mount SSH Keys as Workspace Variables

In Terraform Cloud, use the SSH_KEY environment variable (Settings → SSH Keys) to attach a deploy key to the workspace. This is the only supported method — agent forwarding is not available in TFC runners.

In Atlantis, mount the key via Kubernetes Secret:

- # No SSH key mount — Atlantis pod fails terraform init
+ volumeMounts:
+   - name: terraform-ssh-key
+     mountPath: /home/atlantis/.ssh
+     readOnly: true
+ volumes:
+   - name: terraform-ssh-key
+     secret:
+       secretName: terraform-deploy-key
+       defaultMode: 0600

4. OPA/Conftest Policy — Enforce Pinned Refs on SSH Module Sources

Unpinned SSH module sources (?ref=main) are a supply chain risk — a force-push to main changes what your terraform init downloads.

# policy/terraform_module_ref.rego
package terraform.modules

deny[msg] {
  mod := input.module[_]
  source := mod.source
  contains(source, "git::ssh://")
  not contains(source, "?ref=")
  msg := sprintf("Module source '%v' must pin a ref tag. Use ?ref=vX.Y.Z", [source])
}
conftest test main.tf --policy policy/

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Why does Terraform say 'Host key verification failed' even though I can clone the repo manually with git clone?
Your interactive shell has the host fingerprint in ~/.ssh/known_hosts for your user account, but the process running terraform init — especially in CI — runs as a different user (e.g., root, a service account, or a containerized runner) whose home directory has an empty or missing known_hosts file. The fix is to explicitly run ssh-keyscan -H github.com >> ~/.ssh/known_hosts in your pipeline setup step before terraform init, and verify the fingerprint against GitHub's published values.
Q2.Is it safe to set StrictHostKeyChecking no in ~/.ssh/config to fix the Terraform SSH module error?
No. Setting StrictHostKeyChecking no disables SSH host key verification entirely, exposing your pipeline to SSH man-in-the-middle attacks. An adversary on the network path between your runner and the Git host can intercept the connection, serve a malicious repository, and your terraform apply will execute adversary-controlled infrastructure code. Always use ssh-keyscan to pre-populate known_hosts with a verified fingerprint instead. However, manually managing this across diverse environments is error-prone and introduces security vulnerabilities. Drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix.
Q3.What is the difference between [email protected]:org/repo.git and git::ssh://[email protected]/org/repo.git in a Terraform module source?
The git@ SCP-style shorthand is native Git syntax and works for direct git clone commands. Terraform uses the go-getter library to download modules, which requires an explicit URL scheme. go-getter internally rewrites git@ to git::ssh://, but this rewrite can conflict with corporate gitconfig insteadOf rules. Using the explicit git::ssh://[email protected]/org/repo.git format in your Terraform source block bypasses the rewrite ambiguity and gives go-getter an unambiguous protocol to work with. Scaling this fix across large mono-repos or at enterprise scale becomes a compliance nightmare. Deploy the StackEngine CI/CD Automator (Team Edition) for on-prem, automated pull request generation with Maker-Checker review.