Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/add-rebase-rules/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Important:
- **Choosing add vs override for dependency version pins (e.g. CVE fixes):**
- If the dependency already exists in the upstream `package.json` (in `dependencies`, `devDependencies`, etc.) and we need a different version → use `.rebase/override/` with the correct section. This is an override of an existing upstream value.
- If the fix uses npm `overrides` (the npm feature that pins transitive dependency versions) and the override key does not exist in upstream's `overrides` section → use `.rebase/add/`. This is additive content absent from upstream.
- Prefer the JSON merge mechanism (add or override) over `.rebase/replace/` for `package.json` changes. However, `.rebase/replace/` is acceptable when the change cannot be expressed by JSON merge alone — for example, **replacing one dependency with a different package** (e.g. swapping `gulp-untar` for `gulp-decompress` — see `.rebase/replace/code/package.json.json`) or **inserting a new entry at a specific position** among existing keys.
- Prefer the JSON merge mechanism (add or override) over `.rebase/replace/` for `package.json` changes. However, `.rebase/replace/` is acceptable when the change cannot be expressed by JSON merge alone — for example, **rewriting a dependency URL format** (e.g. fixing a Git URL — see `.rebase/replace/code/extensions/emmet/package.json.json`) or **inserting a new entry at a specific position** among existing keys.
- Place each entry in the file where the dependency actually lives in upstream. For example, if `@vitest/coverage-v8` is a devDependency of `code/extensions/copilot/package.json`, its override rule belongs in `.rebase/override/code/extensions/copilot/package.json`, not in a sibling package.

4. Create or update replace rules for non-JSON files
Expand Down
14 changes: 0 additions & 14 deletions .rebase/replace/code/build/gulpfile.cli.ts.json

This file was deleted.

16 changes: 0 additions & 16 deletions .rebase/replace/code/build/gulpfile.reh.ts.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@
"from": "import product from '../product.json' with { type: 'json' };",
"by": "import product from '../product.json' with { type: 'json' };\nimport workbenchConfig from '../src/vs/code/browser/workbench/che/workbench-config.json' with { type: 'json' };"
},
{
"from": "import { gulp, rename, replace, filter, flatmap, gunzip, jsonEditor } from './lib/gulp/facade.ts';",
"by": "import { gulp, rename, replace, filter, jsonEditor } from './lib/gulp/facade.ts';\nimport decompress from 'gulp-decompress';"
},
{
"from": "import packageJson from '../package.json' with { type: 'json' };\nimport { untar } from './lib/util.ts';",
"by": "import packageJson from '../package.json' with { type: 'json' };"
},
{
"from": "\t\t\t\tfetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 })\n\t\t\t).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))\n\t\t\t\t.pipe(filter('**/node'))",
"by": "\t\t\t\tfetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 })\n\t\t\t).pipe(decompress())\n\t\t\t\t.pipe(filter('**/node'))"
},
{
"from": "\t\t\t\tfetchNodejs(expectedName!, checksumSha256)\n\t\t\t\t\t.pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))\n\t\t\t\t\t.pipe(filter('**/node'))",
"by": "\t\t\t\tfetchNodejs(expectedName!, checksumSha256)\n\t\t\t\t\t.pipe(decompress())\n\t\t\t\t\t.pipe(filter('**/node'))"
},
{
"from": "{ platform: 'linux', arch: 'x64' },\n\t{ platform: 'linux', arch: 'arm64' },",
"by": "{ platform: 'linux', arch: 'x64' },\n\t{ platform: 'linux', arch: 'ppc64' },\n\t{ platform: 'linux', arch: 's390x' },\n\t{ platform: 'linux', arch: 'arm64' },"
Expand Down
6 changes: 0 additions & 6 deletions .rebase/replace/code/package.json.json

This file was deleted.

8 changes: 4 additions & 4 deletions code/build/gulpfile.cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import es from 'event-stream';
import { gulp } from './lib/gulp/facade.ts';
import decompress from 'gulp-decompress';
import { gulp, gunzip } from './lib/gulp/facade.ts';
import * as path from 'path';
import fancyLog from 'fancy-log';
import ansiColors from 'ansi-colors';
Expand All @@ -14,7 +13,7 @@ import { tmpdir } from 'os';
import { existsSync, mkdirSync, rmSync } from 'fs';
import * as task from './lib/gulp/task.ts';
import watcher from './lib/watch/index.ts';
import { debounce } from './lib/util.ts';
import { debounce, untar } from './lib/util.ts';
import { createReporter } from './lib/reporter.ts';

const root = 'cli';
Expand Down Expand Up @@ -86,7 +85,8 @@ const acquireBuiltOpenSSL = (callback: (err?: unknown) => void) => {
);

gulp.src('*.tgz', { cwd: dir })
.pipe(decompress())
.pipe(gunzip())
.pipe(untar())
Comment on lines +88 to +89

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.

.pipe(gulp.dest(`${root}/openssl`))
.on('error', callback)
.on('end', () => {
Expand Down
8 changes: 4 additions & 4 deletions code/build/gulpfile.reh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { gulp, rename, replace, filter, jsonEditor } from './lib/gulp/facade.ts';
import decompress from 'gulp-decompress';
import { gulp, rename, replace, filter, flatmap, gunzip, jsonEditor } from './lib/gulp/facade.ts';
import * as path from 'path';
import es from 'event-stream';
import * as util from './lib/util.ts';
Expand All @@ -18,6 +17,7 @@ import { getProductionDependencies } from './lib/dependencies.ts';
import { readISODate } from './lib/date.ts';
import vfs from 'vinyl-fs';
import packageJson from '../package.json' with { type: 'json' };
import { untar } from './lib/util.ts';
import File from 'vinyl';
import * as fs from 'fs';
import glob from 'glob';
Expand Down Expand Up @@ -319,7 +319,7 @@ function nodejs(platform: string, arch: string): NodeJS.ReadWriteStream | undefi
const downloaded = (nodejsArtifactFeed ?
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.

.pipe(filter('**/node'))
.pipe(util.setExecutableBit('**'))
.pipe(rename('node'));
Expand All @@ -328,7 +328,7 @@ function nodejs(platform: string, arch: string): NodeJS.ReadWriteStream | undefi
case 'alpine':
return nodejsArtifactFeed ?
fetchNodejs(expectedName!, checksumSha256)
.pipe(decompress())
.pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))
.pipe(filter('**/node'))
.pipe(util.setExecutableBit('**'))
.pipe(rename('node'))
Expand Down
24 changes: 0 additions & 24 deletions code/build/lib/typings/gulp-decompress.d.ts

This file was deleted.

Loading