mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-18 13:37:23 +02:00
Merge pull request #2227 from time-attack/fix/basic-ftp-cve-2026-39983
fix(deps): pin basic-ftp to 5.3.1 to fully clear CVE-2026-39983 and 3 sibling HIGH advisories
This commit is contained in:
@@ -22,6 +22,9 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"basic-ftp": "5.3.1",
|
||||||
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.117", "", { "dependencies": { "@anthropic-ai/sdk": "^0.81.0", "@modelcontextprotocol/sdk": "^1.29.0" }, "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.117", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.117" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-pVBss1Vu0w87nKCBhWtjMggSgCh6GVUtdRmuE58ZvXv0E2q0JcnUCQHehmn92BAW0+VCwPY8q/k7uKWkgwz/gA=="],
|
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.117", "", { "dependencies": { "@anthropic-ai/sdk": "^0.81.0", "@modelcontextprotocol/sdk": "^1.29.0" }, "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.2.117", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.2.117", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.2.117", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.2.117" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-pVBss1Vu0w87nKCBhWtjMggSgCh6GVUtdRmuE58ZvXv0E2q0JcnUCQHehmn92BAW0+VCwPY8q/k7uKWkgwz/gA=="],
|
||||||
|
|
||||||
@@ -201,7 +204,7 @@
|
|||||||
|
|
||||||
"bare-url": ["bare-url@2.4.0", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA=="],
|
"bare-url": ["bare-url@2.4.0", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA=="],
|
||||||
|
|
||||||
"basic-ftp": ["basic-ftp@5.2.0", "", {}, "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw=="],
|
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
|
||||||
|
|
||||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -76,5 +76,8 @@
|
|||||||
"@anthropic-ai/sdk": "^0.78.0",
|
"@anthropic-ai/sdk": "^0.78.0",
|
||||||
"xterm": "5",
|
"xterm": "5",
|
||||||
"xterm-addon-fit": "^0.8.0"
|
"xterm-addon-fit": "^0.8.0"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"basic-ftp": "5.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { describe, test, expect } from 'bun:test';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
const ROOT = join(import.meta.dir, '..');
|
||||||
|
|
||||||
|
// Security regression guard for the basic-ftp transitive dependency.
|
||||||
|
//
|
||||||
|
// basic-ftp reaches the tree only transitively:
|
||||||
|
// puppeteer-core > @puppeteer/browsers > proxy-agent > pac-proxy-agent > get-uri > basic-ftp
|
||||||
|
//
|
||||||
|
// Versions <= 5.3.0 carry four HIGH advisories, all fixed in 5.3.1:
|
||||||
|
// - GHSA-chqc-8p9q-pq6q CVE-2026-39983 FTP command injection via CRLF (fixed 5.2.1)
|
||||||
|
// - GHSA-6v7q-wjvx-w8wg incomplete CRLF protection, USER/PASS + MKD bypass (fixed 5.2.2)
|
||||||
|
// - GHSA-rpmf-866q-6p89 DoS via unbounded multiline control-response buffering
|
||||||
|
// - GHSA-rp42-5vxx-qpwr DoS via unbounded memory in Client.list()
|
||||||
|
//
|
||||||
|
// The fix is a bun `overrides` pin (not a phantom direct dependency): overriding
|
||||||
|
// forces EVERY basic-ftp in the tree to the safe version, including get-uri's
|
||||||
|
// nested copy. A direct-dependency bump leaves that nested copy behind — which is
|
||||||
|
// exactly the failure mode this test is here to catch. See the CVE-2026-39983
|
||||||
|
// upgrade fix for the full rationale.
|
||||||
|
const MIN_SAFE = '5.3.1';
|
||||||
|
|
||||||
|
function cmpSemver(a: string, b: string): number {
|
||||||
|
const pa = a.split('.').map(Number);
|
||||||
|
const pb = b.split('.').map(Number);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('basic-ftp security pin (CVE-2026-39983 and siblings)', () => {
|
||||||
|
test('package.json overrides basic-ftp to a safe version', () => {
|
||||||
|
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
||||||
|
const pin = pkg.overrides?.['basic-ftp'];
|
||||||
|
expect(pin, 'package.json overrides.basic-ftp must exist').toBeDefined();
|
||||||
|
const pinned = String(pin).replace(/^[\^~]/, '');
|
||||||
|
expect(
|
||||||
|
cmpSemver(pinned, MIN_SAFE) >= 0,
|
||||||
|
`overrides.basic-ftp is "${pin}" but must be >= ${MIN_SAFE}`,
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no basic-ftp entry in bun.lock resolves below the safe version', () => {
|
||||||
|
const lock = readFileSync(join(ROOT, 'bun.lock'), 'utf-8');
|
||||||
|
// Match every resolved basic-ftp specifier, including nested paths like
|
||||||
|
// "get-uri/basic-ftp" that a direct-dependency bump would leave vulnerable.
|
||||||
|
const versions = [...lock.matchAll(/basic-ftp@(\d+\.\d+\.\d+)/g)].map(m => m[1]);
|
||||||
|
expect(versions.length, 'expected at least one basic-ftp entry in bun.lock').toBeGreaterThan(0);
|
||||||
|
const vulnerable = versions.filter(v => cmpSemver(v, MIN_SAFE) < 0);
|
||||||
|
expect(
|
||||||
|
vulnerable,
|
||||||
|
`bun.lock still resolves vulnerable basic-ftp version(s): ${vulnerable.join(', ')}`,
|
||||||
|
).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user