Skip to content

fix: Remove gulp-decompress and align with upstream untar - #788

Open
sbouchet wants to merge 1 commit into
che-incubator:mainfrom
sbouchet:fix/align-upstream-untar
Open

fix: Remove gulp-decompress and align with upstream untar#788
sbouchet wants to merge 1 commit into
che-incubator:mainfrom
sbouchet:fix/align-upstream-untar

Conversation

@sbouchet

@sbouchet sbouchet commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Reverts the gulp-decompress introduction from #648/#666 and realigns with upstream VS Code's own solution: gunzip + untar() backed by tar.Parser. This removes the vulnerable decompress@4.2.1 (CVE-2026-53486, CVSS 9.1) while keeping the tar@^7.5.22 pin for GHSA-34x7-hfp2-rc4v.

What issues does this PR fix?

https://github.com/che-incubator/che-code/security/dependabot/875
https://github.com/che-incubator/che-code/security/dependabot/1013

How to test this PR?

Does this PR contain changes that override default upstream Code-OSS behavior?

  • the PR contains changes in the code folder (you can skip it if your changes are placed in a che extension )
  • the corresponding items were added to the CHANGELOG.md file
  • rules for automatic git rebase were added to the .rebase folder

Summary by CodeRabbit

  • Bug Fixes

    • Improved archive extraction reliability across supported build environments.
    • Expanded Linux build support to include PPC64 and s390x architectures.
  • Chores

    • Simplified build configuration and archive processing.
    • Updated package and replacement configuration to reflect the revised build tooling.

Reverts the gulp-decompress introduction from che-incubator#648/che-incubator#666 and realigns
with upstream VS Code's own solution: gunzip + untar() backed by
tar.Parser. This removes the vulnerable decompress@4.2.1 (CVE-2026-53486,
CVSS 9.1) while keeping the tar@^7.5.22 pin for GHSA-34x7-hfp2-rc4v.

Signed-off-by: Stephane Bouchet <sbouchet@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sbouchet
sbouchet marked this pull request as ready for review August 11, 2026 21:58
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CLI and REH build pipelines replace gulp-decompress with the local gunzip and untar utilities. The gulp-decompress dependency and TypeScript declaration are removed. REH replacement configuration now passes workbenchConfig to the web file mapper and adds Linux ppc64 and s390x targets. Rebase handling no longer applies multiline package replacements or a dedicated CLI conflict rule.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: vitaliy-guliy, azatsarynnyy, rgrunber


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Rebase Rules For Upstream Changes ❌ Error The PR changes upstream files under code/, but HEAD^..HEAD has no .rebase/CHANGELOG.md change and deletes the cli/package.json rebase rules. Add the required .rebase/CHANGELOG.md entry and corresponding .rebase rules with rebase.sh conflict routing for each changed upstream file.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, uses imperative wording, and clearly describes removing gulp-decompress and aligning with upstream untar behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/build/gulpfile.cli.ts`:
- Around line 88-89: Update the extraction stream around gunzip() and untar() so
errors from both transforms propagate to the task callback instead of being
handled only after gulp.dest(). Use stream.pipeline() or a shared
error-forwarding path, and ensure the temporary directory is cleaned up when
extraction fails.
- Around line 88-89: Harden the archive extraction pipelines at
code/build/gulpfile.cli.ts:88-89 and code/build/gulpfile.reh.ts:322,331 by
making untar() reject absolute and parent-traversal entry paths before emitting
files. In the CLI pipeline, forward gunzip() and untar() errors to callback
rather than relying on gulp.dest(); in both REH pipelines, forward gunzip()
errors while retaining gulp-flatmap’s untar() error propagation.

In `@code/build/gulpfile.reh.ts`:
- Line 322: Update the flatmap pipeline around gunzip() and untar() to use an
error-propagating composition instead of nested .pipe() calls, ensuring errors
from both transforms reach flatmap(). Apply the same change to the corresponding
pipeline at the other referenced location.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 94216977-ac69-4591-9269-b39b90636beb

📥 Commits

Reviewing files that changed from the base of the PR and between 70ba2e7 and 750dbed.

⛔ Files ignored due to path filters (1)
  • code/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .rebase/replace/code/build/gulpfile.cli.ts.json
  • .rebase/replace/code/build/gulpfile.reh.ts.json
  • .rebase/replace/code/package.json.json
  • code/build/gulpfile.cli.ts
  • code/build/gulpfile.reh.ts
  • code/build/lib/typings/gulp-decompress.d.ts
  • code/package.json
  • rebase.sh
💤 Files with no reviewable changes (6)
  • .rebase/replace/code/package.json.json
  • code/build/lib/typings/gulp-decompress.d.ts
  • .rebase/replace/code/build/gulpfile.cli.ts.json
  • rebase.sh
  • code/package.json
  • .rebase/replace/code/build/gulpfile.reh.ts.json

Comment on lines +88 to +89
.pipe(gunzip())
.pipe(untar())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 \
  'pipe\(gunzip\(\)\)\.pipe\(untar\(\)\)|\.on\([^\n]*error|pipeline\(' \
  code/build/gulpfile.cli.ts \
  code/build/gulpfile.reh.ts \
  code/build/lib

Repository: che-incubator/che-code

Length of output: 20104


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- gulpfile imports and extraction task ---'
sed -n '1,110p' code/build/gulpfile.cli.ts

printf '%s\n' '--- extraction utility implementation ---'
rg -n -C 12 'function (gunzip|untar)|export (function|const) (gunzip|untar)|gunzip|untar' code/build/lib code/build

printf '%s\n' '--- destination and task stream handling ---'
rg -n -C 12 'function dest|export .*dest|gulp\.dest|taskResult\.on|streamToPromise' code/build/lib code/build/gulpfile.cli.ts

printf '%s\n' '--- package declarations for stream-related dependencies ---'
rg -n -C 3 '"(gulp|gulp-util|tar|gunzip|zlib|through2|vinyl)"' package.json code/package.json 2>/dev/null || true

Repository: che-incubator/che-code

Length of output: 44774


🏁 Script executed:

node - <<'JS'
const { PassThrough, Transform } = require('node:stream');

function probe(label, attachUpstreamHandlers) {
  const source = new PassThrough();
  const middle = new Transform({
    transform(chunk, encoding, callback) {
      callback(new Error(`${label} extraction failure`));
    }
  });
  const destination = new PassThrough();
  let destinationError = false;
  let uncaught = false;

  destination.on('error', () => { destinationError = true; });
  if (attachUpstreamHandlers) {
    source.on('error', () => {});
    middle.on('error', () => {});
  }
  source.pipe(middle).pipe(destination);
  process.once('uncaughtException', () => { uncaught = true; });
  source.end(Buffer.from('archive'));

  setImmediate(() => {
    console.log(JSON.stringify({ label, attachUpstreamHandlers, destinationError, uncaught }));
  });
}

probe('final-listener-only', false);
setTimeout(() => probe('upstream-listeners', true), 25);
JS

Repository: che-incubator/che-code

Length of output: 367


Propagate extraction errors to the task callback.

gunzip() and untar() can emit errors that the listener after gulp.dest() does not receive. Use stream.pipeline() or forward both errors to a single callback and clean up the temporary directory on failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/build/gulpfile.cli.ts` around lines 88 - 89, Update the extraction
stream around gunzip() and untar() so errors from both transforms propagate to
the task callback instead of being handled only after gulp.dest(). Use
stream.pipeline() or a shared error-forwarding path, and ensure the temporary
directory is cleaned up when extraction fails.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files 'code/build/gulpfile.cli.ts' 'code/build/gulpfile.reh.ts' 'code/build/lib/util.ts'

printf '%s\n' '--- relevant source ranges ---'
sed -n '60,105p' code/build/gulpfile.cli.ts
sed -n '290,345p' code/build/gulpfile.reh.ts
sed -n '410,490p' code/build/lib/util.ts

printf '%s\n' '--- tar-related imports and usages ---'
rg -n -C 3 'untar|gunzip|tar\.Parser|tar\.Unpack|flatmap|pipeline|destroy|on\([' \
  code/build/gulpfile.cli.ts code/build/gulpfile.reh.ts code/build/lib/util.ts

printf '%s\n' '--- package declarations ---'
rg -n -C 3 '"(tar|gulp|gulp-util|through2|flatmap-stream|zlib)"|from ["'\'']tar["'\'']|require\(["'\'']tar["'\'']' \
  package.json code package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true

Repository: che-incubator/che-code

Length of output: 6070


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- imports and exact call sites ---'
sed -n '1,55p' code/build/gulpfile.cli.ts
sed -n '1,45p' code/build/gulpfile.reh.ts
rg -n -C 4 'untar|gunzip|flatmap|fetchNodejs|acquireBuiltOpenSSL|extractAlpinefromDocker' \
  code/build/gulpfile.cli.ts code/build/gulpfile.reh.ts code/build/lib/util.ts

printf '%s\n' '--- declared dependency versions ---'
rg -n -C 3 '"tar"|"gulp"|"gulp-util"|"event-stream"|"flatmap-stream"|"through2"|"gulp-filter"|"gulp-rename"' \
  --glob 'package.json' --glob 'package-lock.json' --glob 'yarn.lock' --glob 'pnpm-lock.yaml' .

printf '%s\n' '--- repository references to the extracted paths ---'
rg -n -C 3 'entry\.path|file\.relative|gulp\.dest|rename\(' code/build

Repository: che-incubator/che-code

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const { Readable, Transform, Writable } = require('node:stream');

function probe(label, build) {
  return new Promise(resolve => {
    const events = [];
    const source = new Readable({
      read() {
        this.push('x');
        this.push(null);
      }
    });
    const middle = new Transform({
      transform(chunk, encoding, callback) { callback(null, chunk); }
    });
    const destination = new Writable({
      write(chunk, encoding, callback) { callback(); }
    });

    source.pipe(middle).pipe(destination);
    source.on('error', () => events.push('source-error'));
    middle.on('error', () => events.push('middle-error'));
    destination.on('error', () => events.push('destination-error'));
    destination.on('finish', () => {
      setImmediate(() => resolve([label, events]));
    });

    build(source, middle, destination);
  });
}

(async () => {
  const [result] = await Promise.all([
    probe('middle emits error after pipe', (source, middle) => {
      setImmediate(() => middle.emit('error', new Error('middle failure')));
    }),
  ]);
  console.log(JSON.stringify(result));
})();
JS

Repository: che-incubator/che-code

Length of output: 211


🌐 Web query:

node-tar v7.5.22 Parser entry.path path traversal validation versus Unpack source

💡 Result:

In node-tar, the path traversal protections rely on validating entry paths and link targets against the extraction root [1][2][3]. However, vulnerabilities have historically stemmed from mismatches between how these paths are validated and how they are eventually resolved or used during file system operations [1][4][5]. Key differences between entry.path validation and Unpack source behavior include: 1. Path Resolution Semantics: Vulnerabilities often arise because security checks perform path resolution (e.g., using path.resolve or normalize) based on different baselines or context than the actual file system operation [1][5]. For example, a validation check might treat a path as safe because it appears relative to an entry's parent directory, while the subsequent extraction operation resolves it against the entire extraction root (this.cwd), allowing it to escape the intended directory [1]. 2. Hardlink and Symlink Target Handling: Even when entry.path itself is validated, the linkpath (for hardlinks and symlinks) may not be correctly constrained [2][5]. Attackers have historically exploited this by providing linkpaths that resolve to absolute paths outside the extraction directory [2]. Because the library's security checks were sometimes string-based or failed to account for symlink chains on disk, the validation could be bypassed, leading to arbitrary file read or overwrite [5]. 3. Normalization and Stripping Inconsistencies: Issues have been identified where paths are validated before they are "stripped" (e.g., removing absolute roots or drive letters) [4][6]. In such cases, the security check runs on the original, un-sanitized input, while the extraction operation proceeds with a modified, unsafe path that was incorrectly permitted by the initial check [4]. In versions like 7.5.22, the library has undergone numerous fixes to align these validation and resolution logics, but historically, the complexity of correctly handling absolute paths, drive-relative paths (on Windows), and symlink-based escapes across varying Node.js environments has made achieving perfect isolation difficult [7][4][8][5]. Users are strongly advised to keep node-tar updated to the latest version and, when processing untrusted archives, to use a filter function to explicitly reject or validate suspicious entries [6][3].

Citations:


🌐 Web query:

gulp.dest vinyl file path relative path traversal behavior vinyl-fs dest

💡 Result:

In gulp and vinyl-fs, the gulp.dest method determines the output file path by joining the destination directory with the file's relative path [1][2]. The relative path is calculated as the portion of the file's path that follows its base directory (file.relative = path.relative(file.base, file.path)) [3][2]. Regarding path traversal behavior, vinyl-fs does not explicitly sanitize the relative path to prevent directory traversal attacks (e.g., sequences like../) [1][4]. If a Vinyl object enters the stream with a relative path containing such segments, gulp.dest will resolve that path relative to the destination directory [2][5]. This can result in files being written outside the intended destination directory if the relative path resolves to a location above it [1][2]. Because gulp.dest relies on the integrity of the Vinyl object's properties (specifically path, base, and relative), developers should ensure that any user-controlled input or untrusted file paths are validated before they are processed as Vinyl objects [6]. If a source provides malicious path information, the destination stream will treat it as legitimate and perform the write operation at the resolved location on the file system [4][7]. In summary, the design of vinyl-fs assumes that the file metadata (base and path) is trustworthy [6]. To mitigate potential path traversal, you should sanitize or validate the file paths in the pipeline before they reach gulp.dest if the source of those files is not fully trusted [6][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- exact locked tar package ---'
python3 - <<'PY'
import json
for filename in ('code/package-lock.json', 'code/build/package-lock.json'):
    try:
        data = json.load(open(filename))
    except FileNotFoundError:
        continue
    for key, value in data.get('packages', {}).items():
        if key.endswith('/node_modules/tar') or key == 'node_modules/tar':
            print(filename, key, value.get('version'), value.get('resolved'), value.get('integrity'))
PY

printf '%s\n' '--- focused gulp facade and package metadata ---'
rg -n -C 8 'function (gulp|gunzip|flatmap|filter|rename)|const (gulp|gunzip|flatmap|filter|rename)|export .*gulp|gulp-dest|vinyl-fs' \
  code/build/lib/gulp code/build/package.json code/build/package-lock.json

Repository: che-incubator/che-code

Length of output: 5057


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import json
data = json.load(open('code/package-lock.json'))
for key, value in data.get('packages', {}).items():
    if key in ('node_modules/gulp', 'node_modules/vinyl-fs', 'node_modules/vinyl'):
        print(key, value.get('version'), value.get('resolved'))
PY

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL https://raw.githubusercontent.com/isaacs/node-tar/v7.5.22/src/parse.ts \
  -o "$tmp/parse.ts"
curl -fsSL https://raw.githubusercontent.com/isaacs/node-tar/v7.5.22/src/read-entry.ts \
  -o "$tmp/read-entry.ts"
curl -fsSL https://raw.githubusercontent.com/isaacs/node-tar/v7.5.22/src/unpack.ts \
  -o "$tmp/unpack.ts"

printf '%s\n' '--- tar parser path handling ---'
rg -n -C 5 'new ReadEntry|entry\.path|path =|normalize|strip|preservePaths|filter' \
  "$tmp/parse.ts" "$tmp/read-entry.ts" "$tmp/unpack.ts"

printf '%s\n' '--- vinyl-fs destination implementation ---'
curl -fsSL https://raw.githubusercontent.com/gulpjs/vinyl-fs/v4.0.2/lib/dest/index.js \
  -o "$tmp/dest.js"
rg -n -C 7 'file\.path|file\.relative|path\.join|path\.resolve|cwd|base' "$tmp/dest.js"

Repository: che-incubator/che-code

Length of output: 26830


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const path = require('node:path');

const cases = [
  'safe/file',
  '../outside',
  '../../outside',
  '/absolute/outside',
  'safe/../../outside',
  'safe\\..\\outside',
];

const base = '/work/repo';
const destination = '/work/repo/cli/openssl';

for (const entryPath of cases) {
  // This is the path relationship used by Vinyl-style file metadata.
  const filePath = entryPath;
  const relative = path.relative(base, filePath);
  const output = path.resolve(destination, relative);
  console.log(JSON.stringify({ entryPath, relative, output }));
}
JS

Repository: che-incubator/che-code

Length of output: 868


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- custom VinylFile implementation ---'
sed -n '300,445p' code/build/lib/util.ts

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL https://raw.githubusercontent.com/gulpjs/vinyl/v3.0.0/index.js \
  -o "$tmp/vinyl.js"
curl -fsSL https://raw.githubusercontent.com/gulpjs/vinyl-fs/v3.0.3/lib/dest/index.js \
  -o "$tmp/dest.js"
curl -fsSL https://raw.githubusercontent.com/gulpjs/vinyl-fs/v3.0.3/lib/prepare.js \
  -o "$tmp/prepare.js" || true

printf '%s\n' '--- Vinyl path and relative-property implementation ---'
rg -n -C 8 'constructor|path|relative|base|cwd' "$tmp/vinyl.js"

printf '%s\n' '--- vinyl-fs@3.0.3 destination implementation ---'
cat "$tmp/dest.js"
printf '%s\n' '--- vinyl-fs preparation implementation ---'
if test -f "$tmp/prepare.js"; then cat "$tmp/prepare.js"; fi

Repository: che-incubator/che-code

Length of output: 12656


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const path = require('node:path');

function vinylMetadata(entryPath) {
  // Vinyl resolves its path and computes relative from the resolved base.
  const absolutePath = path.resolve(entryPath);
  const base = path.dirname(absolutePath);
  const relative = path.relative(base, absolutePath);
  const destination = path.resolve('/work/repo/cli/openssl', relative);
  return { entryPath, absolutePath, base, relative, destination };
}

for (const entryPath of [
  'safe/file',
  '../outside',
  '../../outside',
  '/absolute/outside',
  'safe/../../outside',
]) {
  console.log(JSON.stringify(vinylMetadata(entryPath)));
}
JS

Repository: che-incubator/che-code

Length of output: 953


🏁 Script executed:

#!/bin/bash
set -eu

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

for url in \
  https://raw.githubusercontent.com/gulpjs/vinyl-fs/v3.0.3/lib/prepare.js \
  https://raw.githubusercontent.com/gulpjs/vinyl-fs/v3.0.3/lib/dest/prepare.js \
  https://raw.githubusercontent.com/gulpjs/vinyl-fs/3.0.3/lib/prepare.js \
  https://raw.githubusercontent.com/gulpjs/vinyl-fs/3.0.3/lib/dest/prepare.js
do
  target="$tmp/$(echo "$url" | tr '/:' '__')"
  if curl -fsSL "$url" -o "$target"; then
    printf '%s\n' "--- $url ---"
    cat "$target"
  fi
done

printf '%s\n' '--- package file list from npm tarball ---'
curl -fsSL https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz \
  | tar -tzf - | rg 'prepare|dest|write-contents|options'

Repository: che-incubator/che-code

Length of output: 2173


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- REH pipeline callers ---'
rg -n -C 8 'getNode|nodejsArtifact|downloadNode|fetchNodejs|node\.exe|patchElfLoadAlign' \
  code/build/gulpfile.reh.ts code/build

printf '%s\n' '--- exact flatmap package version ---'
python3 - <<'PY'
import json
data = json.load(open('code/package-lock.json'))
for key, value in data.get('packages', {}).items():
    if key in ('node_modules/gulp-flatmap', 'node_modules/flatmap-stream', 'node_modules/event-stream'):
        print(key, value.get('version'), value.get('resolved'))
PY

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL https://registry.npmjs.org/gulp-flatmap/-/gulp-flatmap-1.0.2.tgz \
  | tar -xzf - -C "$tmp"
printf '%s\n' '--- gulp-flatmap source ---'
find "$tmp" -type f -maxdepth 4 -print -exec sed -n '1,240p' {} \;

printf '%s\n' '--- event-stream through source ---'
curl -fsSL https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz \
  | tar -xzOf - package/index.js | sed -n '1,260p'

Repository: che-incubator/che-code

Length of output: 43903


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- nodejs() call sites and output sinks ---'
rg -n -C 12 'nodejs\(' code/build/gulpfile.reh.ts
rg -n -C 12 'nodejs\)|nodejsStream|downloaded|setExecutableBit' code/build/gulpfile.reh.ts

printf '%s\n' '--- package-task and stream-to-destination ranges ---'
sed -n '340,430p' code/build/gulpfile.reh.ts
rg -n -C 5 'gulp\.dest|streamToPromise|promisify|\.on\([' code/build/gulpfile.reh.ts | head -240

printf '%s\n' '--- exact Vinyl path transformation for relative archive paths ---'
node - <<'JS'
const path = require('node:path');

const cwd = '/work/repo/code';
const outFolder = '/work/repo/cli/openssl';
const basePath = path.resolve(cwd, outFolder);

for (const entryPath of ['package/out/file', '../outside', '../../outside', '/absolute/outside']) {
  const relative = path.relative(cwd, path.normalize(entryPath));
  const writePath = path.resolve(basePath, relative);
  console.log(JSON.stringify({ entryPath, relative, writePath }));
}
JS

Repository: che-incubator/che-code

Length of output: 11599


Confine archive paths and forward all extraction errors.

tar.Parser does not reject ../ or absolute entry paths. VinylFile preserves these paths, and gulp.dest()/vfs.dest() resolves them against the destination. A crafted archive can therefore write outside the target directory.

  • Reject unsafe paths in untar() before emitting files for code/build/gulpfile.cli.ts#L88-L89, code/build/gulpfile.reh.ts#L322, and code/build/gulpfile.reh.ts#L331.
  • In the CLI pipeline, forward gunzip() and untar() errors to callback; the listener on gulp.dest() does not receive intermediate stream errors.
  • In both REH pipelines, forward gunzip() errors. gulp-flatmap forwards untar() errors, but the inner gunzip() error remains unhandled.
📍 Affects 2 files
  • code/build/gulpfile.cli.ts#L88-L89 (this comment)
  • code/build/gulpfile.reh.ts#L322-L322
  • code/build/gulpfile.reh.ts#L331-L331
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/build/gulpfile.cli.ts` around lines 88 - 89, Harden the archive
extraction pipelines at code/build/gulpfile.cli.ts:88-89 and
code/build/gulpfile.reh.ts:322,331 by making untar() reject absolute and
parent-traversal entry paths before emitting files. In the CLI pipeline, forward
gunzip() and untar() errors to callback rather than relying on gulp.dest(); in
both REH pipelines, forward gunzip() errors while retaining gulp-flatmap’s
untar() error propagation.

fetchNodejs(expectedName!, checksumSha256) :
fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 })
).pipe(decompress())
).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target context ---'
sed -n '1,45p;300,345p' code/build/gulpfile.reh.ts

printf '%s\n' '--- flatmap declarations and usages ---'
rg -n -S 'flatmap|from[[:space:]]+.*flatmap|require\(.*flatmap' --glob '!node_modules' .

printf '%s\n' '--- dependency declarations ---'
rg -n -S '"(flatmap|gulp-flatmap|through2|gunzip-maybe|untar|tar-stream|decompress)"|flatmap|gulp-flatmap' package.json code package.json yarn.lock package-lock.json pnpm-lock.yaml 2>/dev/null || true

printf '%s\n' '--- relevant tracked files ---'
git ls-files | rg '(^|/)(package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|gulpfile\.reh\.ts|.*flatmap.*)$'

Repository: che-incubator/che-code

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- package metadata ---'
find . -maxdepth 3 -type f \( -name 'package.json' -o -name 'yarn.lock' -o -name 'package-lock.json' -o -name 'pnpm-lock.yaml' \) -print

printf '%s\n' '--- exact imports and calls ---'
rg -n -C 8 'flatmap|gunzip|untar|decompress' code/build/gulpfile.reh.ts

printf '%s\n' '--- local flatmap source or documentation ---'
rg -n -S -g '*.js' -g '*.ts' -g '*.md' -g '*.json' 'flatmap|gulp-flatmap' . --glob '!node_modules' | head -200

Repository: che-incubator/che-code

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- facade implementation ---'
sed -n '1,70p' code/build/lib/gulp/facade.ts

printf '%s\n' '--- local untar and gunzip implementations ---'
rg -n -C 12 'function untar|const untar|export .*untar|function gunzip|const gunzip|export .*gunzip' code/build/lib code/build

printf '%s\n' '--- gulp-flatmap package metadata ---'
python3 - <<'PY'
import json
from pathlib import Path
p = json.loads(Path('code/package-lock.json').read_text())
for key in ('node_modules/gulp-flatmap', 'node_modules/gulp-flatmap/node_modules/through2'):
    print(key, json.dumps(p['packages'].get(key), indent=2))
PY

printf '%s\n' '--- gulp-flatmap published source ---'
curl --fail --silent --show-error https://registry.npmjs.org/gulp-flatmap/1.0.2 \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["dist"]["tarball"])' \
  | xargs -r curl --fail --silent --show-error \
  | tar -xzO --wildcards 'package/*.js' 'package/*.ts' 2>/dev/null

Repository: che-incubator/che-code

Length of output: 13616


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- untar parser error paths ---'
sed -n '440,515p' code/build/lib/util.ts

printf '%s\n' '--- gulp-flatmap error-handler behavior ---'
curl --fail --silent --show-error https://registry.npmjs.org/gulp-flatmap/1.0.2 \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["dist"]["tarball"])' \
  | xargs -r curl --fail --silent --show-error \
  | tar -xzO package/index.js \
  | nl -ba | sed -n '35,85p'

printf '%s\n' '--- standalone Node pipe error propagation probe ---'
node - <<'JS'
const { Readable, Transform } = require('node:stream');

const source = new Readable({
  read() {
    this.push(Buffer.from('input'));
    this.push(null);
    process.nextTick(() => this.emit('error', new Error('source-error')));
  }
});

const destination = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk);
  }
});

let destinationErrors = 0;
let sourceErrors = 0;
source.on('error', () => sourceErrors++);
destination.on('error', () => destinationErrors++);
source.pipe(destination);

setTimeout(() => {
  console.log(JSON.stringify({ sourceErrors, destinationErrors }));
}, 25);
JS

Repository: che-incubator/che-code

Length of output: 1290


Use an error-propagating pipeline for gunzip() and untar()

flatmap() receives untar() errors, but .pipe() does not forward gunzip() errors. Use an error-propagating pipeline at lines 322 and 331.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/build/gulpfile.reh.ts` at line 322, Update the flatmap pipeline around
gunzip() and untar() to use an error-propagating composition instead of nested
.pipe() calls, ensuring errors from both transforms reach flatmap(). Apply the
same change to the corresponding pipeline at the other referenced location.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Click here to review and test in web IDE: Contribute

@github-actions

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant