diff --git a/gix-transport/src/client/blocking_io/file.rs b/gix-transport/src/client/blocking_io/file.rs
index 5f5bf25b0db..06ce1cbfbd9 100644
--- a/gix-transport/src/client/blocking_io/file.rs
+++ b/gix-transport/src/client/blocking_io/file.rs
@@ -291,7 +291,7 @@ pub fn connect(
mod tests {
mod ssh {
mod connect {
- use crate::{client::blocking_io::ssh::connect, Protocol};
+ use crate::{client::blocking_io::ssh, Protocol};
#[test]
fn path() {
@@ -304,10 +304,29 @@ mod tests {
("user@host.xy:~/repo", "~/repo"),
] {
let url = gix_url::parse((*url).into()).expect("valid url");
- let cmd = connect(url, Protocol::V1, Default::default(), false).expect("parse success");
+ let cmd = ssh::connect(url, Protocol::V1, Default::default(), false).expect("parse success");
assert_eq!(cmd.path, expected, "the path will be substituted by the remote shell");
}
}
+
+ #[test]
+ fn ambiguous_host_disallowed() {
+ for url in [
+ "ssh://-oProxyCommand=open$IFS-aCalculator/foo",
+ "user@-oProxyCommand=open$IFS-aCalculator:username/repo",
+ ] {
+ let url = gix_url::parse((*url).into()).expect("valid url");
+ let options = ssh::connect::Options {
+ command: Some("unrecognized".into()),
+ disallow_shell: false,
+ kind: None,
+ };
+ assert!(matches!(
+ ssh::connect(url, Protocol::V1, options, false),
+ Err(ssh::Error::AmbiguousHostName { host }) if host == "-oProxyCommand=open$IFS-aCalculator",
+ ));
+ }
+ }
}
}
}
diff --git a/gix-transport/src/client/blocking_io/ssh/mod.rs b/gix-transport/src/client/blocking_io/ssh/mod.rs
index 16f47bd25f4..6820051a5ec 100644
--- a/gix-transport/src/client/blocking_io/ssh/mod.rs
+++ b/gix-transport/src/client/blocking_io/ssh/mod.rs
@@ -1,5 +1,7 @@
use std::process::Stdio;
+use gix_url::ArgumentSafety::*;
+
use crate::{client::blocking_io, Protocol};
/// The error used in [`connect()`].
@@ -42,6 +44,8 @@ pub mod invocation {
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
+ #[error("Username '{user}' could be mistaken for a command-line argument")]
+ AmbiguousUserName { user: String },
#[error("Host name '{host}' could be mistaken for a command-line argument")]
AmbiguousHostName { host: String },
#[error("The 'Simple' ssh variant doesn't support {function}")]
@@ -116,9 +120,11 @@ pub fn connect(
.stdin(Stdio::null())
.with_shell()
.arg("-G")
- .arg(url.host_argument_safe().ok_or_else(|| Error::AmbiguousHostName {
- host: url.host().expect("set in ssh urls").into(),
- })?),
+ .arg(match url.host_as_argument() {
+ Usable(host) => host,
+ Dangerous(host) => Err(Error::AmbiguousHostName { host: host.into() })?,
+ Absent => panic!("BUG: host should always be present in SSH URLs"),
+ }),
);
gix_features::trace::debug!(cmd = ?cmd, "invoking `ssh` for feature check");
kind = if cmd.status().ok().map_or(false, |status| status.success()) {
diff --git a/gix-transport/src/client/blocking_io/ssh/program_kind.rs b/gix-transport/src/client/blocking_io/ssh/program_kind.rs
index 70905829f64..d7162c09b33 100644
--- a/gix-transport/src/client/blocking_io/ssh/program_kind.rs
+++ b/gix-transport/src/client/blocking_io/ssh/program_kind.rs
@@ -2,6 +2,8 @@ use std::{ffi::OsStr, io::ErrorKind};
use bstr::{BString, ByteSlice, ByteVec};
+use gix_url::ArgumentSafety::*;
+
use crate::{
client::{ssh, ssh::ProgramKind},
Protocol,
@@ -60,23 +62,21 @@ impl ProgramKind {
}
}
};
- let host_as_ssh_arg = match url.user() {
- Some(user) => {
- let host = url.host().expect("present in ssh urls");
- format!("{user}@{host}")
- }
- None => {
- let host = url
- .host_argument_safe()
- .ok_or_else(|| ssh::invocation::Error::AmbiguousHostName {
- host: url.host().expect("ssh host always set").into(),
- })?;
- host.into()
- }
+
+ let host_maybe_with_user_as_ssh_arg = match (url.user_as_argument(), url.host_as_argument()) {
+ (Usable(user), Usable(host)) => format!("{user}@{host}"),
+ (Usable(user), Dangerous(host)) => format!("{user}@{host}"), // The `user@` makes it safe.
+ (Absent, Usable(host)) => host.into(),
+ (Dangerous(user), _) => Err(ssh::invocation::Error::AmbiguousUserName { user: user.into() })?,
+ (_, Dangerous(host)) => Err(ssh::invocation::Error::AmbiguousHostName { host: host.into() })?,
+ (_, Absent) => panic!("BUG: host should always be present in SSH URLs"),
};
- // Try to force ssh to yield english messages (for parsing later)
- Ok(prepare.arg(host_as_ssh_arg).env("LANG", "C").env("LC_ALL", "C"))
+ Ok(prepare
+ .arg(host_maybe_with_user_as_ssh_arg)
+ // Try to force ssh to yield English messages (for parsing later).
+ .env("LANG", "C")
+ .env("LC_ALL", "C"))
}
/// Note that the caller has to assure that the ssh program is launched in English by setting the locale.
diff --git a/gix-transport/src/client/blocking_io/ssh/tests.rs b/gix-transport/src/client/blocking_io/ssh/tests.rs
index 4e4da780703..8fa19d0bb77 100644
--- a/gix-transport/src/client/blocking_io/ssh/tests.rs
+++ b/gix-transport/src/client/blocking_io/ssh/tests.rs
@@ -144,8 +144,25 @@ mod program_kind {
assert!(call_args(kind, "ssh://user@host:43/p", Protocol::V2).ends_with("-P 43 user@host"));
}
}
+
#[test]
- fn ambiguous_host_is_allowed_with_user() {
+ fn ambiguous_user_is_disallowed_explicit_ssh() {
+ assert!(matches!(
+ try_call(ProgramKind::Ssh, "ssh://-arg@host/p", Protocol::V2),
+ Err(ssh::invocation::Error::AmbiguousUserName { user }) if user == "-arg"
+ ))
+ }
+
+ #[test]
+ fn ambiguous_user_is_disallowed_implicit_ssh() {
+ assert!(matches!(
+ try_call(ProgramKind::Ssh, "-arg@host:p/q", Protocol::V2),
+ Err(ssh::invocation::Error::AmbiguousUserName { user }) if user == "-arg"
+ ))
+ }
+
+ #[test]
+ fn ambiguous_host_is_allowed_with_user_explicit_ssh() {
assert_eq!(
call_args(ProgramKind::Ssh, "ssh://user@-arg/p", Protocol::V2),
joined(&["ssh", "-o", "SendEnv=GIT_PROTOCOL", "user@-arg"])
@@ -153,13 +170,37 @@ mod program_kind {
}
#[test]
- fn ambiguous_host_is_disallowed() {
+ fn ambiguous_host_is_allowed_with_user_implicit_ssh() {
+ assert_eq!(
+ call_args(ProgramKind::Ssh, "user@-arg:p/q", Protocol::V2),
+ joined(&["ssh", "-o", "SendEnv=GIT_PROTOCOL", "user@-arg"])
+ );
+ }
+
+ #[test]
+ fn ambiguous_host_is_disallowed_without_user() {
assert!(matches!(
try_call(ProgramKind::Ssh, "ssh://-arg/p", Protocol::V2),
Err(ssh::invocation::Error::AmbiguousHostName { host }) if host == "-arg"
));
}
+ #[test]
+ fn ambiguous_user_and_host_remain_disallowed_together_explicit_ssh() {
+ assert!(matches!(
+ try_call(ProgramKind::Ssh, "ssh://-arg@host/p", Protocol::V2),
+ Err(ssh::invocation::Error::AmbiguousUserName { user }) if user == "-arg"
+ ));
+ }
+
+ #[test]
+ fn ambiguous_user_and_host_remain_disallowed_together_implicit_ssh() {
+ assert!(matches!(
+ try_call(ProgramKind::Ssh, "-userarg@-hostarg:p/q", Protocol::V2),
+ Err(ssh::invocation::Error::AmbiguousUserName { user }) if user == "-userarg"
+ ));
+ }
+
#[test]
fn simple_cannot_handle_any_arguments() {
assert!(matches!(
diff --git a/gix-url/src/lib.rs b/gix-url/src/lib.rs
index a810ad66c7d..8468ec80eff 100644
--- a/gix-url/src/lib.rs
+++ b/gix-url/src/lib.rs
@@ -55,6 +55,27 @@ pub fn expand_path(user: Option<&expand_path::ForUser>, path: &BStr) -> Result
for details.
+///
+/// # Security Warning
+///
+/// This type only expresses known *syntactic* risk. It does not cover other risks, such as passing a personal access
+/// token as a username rather than a password in an application that logs usernames.
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+pub enum ArgumentSafety<'a> {
+ /// May be safe. There is nothing to pass, so there is nothing dangerous.
+ Absent,
+ /// May be safe. The argument does not begin with a `-` and so will not be confused as an option.
+ Usable(&'a str),
+ /// Dangerous! Begins with `-` and could be treated as an option. Use the value in error messages only.
+ Dangerous(&'a str),
+}
+
/// A URL with support for specialized git related capabilities.
///
/// Additionally there is support for [deserialization](Url::from_bytes()) and [serialization](Url::to_bstring()).
@@ -85,12 +106,12 @@ pub struct Url {
pub port: Option,
/// The path portion of the URL, usually the location of the git repository.
///
- /// # Security-Warning
+ /// # Security Warning
///
/// URLs allow paths to start with `-` which makes it possible to mask command-line arguments as path which then leads to
/// the invocation of programs from an attacker controlled URL. See for details.
///
- /// If this value is going to be used in a command-line application, call [Self::path_argument_safe()] instead.
+ /// If this value is ever going to be passed to a command-line application, call [Self::path_argument_safe()] instead.
pub path: BString,
}
@@ -164,48 +185,101 @@ impl Url {
/// Access
impl Url {
- /// Returns the user mentioned in the url, if present.
+ /// Return the username mentioned in the URL, if present.
+ ///
+ /// # Security Warning
+ ///
+ /// URLs allow usernames to start with `-` which makes it possible to mask command-line arguments as username which then leads to
+ /// the invocation of programs from an attacker controlled URL. See for details.
+ ///
+ /// If this value is ever going to be passed to a command-line application, call [Self::user_argument_safe()] instead.
pub fn user(&self) -> Option<&str> {
self.user.as_deref()
}
- /// Returns the password mentioned in the url, if present.
+
+ /// Classify the username of this URL by whether it is safe to pass as a command-line argument.
+ ///
+ /// Use this method instead of [Self::user()] if the host is going to be passed to a command-line application.
+ /// If the unsafe and absent cases need not be distinguished, [Self::user_argument_safe()] may also be used.
+ pub fn user_as_argument(&self) -> ArgumentSafety<'_> {
+ match self.user() {
+ Some(user) if looks_like_command_line_option(user.as_bytes()) => ArgumentSafety::Dangerous(user),
+ Some(user) => ArgumentSafety::Usable(user),
+ None => ArgumentSafety::Absent,
+ }
+ }
+
+ /// Return the username of this URL if present *and* if it can't be mistaken for a command-line argument.
+ ///
+ /// Use this method or [Self::user_as_argument()] instead of [Self::user()] if the host is going to be
+ /// passed to a command-line application. Prefer [Self::user_as_argument()] unless the unsafe and absent
+ /// cases need not be distinguished from each other.
+ pub fn user_argument_safe(&self) -> Option<&str> {
+ match self.user_as_argument() {
+ ArgumentSafety::Usable(user) => Some(user),
+ _ => None,
+ }
+ }
+
+ /// Return the password mentioned in the url, if present.
pub fn password(&self) -> Option<&str> {
self.password.as_deref()
}
- /// Returns the host mentioned in the url, if present.
+
+ /// Return the host mentioned in the URL, if present.
///
- /// # Security-Warning
+ /// # Security Warning
///
/// URLs allow hosts to start with `-` which makes it possible to mask command-line arguments as host which then leads to
/// the invocation of programs from an attacker controlled URL. See for details.
///
- /// If this value is going to be used in a command-line application, call [Self::host_argument_safe()] instead.
+ /// If this value is ever going to be passed to a command-line application, call [Self::host_as_argument()]
+ /// or [Self::host_argument_safe()] instead.
pub fn host(&self) -> Option<&str> {
self.host.as_deref()
}
+ /// Classify the host of this URL by whether it is safe to pass as a command-line argument.
+ ///
+ /// Use this method instead of [Self::host()] if the host is going to be passed to a command-line application.
+ /// If the unsafe and absent cases need not be distinguished, [Self::host_argument_safe()] may also be used.
+ pub fn host_as_argument(&self) -> ArgumentSafety<'_> {
+ match self.host() {
+ Some(host) if looks_like_command_line_option(host.as_bytes()) => ArgumentSafety::Dangerous(host),
+ Some(host) => ArgumentSafety::Usable(host),
+ None => ArgumentSafety::Absent,
+ }
+ }
+
/// Return the host of this URL if present *and* if it can't be mistaken for a command-line argument.
///
- /// Use this method if the host is going to be passed to a command-line application.
+ /// Use this method or [Self::host_as_argument()] instead of [Self::host()] if the host is going to be
+ /// passed to a command-line application. Prefer [Self::host_as_argument()] unless the unsafe and absent
+ /// cases need not be distinguished from each other.
pub fn host_argument_safe(&self) -> Option<&str> {
- self.host().filter(|host| !looks_like_argument(host.as_bytes()))
+ match self.host_as_argument() {
+ ArgumentSafety::Usable(host) => Some(host),
+ _ => None,
+ }
}
- /// Return the path of this URL *and* if it can't be mistaken for a command-line argument.
+ /// Return the path of this URL *if* it can't be mistaken for a command-line argument.
/// Note that it always begins with a slash, which is ignored for this comparison.
///
- /// Use this method if the path is going to be passed to a command-line application.
+ /// Use this method instead of accessing [Self::path] directly if the path is going to be passed to a
+ /// command-line application, unless it is certain that the leading `/` will always be included.
pub fn path_argument_safe(&self) -> Option<&BStr> {
self.path
.get(1..)
- .and_then(|truncated| (!looks_like_argument(truncated)).then_some(self.path.as_ref()))
+ .and_then(|truncated| (!looks_like_command_line_option(truncated)).then_some(self.path.as_ref()))
}
- /// Returns true if the path portion of the url is `/`.
+ /// Return true if the path portion of the URL is `/`.
pub fn path_is_root(&self) -> bool {
self.path == "/"
}
- /// Returns the actual or default port for use according to the url scheme.
+
+ /// Return the actual or default port for use according to the URL scheme.
/// Note that there may be no default port either.
pub fn port_or_default(&self) -> Option {
self.port.or_else(|| {
@@ -221,13 +295,13 @@ impl Url {
}
}
-fn looks_like_argument(b: &[u8]) -> bool {
+fn looks_like_command_line_option(b: &[u8]) -> bool {
b.first() == Some(&b'-')
}
/// Transformation
impl Url {
- /// Turn a file url like `file://relative` into `file:///root/relative`, hence it assures the url's path component is absolute, using
+ /// Turn a file URL like `file://relative` into `file:///root/relative`, hence it assures the URL's path component is absolute, using
/// `current_dir` if necessary.
pub fn canonicalized(&self, current_dir: &std::path::Path) -> Result {
let mut res = self.clone();
@@ -287,7 +361,7 @@ impl Url {
/// Deserialization
impl Url {
- /// Parse a URL from `bytes`
+ /// Parse a URL from `bytes`.
pub fn from_bytes(bytes: &BStr) -> Result {
parse(bytes)
}
diff --git a/gix-url/tests/access/mod.rs b/gix-url/tests/access/mod.rs
index b437834577e..ae2269ea2fc 100644
--- a/gix-url/tests/access/mod.rs
+++ b/gix-url/tests/access/mod.rs
@@ -30,6 +30,19 @@ mod canonicalized {
}
}
+use gix_url::ArgumentSafety;
+
+#[test]
+fn user() -> crate::Result {
+ let mut url = gix_url::parse("https://user:password@host/path".into())?;
+
+ assert_eq!(url.user(), Some("user"));
+ assert_eq!(url.set_user(Some("new-user".into())), Some("user".into()));
+ assert_eq!(url.user(), Some("new-user"));
+
+ Ok(())
+}
+
#[test]
fn password() -> crate::Result {
let mut url = gix_url::parse("https://user:password@host/path".into())?;
@@ -42,33 +55,107 @@ fn password() -> crate::Result {
}
#[test]
-fn user() -> crate::Result {
- let mut url = gix_url::parse("https://user:password@host/path".into())?;
+fn user_argument_safety() -> crate::Result {
+ let url = gix_url::parse("ssh://-Fconfigfile@foo/bar".into())?;
- assert_eq!(url.user(), Some("user"));
- assert_eq!(url.set_user(Some("new-user".into())), Some("user".into()));
- assert_eq!(url.user(), Some("new-user"));
+ assert_eq!(url.user(), Some("-Fconfigfile"));
+ assert_eq!(url.user_as_argument(), ArgumentSafety::Dangerous("-Fconfigfile"));
+ assert_eq!(url.user_argument_safe(), None, "An unsafe username is blocked.");
+
+ assert_eq!(url.host(), Some("foo"));
+ assert_eq!(url.host_as_argument(), ArgumentSafety::Usable("foo"));
+ assert_eq!(url.host_argument_safe(), Some("foo"));
+
+ assert_eq!(url.path, "/bar");
+ assert_eq!(url.path_argument_safe(), Some("/bar".into()));
Ok(())
}
#[test]
-fn host_argument_safe() -> crate::Result {
+fn host_argument_safety() -> crate::Result {
let url = gix_url::parse("ssh://-oProxyCommand=open$IFS-aCalculator/foo".into())?;
+
+ assert_eq!(url.user(), None);
+ assert_eq!(url.user_as_argument(), ArgumentSafety::Absent);
+ assert_eq!(
+ url.user_argument_safe(),
+ None,
+ "As there is no user. See all_argument_safe_valid()"
+ );
+
assert_eq!(url.host(), Some("-oProxyCommand=open$IFS-aCalculator"));
- assert_eq!(url.host_argument_safe(), None);
+ assert_eq!(
+ url.host_as_argument(),
+ ArgumentSafety::Dangerous("-oProxyCommand=open$IFS-aCalculator")
+ );
+ assert_eq!(url.host_argument_safe(), None, "An unsafe host string is blocked");
+
assert_eq!(url.path, "/foo");
assert_eq!(url.path_argument_safe(), Some("/foo".into()));
+
Ok(())
}
#[test]
-fn path_argument_safe() -> crate::Result {
+fn path_argument_safety() -> crate::Result {
let url = gix_url::parse("ssh://foo/-oProxyCommand=open$IFS-aCalculator".into())?;
+
+ assert_eq!(url.user(), None);
+ assert_eq!(url.user_as_argument(), ArgumentSafety::Absent);
+ assert_eq!(
+ url.user_argument_safe(),
+ None,
+ "As there is no user. See all_argument_safe_valid()"
+ );
+
assert_eq!(url.host(), Some("foo"));
+ assert_eq!(url.host_as_argument(), ArgumentSafety::Usable("foo"));
assert_eq!(url.host_argument_safe(), Some("foo"));
+
assert_eq!(url.path, "/-oProxyCommand=open$IFS-aCalculator");
- assert_eq!(url.path_argument_safe(), None);
+ assert_eq!(url.path_argument_safe(), None, "An unsafe path is blocked");
+
+ Ok(())
+}
+
+#[test]
+fn all_argument_safety_safe() -> crate::Result {
+ let url = gix_url::parse("ssh://user.name@example.com/path/to/file".into())?;
+
+ assert_eq!(url.user(), Some("user.name"));
+ assert_eq!(url.user_as_argument(), ArgumentSafety::Usable("user.name"));
+ assert_eq!(url.user_argument_safe(), Some("user.name"));
+
+ assert_eq!(url.host(), Some("example.com"));
+ assert_eq!(url.host_as_argument(), ArgumentSafety::Usable("example.com"));
+ assert_eq!(url.host_argument_safe(), Some("example.com"));
+
+ assert_eq!(url.path, "/path/to/file");
+ assert_eq!(url.path_argument_safe(), Some("/path/to/file".into()));
+
+ Ok(())
+}
+
+#[test]
+fn all_argument_safety_not_safe() -> crate::Result {
+ let all_bad = "ssh://-Fconfigfile@-oProxyCommand=open$IFS-aCalculator/-oProxyCommand=open$IFS-aCalculator";
+ let url = gix_url::parse(all_bad.into())?;
+
+ assert_eq!(url.user(), Some("-Fconfigfile"));
+ assert_eq!(url.user_as_argument(), ArgumentSafety::Dangerous("-Fconfigfile"));
+ assert_eq!(url.user_argument_safe(), None); // An unsafe username is blocked.
+
+ assert_eq!(url.host(), Some("-oProxyCommand=open$IFS-aCalculator"));
+ assert_eq!(
+ url.host_as_argument(),
+ ArgumentSafety::Dangerous("-oProxyCommand=open$IFS-aCalculator")
+ );
+ assert_eq!(url.host_argument_safe(), None, "An unsafe host string is blocked");
+
+ assert_eq!(url.path, "/-oProxyCommand=open$IFS-aCalculator");
+ assert_eq!(url.path_argument_safe(), None, "An unsafe path is blocked");
+
Ok(())
}
diff --git a/tests/journey/gix.sh b/tests/journey/gix.sh
index ce359b134d8..6af7cb507aa 100644
--- a/tests/journey/gix.sh
+++ b/tests/journey/gix.sh
@@ -345,12 +345,33 @@ title "gix commit-graph"
}
)
)
+ (with "an ambiguous ssh username which could be mistaken for an argument"
+ snapshot="$snapshot/fail-ambiguous-username"
+ (with "explicit ssh (true url with scheme)"
+ it "fails without trying to pass it to command-line programs" && {
+ WITH_SNAPSHOT="$snapshot/explicit-ssh" \
+ expect_run $WITH_FAILURE "$exe_plumbing" free pack receive 'ssh://-Fconfigfile@foo/bar'
+ }
+ )
+ (with "implicit ssh (special syntax with no scheme)"
+ it "fails without trying to pass it to command-line programs" && {
+ WITH_SNAPSHOT="$snapshot/implicit-ssh" \
+ expect_run $WITH_FAILURE "$exe_plumbing" free pack receive -- '-Fconfigfile@foo:bar/baz'
+ }
+ )
+ )
(with "an ambiguous ssh host which could be mistaken for an argument"
it "fails without trying to pass it to command-line programs" && {
- WITH_SNAPSHOT="$snapshot/fail-ambigous-host" \
+ WITH_SNAPSHOT="$snapshot/fail-ambiguous-host" \
expect_run $WITH_FAILURE "$exe_plumbing" free pack receive 'ssh://-oProxyCommand=open$IFS-aCalculator/foo'
}
)
+ (with "an ambiguous ssh path which could be mistaken for an argument"
+ it "fails without trying to pass it to command-line programs" && {
+ WITH_SNAPSHOT="$snapshot/fail-ambiguous-path" \
+ expect_run $WITH_FAILURE "$exe_plumbing" free pack receive 'git@foo:-oProxyCommand=open$IFS-aCalculator/bar'
+ }
+ )
fi
)
elif [[ "$kind" = "small" ]]; then
@@ -364,12 +385,33 @@ title "gix commit-graph"
if test "$kind" = "max" || test "$kind" = "max-pure"; then
(with "the 'clone' sub-command"
snapshot="$snapshot/clone"
+ (with "an ambiguous ssh username which could be mistaken for an argument"
+ snapshot="$snapshot/fail-ambiguous-username"
+ (with "explicit ssh (true url with scheme)"
+ it "fails without trying to pass it to command-line programs" && {
+ WITH_SNAPSHOT="$snapshot/explicit-ssh" \
+ expect_run $WITH_FAILURE "$exe_plumbing" clone 'ssh://-Fconfigfile@foo/bar'
+ }
+ )
+ (with "implicit ssh (special syntax with no scheme)"
+ it "fails without trying to pass it to command-line programs" && {
+ WITH_SNAPSHOT="$snapshot/implicit-ssh" \
+ expect_run $WITH_FAILURE "$exe_plumbing" clone -- '-Fconfigfile@foo:bar/baz'
+ }
+ )
+ )
(with "an ambiguous ssh host which could be mistaken for an argument"
it "fails without trying to pass it to command-line programs" && {
- WITH_SNAPSHOT="$snapshot/fail-ambigous-host" \
+ WITH_SNAPSHOT="$snapshot/fail-ambiguous-host" \
expect_run $WITH_FAILURE "$exe_plumbing" clone 'ssh://-oProxyCommand=open$IFS-aCalculator/foo'
}
)
+ (with "an ambiguous ssh path which could be mistaken for an argument"
+ it "fails without trying to pass it to command-line programs" && {
+ WITH_SNAPSHOT="$snapshot/fail-ambiguous-path" \
+ expect_run $WITH_FAILURE "$exe_plumbing" clone 'git@foo:-oProxyCommand=open$IFS-aCalculator/bar'
+ }
+ )
)
fi
(with "the 'index' sub-command"
diff --git a/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-path b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-path
new file mode 100644
index 00000000000..5ce09d4e1a9
--- /dev/null
+++ b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-path
@@ -0,0 +1 @@
+[2K
Error: The repository path '-oProxyCommand=open$IFS-aCalculator/bar' could be mistaken for a command-line argument
\ No newline at end of file
diff --git a/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/explicit-ssh b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/explicit-ssh
new file mode 100644
index 00000000000..78d252596ad
--- /dev/null
+++ b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/explicit-ssh
@@ -0,0 +1 @@
+[2K
Error: Username '-Fconfigfile' could be mistaken for a command-line argument
\ No newline at end of file
diff --git a/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/implicit-ssh b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/implicit-ssh
new file mode 100644
index 00000000000..78d252596ad
--- /dev/null
+++ b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/implicit-ssh
@@ -0,0 +1 @@
+[2K
Error: Username '-Fconfigfile' could be mistaken for a command-line argument
\ No newline at end of file
diff --git a/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-path b/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-path
new file mode 100644
index 00000000000..2d3c844e17f
--- /dev/null
+++ b/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-path
@@ -0,0 +1 @@
+Error: The repository path '-oProxyCommand=open$IFS-aCalculator/bar' could be mistaken for a command-line argument
\ No newline at end of file
diff --git a/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-username/explicit-ssh b/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-username/explicit-ssh
new file mode 100644
index 00000000000..8a9fca8771d
--- /dev/null
+++ b/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-username/explicit-ssh
@@ -0,0 +1 @@
+Error: Username '-Fconfigfile' could be mistaken for a command-line argument
\ No newline at end of file
diff --git a/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-username/implicit-ssh b/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-username/implicit-ssh
new file mode 100644
index 00000000000..8a9fca8771d
--- /dev/null
+++ b/tests/snapshots/plumbing/no-repo/pack/receive/fail-ambiguous-username/implicit-ssh
@@ -0,0 +1 @@
+Error: Username '-Fconfigfile' could be mistaken for a command-line argument
\ No newline at end of file