fix: Remove gulp-decompress and align with upstream untar - #788
Conversation
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>
📝 WalkthroughWalkthroughThe CLI and REH build pipelines replace Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
code/package-lock.jsonis 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.jsoncode/build/gulpfile.cli.tscode/build/gulpfile.reh.tscode/build/lib/typings/gulp-decompress.d.tscode/package.jsonrebase.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
| .pipe(gunzip()) | ||
| .pipe(untar()) |
There was a problem hiding this comment.
🩺 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/libRepository: 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 || trueRepository: 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);
JSRepository: 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 || trueRepository: 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/buildRepository: 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));
})();
JSRepository: 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:
- 1: GHSA-34x7-hfp2-rc4v
- 2: GHSA-8qq5-rm4j-mr97
- 3: https://github.com/isaacs/node-tar
- 4: GHSA-9ppj-qmqm-q256
- 5: GHSA-83g3-92jg-28cx
- 6: GHSA-3jfq-g458-7qm9
- 7: isaacs/node-tar@v7.5.11...v7.5.22
- 8: https://nvd.nist.gov/vuln/detail/CVE-2026-24842
🌐 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:
- 1: https://gulpjs.com/docs/en/api/dest/
- 2: https://github.com/gulpjs/gulp/blob/ae1ef092e6e3853760224f1b7acf5876f81c03f0/docs/API.md
- 3: Incorrect relative path in gulp.dest() > vinyl-fs gulpjs/gulp#699
- 4: http://github.com/wearefractal/vinyl-fs
- 5: https://stackoverflow.com/questions/55507902/how-to-manipulate-by-relative-output-path-of-vinyl-file-inside-gulp-pipeline
- 6: https://github.com/gulpjs/vinyl
- 7: https://github.com/gulpjs/vinyl-fs/
🏁 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.jsonRepository: 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 }));
}
JSRepository: 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"; fiRepository: 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)));
}
JSRepository: 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 }));
}
JSRepository: 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 forcode/build/gulpfile.cli.ts#L88-L89,code/build/gulpfile.reh.ts#L322, andcode/build/gulpfile.reh.ts#L331. - In the CLI pipeline, forward
gunzip()anduntar()errors tocallback; the listener ongulp.dest()does not receive intermediate stream errors. - In both REH pipelines, forward
gunzip()errors.gulp-flatmapforwardsuntar()errors, but the innergunzip()error remains unhandled.
📍 Affects 2 files
code/build/gulpfile.cli.ts#L88-L89(this comment)code/build/gulpfile.reh.ts#L322-L322code/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()))) |
There was a problem hiding this comment.
🩺 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 -200Repository: 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/nullRepository: 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);
JSRepository: 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.
|
Pull Request images published ✨ Editor amd64: quay.io/che-incubator-pull-requests/che-code:pr-788-amd64 |
What does this PR do?
Reverts the
gulp-decompressintroduction from #648/#666 and realigns with upstream VS Code's own solution:gunzip+untar()backed bytar.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?
git rebasewere added to the .rebase folderSummary by CodeRabbit
Bug Fixes
Chores