Skip to content

Commit d80b5f6

Browse files
committed
feat: add Url::host_argument_safe() and Url::path_argument_safe()
This will not provide values if they could be confused for an argument to to a commaneline application.
1 parent 54a8495 commit d80b5f6

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

gix-url/src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ pub struct Url {
4848
/// The port to use when connecting to a host. If `None`, standard ports depending on `scheme` will be used.
4949
pub port: Option<u16>,
5050
/// The path portion of the URL, usually the location of the git repository.
51+
///
52+
/// # Security-Warning
53+
///
54+
/// URLs allow paths to start with `-` which makes it possible to mask command-line arguments as path which then leads to
55+
/// the invocation of programs from an attacker controlled URL. See https://secure.phabricator.com/T12961 for details.
56+
///
57+
/// If this value is going to be used in a command-line application, call [Self::path_argument_safe()] instead.
5158
pub path: bstr::BString,
5259
}
5360

@@ -123,9 +130,34 @@ impl Url {
123130
self.password.as_deref()
124131
}
125132
/// Returns the host mentioned in the url, if present.
133+
///
134+
/// # Security-Warning
135+
///
136+
/// URLs allow hosts to start with `-` which makes it possible to mask command-line arguments as host which then leads to
137+
/// the invocation of programs from an attacker controlled URL. See https://secure.phabricator.com/T12961 for details.
138+
///
139+
/// If this value is going to be used in a command-line application, call [Self::host_argument_safe()] instead.
126140
pub fn host(&self) -> Option<&str> {
127141
self.host.as_deref()
128142
}
143+
144+
/// Return the host of this URL if present *and* if it can't be mistaken for a command-line argument.
145+
///
146+
/// Use this method if the host is going to be passed to a command-line application.
147+
pub fn host_argument_safe(&self) -> Option<&str> {
148+
self.host().filter(|host| !looks_like_argument(host.as_bytes()))
149+
}
150+
151+
/// Return the path of this URL *and* if it can't be mistaken for a command-line argument.
152+
/// Note that it always begins with a slash, which is ignored for this comparison.
153+
///
154+
/// Use this method if the path is going to be passed to a command-line application.
155+
pub fn path_argument_safe(&self) -> Option<&BStr> {
156+
self.path
157+
.get(1..)
158+
.and_then(|truncated| (!looks_like_argument(truncated)).then_some(self.path.as_ref()))
159+
}
160+
129161
/// Returns true if the path portion of the url is `/`.
130162
pub fn path_is_root(&self) -> bool {
131163
self.path == "/"
@@ -146,6 +178,10 @@ impl Url {
146178
}
147179
}
148180

181+
fn looks_like_argument(b: &[u8]) -> bool {
182+
b.get(0) == Some(&b'-')
183+
}
184+
149185
/// Transformation
150186
impl Url {
151187
/// Turn a file url like `file://relative` into `file:///root/relative`, hence it assures the url's path component is absolute, using

gix-url/tests/access/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,23 @@ mod canonicalized {
2929
Ok(())
3030
}
3131
}
32+
33+
#[test]
34+
fn host_argument_safe() -> crate::Result {
35+
let url = gix_url::parse("ssh://-oProxyCommand=open$IFS-aCalculator/foo".into())?;
36+
assert_eq!(url.host(), Some("-oProxyCommand=open$IFS-aCalculator"));
37+
assert_eq!(url.host_argument_safe(), None);
38+
assert_eq!(url.path, "/foo");
39+
assert_eq!(url.path_argument_safe(), Some("/foo".into()));
40+
Ok(())
41+
}
42+
43+
#[test]
44+
fn path_argument_safe() -> crate::Result {
45+
let url = gix_url::parse("ssh://foo/-oProxyCommand=open$IFS-aCalculator".into())?;
46+
assert_eq!(url.host(), Some("foo"));
47+
assert_eq!(url.host_argument_safe(), Some("foo"));
48+
assert_eq!(url.path, "/-oProxyCommand=open$IFS-aCalculator");
49+
assert_eq!(url.path_argument_safe(), None);
50+
Ok(())
51+
}

0 commit comments

Comments
 (0)