isomorphic-git 1.42.1 → 1.42.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
7
7
  var ignore = _interopDefault(require('ignore'));
8
8
  var Hash = _interopDefault(require('sha.js/sha1.js'));
9
9
  var AsyncLock = _interopDefault(require('async-lock'));
10
+ var cleanGitRef = _interopDefault(require('clean-git-ref'));
10
11
  var crc32 = _interopDefault(require('crc-32'));
11
12
  var pako = _interopDefault(require('pako'));
12
13
  var crypto$1 = require('crypto');
@@ -1883,6 +1884,22 @@ function compareRefNames(a, b) {
1883
1884
  return tmp
1884
1885
  }
1885
1886
 
1887
+ /*
1888
+ Adapted from is-git-ref-name-valid
1889
+ SPDX-License-Identifier: MIT
1890
+ Copyright © Vincent Weevers
1891
+ */
1892
+
1893
+ // eslint-disable-next-line no-control-regex
1894
+ const bad = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/;
1895
+
1896
+ function isValidRef(name, onelevel) {
1897
+ if (typeof name !== 'string')
1898
+ throw new TypeError('Reference name must be a string')
1899
+
1900
+ return !bad.test(name) && (!!onelevel || name.includes('/'))
1901
+ }
1902
+
1886
1903
  // @see https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions
1887
1904
  const refpaths = ref => [
1888
1905
  `${ref}`,
@@ -1904,6 +1921,13 @@ function assertWritableRef(ref) {
1904
1921
  if (GIT_FILES.includes(ref)) {
1905
1922
  throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
1906
1923
  }
1924
+ // A ref name is joined onto gitdir with `join()`, which collapses `..`
1925
+ // exactly like `path.join`. A server-supplied ref/tag name or the wildcard
1926
+ // capture of a refspec can therefore climb out of gitdir, or anywhere
1927
+ // beneath it, entirely unvalidated up to this point. See GHSA-h3c3-jh3g-8hcc.
1928
+ if (!isValidRef(ref, true)) {
1929
+ throw new InvalidRefNameError(ref, cleanGitRef.clean(ref))
1930
+ }
1907
1931
  }
1908
1932
 
1909
1933
  /**
@@ -1963,21 +1987,39 @@ class GitRefManager {
1963
1987
  ])) {
1964
1988
  const symtarget = refspec.translateOne(symrefs.get(serverRef));
1965
1989
  if (symtarget) {
1990
+ // The destination (translatedRef) is validated below, but the target
1991
+ // a symref points at is server-supplied too (the wire parser accepts
1992
+ // `symref=HEAD:(.*)` unrestricted) and only ever passed through the
1993
+ // refspec's plain string substitution, never checked. Left alone, a
1994
+ // `..`-laden target is written into the file content as-is and later
1995
+ // drives an out-of-gitdir read the next time something resolves it.
1996
+ // See GHSA-h3c3-jh3g-8hcc.
1997
+ assertWritableRef(symtarget);
1966
1998
  symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
1967
1999
  }
1968
2000
  }
2001
+ // Tags aren't translated by a refspec, but they are still a server-supplied
2002
+ // name written straight to gitdir below, so they need the same check.
2003
+ // Computed here (pure, no I/O) so it can be validated up front and then
2004
+ // reused unchanged in the write step further down.
2005
+ const tagRefsToWrite = tags
2006
+ ? [...refs.keys()].filter(
2007
+ serverRef =>
2008
+ serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')
2009
+ )
2010
+ : [];
1969
2011
  // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
1970
2012
  // config like `+refs/heads/main:index` lands a write on `.git/index`.
1971
2013
  // Refuse it here rather than at the write loop: `pruneTags` and `prune`
1972
2014
  // delete refs in between, so a later throw leaves the repository pruned
1973
- // and not updated. The tags added below are always `refs/tags/...`, which
1974
- // is never a system file, so nothing is missed by checking this early.
2015
+ // and not updated.
1975
2016
  for (const [, translatedRef] of refTranslations) {
1976
2017
  assertWritableRef(translatedRef);
1977
2018
  }
1978
2019
  for (const [translatedRef] of symrefTranslations) {
1979
2020
  assertWritableRef(translatedRef);
1980
2021
  }
2022
+ tagRefsToWrite.forEach(assertWritableRef);
1981
2023
  // Delete all current tags if the pruneTags argument is true.
1982
2024
  if (pruneTags) {
1983
2025
  const tags = await GitRefManager.listRefs({
@@ -1992,16 +2034,12 @@ class GitRefManager {
1992
2034
  });
1993
2035
  }
1994
2036
  // Add all tags if the fetch tags argument is true.
1995
- if (tags) {
1996
- for (const serverRef of refs.keys()) {
1997
- if (serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')) {
1998
- // Git's behavior is to only fetch tags that do not conflict with tags already present.
1999
- if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2000
- // Always use the object id of the tag itself, and not the peeled object id.
2001
- const oid = refs.get(serverRef);
2002
- actualRefsToWrite.set(serverRef, oid);
2003
- }
2004
- }
2037
+ for (const serverRef of tagRefsToWrite) {
2038
+ // Git's behavior is to only fetch tags that do not conflict with tags already present.
2039
+ if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2040
+ // Always use the object id of the tag itself, and not the peeled object id.
2041
+ const oid = refs.get(serverRef);
2042
+ actualRefsToWrite.set(serverRef, oid);
2005
2043
  }
2006
2044
  }
2007
2045
  // Combine refs and symrefs giving symrefs priority
package/managers/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import ignore from 'ignore';
2
2
  import Hash from 'sha.js/sha1.js';
3
3
  import AsyncLock from 'async-lock';
4
+ import cleanGitRef from 'clean-git-ref';
4
5
  import crc32 from 'crc-32';
5
6
  import pako from 'pako';
6
7
 
@@ -1876,6 +1877,22 @@ function compareRefNames(a, b) {
1876
1877
  return tmp
1877
1878
  }
1878
1879
 
1880
+ /*
1881
+ Adapted from is-git-ref-name-valid
1882
+ SPDX-License-Identifier: MIT
1883
+ Copyright © Vincent Weevers
1884
+ */
1885
+
1886
+ // eslint-disable-next-line no-control-regex
1887
+ const bad = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/;
1888
+
1889
+ function isValidRef(name, onelevel) {
1890
+ if (typeof name !== 'string')
1891
+ throw new TypeError('Reference name must be a string')
1892
+
1893
+ return !bad.test(name) && (!!onelevel || name.includes('/'))
1894
+ }
1895
+
1879
1896
  // @see https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions
1880
1897
  const refpaths = ref => [
1881
1898
  `${ref}`,
@@ -1897,6 +1914,13 @@ function assertWritableRef(ref) {
1897
1914
  if (GIT_FILES.includes(ref)) {
1898
1915
  throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
1899
1916
  }
1917
+ // A ref name is joined onto gitdir with `join()`, which collapses `..`
1918
+ // exactly like `path.join`. A server-supplied ref/tag name or the wildcard
1919
+ // capture of a refspec can therefore climb out of gitdir, or anywhere
1920
+ // beneath it, entirely unvalidated up to this point. See GHSA-h3c3-jh3g-8hcc.
1921
+ if (!isValidRef(ref, true)) {
1922
+ throw new InvalidRefNameError(ref, cleanGitRef.clean(ref))
1923
+ }
1900
1924
  }
1901
1925
 
1902
1926
  /**
@@ -1956,21 +1980,39 @@ class GitRefManager {
1956
1980
  ])) {
1957
1981
  const symtarget = refspec.translateOne(symrefs.get(serverRef));
1958
1982
  if (symtarget) {
1983
+ // The destination (translatedRef) is validated below, but the target
1984
+ // a symref points at is server-supplied too (the wire parser accepts
1985
+ // `symref=HEAD:(.*)` unrestricted) and only ever passed through the
1986
+ // refspec's plain string substitution, never checked. Left alone, a
1987
+ // `..`-laden target is written into the file content as-is and later
1988
+ // drives an out-of-gitdir read the next time something resolves it.
1989
+ // See GHSA-h3c3-jh3g-8hcc.
1990
+ assertWritableRef(symtarget);
1959
1991
  symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
1960
1992
  }
1961
1993
  }
1994
+ // Tags aren't translated by a refspec, but they are still a server-supplied
1995
+ // name written straight to gitdir below, so they need the same check.
1996
+ // Computed here (pure, no I/O) so it can be validated up front and then
1997
+ // reused unchanged in the write step further down.
1998
+ const tagRefsToWrite = tags
1999
+ ? [...refs.keys()].filter(
2000
+ serverRef =>
2001
+ serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')
2002
+ )
2003
+ : [];
1962
2004
  // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
1963
2005
  // config like `+refs/heads/main:index` lands a write on `.git/index`.
1964
2006
  // Refuse it here rather than at the write loop: `pruneTags` and `prune`
1965
2007
  // delete refs in between, so a later throw leaves the repository pruned
1966
- // and not updated. The tags added below are always `refs/tags/...`, which
1967
- // is never a system file, so nothing is missed by checking this early.
2008
+ // and not updated.
1968
2009
  for (const [, translatedRef] of refTranslations) {
1969
2010
  assertWritableRef(translatedRef);
1970
2011
  }
1971
2012
  for (const [translatedRef] of symrefTranslations) {
1972
2013
  assertWritableRef(translatedRef);
1973
2014
  }
2015
+ tagRefsToWrite.forEach(assertWritableRef);
1974
2016
  // Delete all current tags if the pruneTags argument is true.
1975
2017
  if (pruneTags) {
1976
2018
  const tags = await GitRefManager.listRefs({
@@ -1985,16 +2027,12 @@ class GitRefManager {
1985
2027
  });
1986
2028
  }
1987
2029
  // Add all tags if the fetch tags argument is true.
1988
- if (tags) {
1989
- for (const serverRef of refs.keys()) {
1990
- if (serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')) {
1991
- // Git's behavior is to only fetch tags that do not conflict with tags already present.
1992
- if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
1993
- // Always use the object id of the tag itself, and not the peeled object id.
1994
- const oid = refs.get(serverRef);
1995
- actualRefsToWrite.set(serverRef, oid);
1996
- }
1997
- }
2030
+ for (const serverRef of tagRefsToWrite) {
2031
+ // Git's behavior is to only fetch tags that do not conflict with tags already present.
2032
+ if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2033
+ // Always use the object id of the tag itself, and not the peeled object id.
2034
+ const oid = refs.get(serverRef);
2035
+ actualRefsToWrite.set(serverRef, oid);
1998
2036
  }
1999
2037
  }
2000
2038
  // Combine refs and symrefs giving symrefs priority