isomorphic-git 1.38.6 → 1.38.8

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.
@@ -4919,13 +4919,19 @@ function parseBuffer(buffer) {
4919
4919
  // "." and ".." (which resolve outside the working directory), and ".git" (which git
4920
4920
  // disallows as a path component). Checks mirror git's is_ntfs_dotgit() and
4921
4921
  // is_hfs_dotgit(): case-insensitive, trailing dots/spaces stripped (NTFS),
4922
- // NTFS 8.3 short name aliases (git~1..git~9), and HFS+ ignorable Unicode
4923
- // characters stripped (zero-width joiners, directional marks, BOM, etc.).
4922
+ // NTFS 8.3 short name aliases (git~1..git~9), NTFS Alternate Data Streams, and
4923
+ // HFS+ ignorable Unicode characters stripped (zero-width joiners, directional
4924
+ // marks, BOM, etc.).
4924
4925
  const hfsClean = path.replace(
4925
4926
  /[\u200C-\u200F\u202A-\u202E\u206A-\u206F\uFEFF]/g,
4926
4927
  ''
4927
4928
  );
4928
- const normalized = hfsClean.toLowerCase().replace(/[. ]+$/, '');
4929
+ // NTFS Alternate Data Streams are referenced as `<name>:<stream>:<type>`, and a
4930
+ // directory's default stream lets `.git::$INDEX_ALLOCATION` resolve to the `.git`
4931
+ // directory itself. git forbids *any* ADS, so only the portion before the first
4932
+ // colon names the actual entry we normalize and check.
4933
+ const ntfsClean = hfsClean.split(':')[0];
4934
+ const normalized = ntfsClean.toLowerCase().replace(/[. ]+$/, '');
4929
4935
  if (
4930
4936
  path.includes('\\') ||
4931
4937
  path.includes('/') ||
@@ -5862,6 +5868,31 @@ async function _writeTree({ fs, gitdir, tree }) {
5862
5868
  return oid
5863
5869
  }
5864
5870
 
5871
+ /**
5872
+ * Refuse to materialize a working-tree entry whose parent path traverses a
5873
+ * symbolic link. A leading component that is a symlink could otherwise redirect
5874
+ * a write or mkdir outside the working tree. git applies the same check: it does
5875
+ * not follow symlinks in the leading path when writing working-tree files.
5876
+ *
5877
+ * @param {import('../models/FileSystem.js').FileSystem} fs
5878
+ * @param {string} dir
5879
+ * @param {string} fullpath
5880
+ */
5881
+ async function assertNoSymlinkInLeadingPath(fs, dir, fullpath) {
5882
+ const parts = fullpath.split('/');
5883
+ parts.pop(); // the final segment is the entry being written, not a leading dir
5884
+ let current = dir;
5885
+ for (const part of parts) {
5886
+ if (part === '' || part === '.') continue
5887
+ current = `${current}/${part}`;
5888
+ const stats = await fs.lstat(current);
5889
+ // lstat returns null when the path doesn't exist yet (nothing to traverse).
5890
+ if (stats && stats.isSymbolicLink()) {
5891
+ throw new UnsafeFilepathError(fullpath)
5892
+ }
5893
+ }
5894
+ }
5895
+
5865
5896
  function posixifyPathBuffer(buffer) {
5866
5897
  let idx;
5867
5898
  while (~(idx = buffer.indexOf(92))) buffer[idx] = 47;
@@ -6118,6 +6149,11 @@ async function applyTreeChanges({
6118
6149
  await fs.rmdir(currentFilepath);
6119
6150
  break
6120
6151
  case 'mkdir':
6152
+ // Don't mkdir through a symlinked leading path: a parent component that
6153
+ // is a symlink could otherwise redirect the directory creation outside
6154
+ // the working tree. git applies the same check (it does not follow
6155
+ // symlinks here) — see checkout.
6156
+ await assertNoSymlinkInLeadingPath(fs, dir, op.filepath);
6121
6157
  await fs.mkdir(currentFilepath);
6122
6158
  break
6123
6159
  case 'rm':
@@ -6130,6 +6166,10 @@ async function applyTreeChanges({
6130
6166
  currentFilepath.startsWith(removedDir)
6131
6167
  )
6132
6168
  ) {
6169
+ // Don't write through a symlinked leading path (see checkout): a
6170
+ // parent component that is a symlink could redirect the write to a
6171
+ // location outside the working tree.
6172
+ await assertNoSymlinkInLeadingPath(fs, dir, op.filepath);
6133
6173
  const { object } = await _readObject({
6134
6174
  fs,
6135
6175
  cache: {},
package/managers/index.js CHANGED
@@ -4906,13 +4906,19 @@ function parseBuffer(buffer) {
4906
4906
  // "." and ".." (which resolve outside the working directory), and ".git" (which git
4907
4907
  // disallows as a path component). Checks mirror git's is_ntfs_dotgit() and
4908
4908
  // is_hfs_dotgit(): case-insensitive, trailing dots/spaces stripped (NTFS),
4909
- // NTFS 8.3 short name aliases (git~1..git~9), and HFS+ ignorable Unicode
4910
- // characters stripped (zero-width joiners, directional marks, BOM, etc.).
4909
+ // NTFS 8.3 short name aliases (git~1..git~9), NTFS Alternate Data Streams, and
4910
+ // HFS+ ignorable Unicode characters stripped (zero-width joiners, directional
4911
+ // marks, BOM, etc.).
4911
4912
  const hfsClean = path.replace(
4912
4913
  /[\u200C-\u200F\u202A-\u202E\u206A-\u206F\uFEFF]/g,
4913
4914
  ''
4914
4915
  );
4915
- const normalized = hfsClean.toLowerCase().replace(/[. ]+$/, '');
4916
+ // NTFS Alternate Data Streams are referenced as `<name>:<stream>:<type>`, and a
4917
+ // directory's default stream lets `.git::$INDEX_ALLOCATION` resolve to the `.git`
4918
+ // directory itself. git forbids *any* ADS, so only the portion before the first
4919
+ // colon names the actual entry we normalize and check.
4920
+ const ntfsClean = hfsClean.split(':')[0];
4921
+ const normalized = ntfsClean.toLowerCase().replace(/[. ]+$/, '');
4916
4922
  if (
4917
4923
  path.includes('\\') ||
4918
4924
  path.includes('/') ||
@@ -5849,6 +5855,31 @@ async function _writeTree({ fs, gitdir, tree }) {
5849
5855
  return oid
5850
5856
  }
5851
5857
 
5858
+ /**
5859
+ * Refuse to materialize a working-tree entry whose parent path traverses a
5860
+ * symbolic link. A leading component that is a symlink could otherwise redirect
5861
+ * a write or mkdir outside the working tree. git applies the same check: it does
5862
+ * not follow symlinks in the leading path when writing working-tree files.
5863
+ *
5864
+ * @param {import('../models/FileSystem.js').FileSystem} fs
5865
+ * @param {string} dir
5866
+ * @param {string} fullpath
5867
+ */
5868
+ async function assertNoSymlinkInLeadingPath(fs, dir, fullpath) {
5869
+ const parts = fullpath.split('/');
5870
+ parts.pop(); // the final segment is the entry being written, not a leading dir
5871
+ let current = dir;
5872
+ for (const part of parts) {
5873
+ if (part === '' || part === '.') continue
5874
+ current = `${current}/${part}`;
5875
+ const stats = await fs.lstat(current);
5876
+ // lstat returns null when the path doesn't exist yet (nothing to traverse).
5877
+ if (stats && stats.isSymbolicLink()) {
5878
+ throw new UnsafeFilepathError(fullpath)
5879
+ }
5880
+ }
5881
+ }
5882
+
5852
5883
  function posixifyPathBuffer(buffer) {
5853
5884
  let idx;
5854
5885
  while (~(idx = buffer.indexOf(92))) buffer[idx] = 47;
@@ -6105,6 +6136,11 @@ async function applyTreeChanges({
6105
6136
  await fs.rmdir(currentFilepath);
6106
6137
  break
6107
6138
  case 'mkdir':
6139
+ // Don't mkdir through a symlinked leading path: a parent component that
6140
+ // is a symlink could otherwise redirect the directory creation outside
6141
+ // the working tree. git applies the same check (it does not follow
6142
+ // symlinks here) — see checkout.
6143
+ await assertNoSymlinkInLeadingPath(fs, dir, op.filepath);
6108
6144
  await fs.mkdir(currentFilepath);
6109
6145
  break
6110
6146
  case 'rm':
@@ -6117,6 +6153,10 @@ async function applyTreeChanges({
6117
6153
  currentFilepath.startsWith(removedDir)
6118
6154
  )
6119
6155
  ) {
6156
+ // Don't write through a symlinked leading path (see checkout): a
6157
+ // parent component that is a symlink could redirect the write to a
6158
+ // location outside the working tree.
6159
+ await assertNoSymlinkInLeadingPath(fs, dir, op.filepath);
6120
6160
  const { object } = await _readObject({
6121
6161
  fs,
6122
6162
  cache: {},