diff --git a/gix-transport/src/client/blocking_io/file.rs b/gix-transport/src/client/blocking_io/file.rs index 5f5bf25b0d..06ce1cbfbd 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 16f47bd25f..6820051a5e 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 70905829f6..d7162c09b3 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 4e4da78070..8fa19d0bb7 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 a810ad66c7..8468ec80ef 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 b437834577..ae2269ea2f 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 ce359b134d..6af7cb507a 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 0000000000..5ce09d4e1a --- /dev/null +++ b/tests/snapshots/plumbing/no-repo/pack/clone/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/clone/fail-ambiguous-username/explicit-ssh b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/explicit-ssh new file mode 100644 index 0000000000..78d252596a --- /dev/null +++ b/tests/snapshots/plumbing/no-repo/pack/clone/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/clone/fail-ambiguous-username/implicit-ssh b/tests/snapshots/plumbing/no-repo/pack/clone/fail-ambiguous-username/implicit-ssh new file mode 100644 index 0000000000..78d252596a --- /dev/null +++ b/tests/snapshots/plumbing/no-repo/pack/clone/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 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 0000000000..2d3c844e17 --- /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 0000000000..8a9fca8771 --- /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 0000000000..8a9fca8771 --- /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