Skip to content

Commit 11698e5

Browse files
committed
Merge branch 'ds/credentials-in-url'
The "fetch.credentialsInUrl" configuration variable controls what happens when a URL with embedded login credential is used. * ds/credentials-in-url: remote: create fetch.credentialsInUrl config
2 parents eef985e + 6dcbdc0 commit 11698e5

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

Documentation/config/fetch.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,17 @@ fetch.writeCommitGraph::
9696
merge and the write may take longer. Having an updated commit-graph
9797
file helps performance of many Git commands, including `git merge-base`,
9898
`git push -f`, and `git log --graph`. Defaults to false.
99+
100+
fetch.credentialsInUrl::
101+
A URL can contain plaintext credentials in the form
102+
`<protocol>://<user>:<password>@<domain>/<path>`. Using such URLs
103+
is not recommended as it exposes the password in multiple ways,
104+
including Git storing the URL as plaintext in the repository config.
105+
The `fetch.credentialsInUrl` option provides instruction for how Git
106+
should react to seeing such a URL, with these values:
107+
+
108+
* `allow` (default): Git will proceed with its activity without warning.
109+
* `warn`: Git will write a warning message to `stderr` when parsing a URL
110+
with a plaintext credential.
111+
* `die`: Git will write a failure message to `stderr` when parsing a URL
112+
with a plaintext credential.

remote.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "cache.h"
22
#include "config.h"
33
#include "remote.h"
4+
#include "urlmatch.h"
45
#include "refs.h"
56
#include "refspec.h"
67
#include "object-store.h"
@@ -617,6 +618,50 @@ const char *remote_ref_for_branch(struct branch *branch, int for_push)
617618
return NULL;
618619
}
619620

621+
static void validate_remote_url(struct remote *remote)
622+
{
623+
int i;
624+
const char *value;
625+
struct strbuf redacted = STRBUF_INIT;
626+
int warn_not_die;
627+
628+
if (git_config_get_string_tmp("fetch.credentialsinurl", &value))
629+
return;
630+
631+
if (!strcmp("warn", value))
632+
warn_not_die = 1;
633+
else if (!strcmp("die", value))
634+
warn_not_die = 0;
635+
else if (!strcmp("allow", value))
636+
return;
637+
else
638+
die(_("unrecognized value fetch.credentialsInURL: '%s'"), value);
639+
640+
for (i = 0; i < remote->url_nr; i++) {
641+
struct url_info url_info = { 0 };
642+
643+
if (!url_normalize(remote->url[i], &url_info) ||
644+
!url_info.passwd_off)
645+
goto loop_cleanup;
646+
647+
strbuf_reset(&redacted);
648+
strbuf_add(&redacted, url_info.url, url_info.passwd_off);
649+
strbuf_addstr(&redacted, "<redacted>");
650+
strbuf_addstr(&redacted,
651+
url_info.url + url_info.passwd_off + url_info.passwd_len);
652+
653+
if (warn_not_die)
654+
warning(_("URL '%s' uses plaintext credentials"), redacted.buf);
655+
else
656+
die(_("URL '%s' uses plaintext credentials"), redacted.buf);
657+
658+
loop_cleanup:
659+
free(url_info.url);
660+
}
661+
662+
strbuf_release(&redacted);
663+
}
664+
620665
static struct remote *
621666
remotes_remote_get_1(struct remote_state *remote_state, const char *name,
622667
const char *(*get_default)(struct remote_state *,
@@ -642,6 +687,9 @@ remotes_remote_get_1(struct remote_state *remote_state, const char *name,
642687
add_url_alias(remote_state, ret, name);
643688
if (!valid_remote(ret))
644689
return NULL;
690+
691+
validate_remote_url(ret);
692+
645693
return ret;
646694
}
647695

t/t5516-fetch-push.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This test checks the following functionality:
1212
* --porcelain output format
1313
* hiderefs
1414
* reflogs
15+
* URL validation
1516
'
1617

1718
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
@@ -1833,4 +1834,35 @@ test_expect_success 'refuse to push a hidden ref, and make sure do not pollute t
18331834
test_dir_is_empty testrepo/.git/objects/pack
18341835
'
18351836

1837+
test_expect_success 'fetch warns or fails when using username:password' '
1838+
message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" &&
1839+
test_must_fail git -c fetch.credentialsInUrl=allow fetch https://username:password@localhost 2>err &&
1840+
! grep "$message" err &&
1841+
1842+
test_must_fail git -c fetch.credentialsInUrl=warn fetch https://username:password@localhost 2>err &&
1843+
grep "warning: $message" err >warnings &&
1844+
test_line_count = 3 warnings &&
1845+
1846+
test_must_fail git -c fetch.credentialsInUrl=die fetch https://username:password@localhost 2>err &&
1847+
grep "fatal: $message" err >warnings &&
1848+
test_line_count = 1 warnings &&
1849+
1850+
test_must_fail git -c fetch.credentialsInUrl=die fetch https://username:@localhost 2>err &&
1851+
grep "fatal: $message" err >warnings &&
1852+
test_line_count = 1 warnings
1853+
'
1854+
1855+
1856+
test_expect_success 'push warns or fails when using username:password' '
1857+
message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" &&
1858+
test_must_fail git -c fetch.credentialsInUrl=allow push https://username:password@localhost 2>err &&
1859+
! grep "$message" err &&
1860+
1861+
test_must_fail git -c fetch.credentialsInUrl=warn push https://username:password@localhost 2>err &&
1862+
grep "warning: $message" err >warnings &&
1863+
test_must_fail git -c fetch.credentialsInUrl=die push https://username:password@localhost 2>err &&
1864+
grep "fatal: $message" err >warnings &&
1865+
test_line_count = 1 warnings
1866+
'
1867+
18361868
test_done

t/t5601-clone.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,29 @@ test_expect_success 'clone respects GIT_WORK_TREE' '
7171
7272
'
7373

74+
test_expect_success 'clone warns or fails when using username:password' '
75+
message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" &&
76+
test_must_fail git -c fetch.credentialsInUrl=allow clone https://username:password@localhost attempt1 2>err &&
77+
! grep "$message" err &&
78+
79+
test_must_fail git -c fetch.credentialsInUrl=warn clone https://username:password@localhost attempt2 2>err &&
80+
grep "warning: $message" err >warnings &&
81+
test_line_count = 2 warnings &&
82+
83+
test_must_fail git -c fetch.credentialsInUrl=die clone https://username:password@localhost attempt3 2>err &&
84+
grep "fatal: $message" err >warnings &&
85+
test_line_count = 1 warnings &&
86+
87+
test_must_fail git -c fetch.credentialsInUrl=die clone https://username:@localhost attempt3 2>err &&
88+
grep "fatal: $message" err >warnings &&
89+
test_line_count = 1 warnings
90+
'
91+
92+
test_expect_success 'clone does not detect username:password when it is https://username@domain:port/' '
93+
test_must_fail git -c fetch.credentialsInUrl=warn clone https://username@localhost:8080 attempt3 2>err &&
94+
! grep "uses plaintext credentials" err
95+
'
96+
7497
test_expect_success 'clone from hooks' '
7598
7699
test_create_repo r0 &&

0 commit comments

Comments
 (0)