diff --git a/node_modules/tar/dist/commonjs/pack.js b/node_modules/tar/dist/commonjs/pack.js index 450b0011a5f0b..67de8272c71f5 100644 --- a/node_modules/tar/dist/commonjs/pack.js +++ b/node_modules/tar/dist/commonjs/pack.js @@ -91,6 +91,7 @@ const ONDRAIN = Symbol('ondrain'); const path_1 = __importDefault(require("path")); const normalize_windows_path_js_1 = require("./normalize-windows-path.js"); class Pack extends minipass_1.Minipass { + sync = false; opt; cwd; maxReadSize; @@ -279,6 +280,17 @@ class Pack extends minipass_1.Minipass { if (!this.filter(job.path, stat)) { job.ignore = true; } + else if (stat.isFile() && + stat.nlink > 1 && + job === this[CURRENT] && + !this.linkCache.get(`${stat.dev}:${stat.ino}`) && + !this.sync) { + // if it's not filtered, and it's a new File entry, + // jump the queue in case any pending Link entries are about + // to try to link to it. This prevents a hardlink from coming ahead + // of its target in the archive. + this[PROCESSJOB](job); + } this[PROCESS](); } [READDIR](job) { diff --git a/node_modules/tar/dist/commonjs/unpack.js b/node_modules/tar/dist/commonjs/unpack.js index dea24ed0a25b5..73ad5f9be5540 100644 --- a/node_modules/tar/dist/commonjs/unpack.js +++ b/node_modules/tar/dist/commonjs/unpack.js @@ -253,28 +253,50 @@ class Unpack extends parse_js_1.Parser { // return false if we need to skip this file // return true if the field was successfully sanitized [STRIPABSOLUTEPATH](entry, field) { - const path = entry[field]; - if (!path || this.preservePaths) + const p = entry[field]; + const { type } = entry; + if (!p || this.preservePaths) return true; - const parts = path.split('/'); + const parts = p.split('/'); if (parts.includes('..') || /* c8 ignore next */ (isWindows && /^[a-z]:\.\.$/i.test(parts[0] ?? ''))) { - this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, { - entry, - [field]: path, - }); - // not ok! - return false; + // For linkpath, check if the resolved path escapes cwd rather than + // just rejecting any path with '..' - relative symlinks like + // '../sibling/file' are valid if they resolve within the cwd. + // For paths, they just simply may not ever use .. at all. + if (field === 'path' || type === 'Link') { + this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, { + entry, + [field]: p, + }); + // not ok! + return false; + } + else { + // Resolve linkpath relative to the entry's directory. + // `path.posix` is safe to use because we're operating on + // tar paths, not a filesystem. + const entryDir = node_path_1.default.posix.dirname(entry.path); + const resolved = node_path_1.default.posix.normalize(node_path_1.default.posix.join(entryDir, p)); + // If the resolved path escapes (starts with ..), reject it + if (resolved.startsWith('../') || resolved === '..') { + this.warn('TAR_ENTRY_ERROR', `${field} escapes extraction directory`, { + entry, + [field]: p, + }); + return false; + } + } } // strip off the root - const [root, stripped] = (0, strip_absolute_path_js_1.stripAbsolutePath)(path); + const [root, stripped] = (0, strip_absolute_path_js_1.stripAbsolutePath)(p); if (root) { // ok, but triggers warning about stripping root entry[field] = String(stripped); this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute ${field}`, { entry, - [field]: path, + [field]: p, }); } return true; diff --git a/node_modules/tar/dist/esm/pack.js b/node_modules/tar/dist/esm/pack.js index 36dc28f8077d4..95d9342ac34e6 100644 --- a/node_modules/tar/dist/esm/pack.js +++ b/node_modules/tar/dist/esm/pack.js @@ -51,6 +51,7 @@ const ONDRAIN = Symbol('ondrain'); import path from 'path'; import { normalizeWindowsPath } from './normalize-windows-path.js'; export class Pack extends Minipass { + sync = false; opt; cwd; maxReadSize; @@ -239,6 +240,17 @@ export class Pack extends Minipass { if (!this.filter(job.path, stat)) { job.ignore = true; } + else if (stat.isFile() && + stat.nlink > 1 && + job === this[CURRENT] && + !this.linkCache.get(`${stat.dev}:${stat.ino}`) && + !this.sync) { + // if it's not filtered, and it's a new File entry, + // jump the queue in case any pending Link entries are about + // to try to link to it. This prevents a hardlink from coming ahead + // of its target in the archive. + this[PROCESSJOB](job); + } this[PROCESS](); } [READDIR](job) { diff --git a/node_modules/tar/dist/esm/unpack.js b/node_modules/tar/dist/esm/unpack.js index 23274872f382d..3a9aa807798a7 100644 --- a/node_modules/tar/dist/esm/unpack.js +++ b/node_modules/tar/dist/esm/unpack.js @@ -214,28 +214,50 @@ export class Unpack extends Parser { // return false if we need to skip this file // return true if the field was successfully sanitized [STRIPABSOLUTEPATH](entry, field) { - const path = entry[field]; - if (!path || this.preservePaths) + const p = entry[field]; + const { type } = entry; + if (!p || this.preservePaths) return true; - const parts = path.split('/'); + const parts = p.split('/'); if (parts.includes('..') || /* c8 ignore next */ (isWindows && /^[a-z]:\.\.$/i.test(parts[0] ?? ''))) { - this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, { - entry, - [field]: path, - }); - // not ok! - return false; + // For linkpath, check if the resolved path escapes cwd rather than + // just rejecting any path with '..' - relative symlinks like + // '../sibling/file' are valid if they resolve within the cwd. + // For paths, they just simply may not ever use .. at all. + if (field === 'path' || type === 'Link') { + this.warn('TAR_ENTRY_ERROR', `${field} contains '..'`, { + entry, + [field]: p, + }); + // not ok! + return false; + } + else { + // Resolve linkpath relative to the entry's directory. + // `path.posix` is safe to use because we're operating on + // tar paths, not a filesystem. + const entryDir = path.posix.dirname(entry.path); + const resolved = path.posix.normalize(path.posix.join(entryDir, p)); + // If the resolved path escapes (starts with ..), reject it + if (resolved.startsWith('../') || resolved === '..') { + this.warn('TAR_ENTRY_ERROR', `${field} escapes extraction directory`, { + entry, + [field]: p, + }); + return false; + } + } } // strip off the root - const [root, stripped] = stripAbsolutePath(path); + const [root, stripped] = stripAbsolutePath(p); if (root) { // ok, but triggers warning about stripping root entry[field] = String(stripped); this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute ${field}`, { entry, - [field]: path, + [field]: p, }); } return true; diff --git a/node_modules/tar/package.json b/node_modules/tar/package.json index 35c8a5525bcab..397764155b514 100644 --- a/node_modules/tar/package.json +++ b/node_modules/tar/package.json @@ -2,7 +2,7 @@ "author": "Isaac Z. Schlueter", "name": "tar", "description": "tar for node", - "version": "7.5.4", + "version": "7.5.7", "repository": { "type": "git", "url": "https://github.com/isaacs/node-tar.git" diff --git a/package-lock.json b/package-lock.json index 648d580e44530..79f45aef9b169 100644 --- a/package-lock.json +++ b/package-lock.json @@ -144,7 +144,7 @@ "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.0", "supports-color": "^10.2.2", - "tar": "^7.5.4", + "tar": "^7.5.7", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", @@ -13456,9 +13456,9 @@ } }, "node_modules/tar": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.4.tgz", - "integrity": "sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { diff --git a/package.json b/package.json index 7286f9f0a7fdc..e6012ef90a989 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "spdx-expression-parse": "^4.0.0", "ssri": "^13.0.0", "supports-color": "^10.2.2", - "tar": "^7.5.4", + "tar": "^7.5.7", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0",