From 5dd4a72a2d378bebc11f8fabd7f5714b9ffa54f2 Mon Sep 17 00:00:00 2001
From: GPT-5 mini
Date: Sun, 5 Oct 2025 13:07:00 +0000
Subject: [PATCH] Quote IdentityFile paths with spaces in ANSIBLE_SSH_ARGS
Fixes an issue where ANSIBLE_SSH_ARGS IdentityFile paths containing
spaces were not quoted, causing Ansible/OpenSSH to misinterpret them as
hostnames or options. Quote paths with spaces and continue to escape '%'
characters.
Fixes #9597.
Powered-by: GitHub Copilot in VS Code
Powered-by: GitHub Codespaces
---
.../provisioners/ansible/provisioner/host.rb | 13 ++++++++++++-
.../provisioners/ansible/provisioner_test.rb | 17 +++++++++++++++++
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb
index 9f9dc5d0d..6c989d527 100644
--- a/plugins/provisioners/ansible/provisioner/host.rb
+++ b/plugins/provisioners/ansible/provisioner/host.rb
@@ -302,7 +302,18 @@ module VagrantPlugins
# Multiple Private Keys
unless !config.inventory_path && @ssh_info[:private_key_path].size == 1
@ssh_info[:private_key_path].each do |key|
- ssh_options += ["-o", "IdentityFile=%s" % [key.gsub('%', '%%')]]
+ # Escape any '%' characters to avoid formatting issues when
+ # building the final command string for Ansible. If the path
+ # contains spaces, wrap it in double quotes so the OpenSSH
+ # client invoked by Ansible treats the whole path as a single
+ # argument (otherwise the path would be split on spaces and
+ # could be interpreted as a hostname or separate option).
+ escaped = key.gsub('%', '%%')
+ if escaped.include?(' ')
+ ssh_options += ["-o", "IdentityFile=\"%s\"" % [escaped]]
+ else
+ ssh_options += ["-o", "IdentityFile=%s" % [escaped]]
+ end
end
end
diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb
index 4c8b77f4c..c96d2639a 100644
--- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb
+++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb
@@ -856,6 +856,23 @@ VF
end
end
+ describe "with an identity file path containing spaces and a custom inventory_path" do
+ before do
+ ssh_info[:private_key_path] = ['/path/with a space/key']
+ # When inventory_path is provided, the provisioner will add IdentityFile
+ # entries to ANSIBLE_SSH_ARGS even if there's a single key. Use a value
+ # containing spaces to reproduce the problematic scenario.
+ config.inventory_path = '/some inventory/with spaces/inv'
+ end
+
+ it "wraps the IdentityFile path in double quotes inside ANSIBLE_SSH_ARGS" do
+ expect(Vagrant::Util::Subprocess).to receive(:execute).with('ansible-playbook', any_args) { |*args|
+ cmd_opts = args.last
+ expect(cmd_opts[:env]['ANSIBLE_SSH_ARGS']).to include("-o IdentityFile=\"/path/with a space/key\"")
+ }.and_return(default_execute_result)
+ end
+ end
+
describe "with an identity file containing `%`" do
before do
ssh_info[:private_key_path] = ['/foo%bar/key', '/bar%%buz/key']