Also check package name validity in InstallPackageRequest (#63401)

This commit is contained in:
Jake Bailey
2026-04-15 09:36:04 -07:00
committed by GitHub
parent c7a0ae102d
commit f1a9288c23
3 changed files with 474 additions and 0 deletions

View File

@@ -56,4 +56,24 @@ describe("unittests:: tsserver:: codeFix::", () => {
});
baselineTsserverLogs("codeFix", "install package when serialized", session);
});
it("install package rejects invalid package names", () => {
const { host, session } = setup();
// A client could craft an applyCodeActionCommand with arbitrary package names.
// The server must validate and reject names with invalid characters to prevent shell injection.
for (const packageName of ["; echo 'hello' #", "react'test", "a/b/c"]) {
session.executeCommandSeq<ts.server.protocol.ApplyCodeActionCommandRequest>({
command: ts.server.protocol.CommandTypes.ApplyCodeActionCommand,
arguments: {
command: {
type: "install package",
file: "/home/src/projects/project/src/file.ts",
packageName,
},
},
});
}
host.runPendingInstalls();
baselineTsserverLogs("codeFix", "install package rejects invalid package names", session);
});
});

View File

@@ -239,6 +239,22 @@ export abstract class TypingsInstaller {
/** @internal */
installPackage(req: InstallPackageRequest): void {
const { fileName, packageName, projectName, projectRootPath, id } = req;
const validationResult = JsTyping.validatePackageName(packageName);
if (validationResult !== JsTyping.NameValidationResult.Ok) {
const message = JsTyping.renderPackageNameValidationFailure(validationResult, packageName);
if (this.log.isEnabled()) {
this.log.writeLine(message);
}
const response: PackageInstalledResponse = {
kind: ActionPackageInstalled,
projectName,
id,
success: false,
message,
};
this.sendResponse(response);
return;
}
const cwd = forEachAncestorDirectory(getDirectoryPath(fileName), directory => {
if (this.installTypingHost.fileExists(combinePaths(directory, "package.json"))) {
return directory;

View File

@@ -0,0 +1,438 @@
Info seq [hh:mm:ss:mss] currentDirectory:: /home/src/Vscode/Projects/bin useCaseSensitiveFileNames:: false
Info seq [hh:mm:ss:mss] libs Location:: /home/src/tslibs/TS/Lib
Info seq [hh:mm:ss:mss] globalTypingsCacheLocation:: /home/src/Library/Caches/typescript
Info seq [hh:mm:ss:mss] Provided types map file "/home/src/tslibs/TS/Lib/typesMap.json" doesn't exist
Before request
//// [/home/src/projects/project/src/file.ts]
import * as os from "os";
import * as https from "https";
import * as vscode from "vscode";
//// [/home/src/projects/project/tsconfig.json]
{ }
//// [/home/src/projects/project/node_modules/vscode/index.js]
export const x = 10;
//// [/home/src/tslibs/TS/Lib/lib.d.ts]
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
declare const console: { log(msg: any): void; };
Info seq [hh:mm:ss:mss] request:
{
"command": "open",
"arguments": {
"file": "/home/src/projects/project/src/file.ts"
},
"seq": 1,
"type": "request"
}
Info seq [hh:mm:ss:mss] getConfigFileNameForFile:: File: /home/src/projects/project/src/file.ts ProjectRootPath: undefined:: Result: /home/src/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] Creating ConfiguredProject: /home/src/projects/project/tsconfig.json, currentDirectory: /home/src/projects/project
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/projects/project/tsconfig.json 2000 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Config file
Info seq [hh:mm:ss:mss] Config: /home/src/projects/project/tsconfig.json : {
"rootNames": [
"/home/src/projects/project/src/file.ts"
],
"options": {
"configFilePath": "/home/src/projects/project/tsconfig.json"
}
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "projectLoadingStart",
"body": {
"projectName": "/home/src/projects/project/tsconfig.json",
"reason": "Creating possible configured project for /home/src/projects/project/src/file.ts to open"
}
}
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project 1 undefined Config: /home/src/projects/project/tsconfig.json WatchType: Wild card directory
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project 1 undefined Config: /home/src/projects/project/tsconfig.json WatchType: Wild card directory
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /home/src/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts 500 undefined WatchType: Closed Script info
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project/src 1 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project/src 1 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/projects 0 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/projects 0 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project/node_modules 1 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project/node_modules 1 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/node_modules 1 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/node_modules 1 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project 0 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/projects/project 0 undefined Project: /home/src/projects/project/tsconfig.json WatchType: Failed Lookup Locations
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /home/src/projects/project/tsconfig.json projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms
Info seq [hh:mm:ss:mss] Project '/home/src/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (2)
/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts Text-1 "interface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };"
/home/src/projects/project/src/file.ts SVC-1-0 "import * as os from \"os\";\nimport * as https from \"https\";\nimport * as vscode from \"vscode\";\n"
../../tslibs/TS/Lib/lib.es2025.full.d.ts
Default library for target 'es2025'
src/file.ts
Matched by default include pattern '**/*'
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "projectLoadingFinish",
"body": {
"projectName": "/home/src/projects/project/tsconfig.json"
}
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "telemetry",
"body": {
"telemetryEventName": "projectInfo",
"payload": {
"projectId": "1097a5f82e8323ba7aba7567ec06402f7ad4ea74abce44ec5efd223ac77ff169",
"fileStats": {
"js": 0,
"jsSize": 0,
"jsx": 0,
"jsxSize": 0,
"ts": 1,
"tsSize": 92,
"tsx": 0,
"tsxSize": 0,
"dts": 1,
"dtsSize": 374,
"deferred": 0,
"deferredSize": 0
},
"compilerOptions": {},
"typeAcquisition": {
"enable": false,
"include": false,
"exclude": false
},
"extends": false,
"files": false,
"include": false,
"exclude": false,
"compileOnSave": false,
"configFileName": "tsconfig.json",
"projectType": "configured",
"languageServiceEnabled": true,
"version": "FakeVersion"
}
}
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "configFileDiag",
"body": {
"triggerFile": "/home/src/projects/project/src/file.ts",
"configFile": "/home/src/projects/project/tsconfig.json",
"diagnostics": []
}
}
Info seq [hh:mm:ss:mss] Project '/home/src/projects/project/tsconfig.json' (Configured)
Info seq [hh:mm:ss:mss] Files (2)
Info seq [hh:mm:ss:mss] -----------------------------------------------
Info seq [hh:mm:ss:mss] Open files:
Info seq [hh:mm:ss:mss] FileName: /home/src/projects/project/src/file.ts ProjectRootPath: undefined
Info seq [hh:mm:ss:mss] Projects: /home/src/projects/project/tsconfig.json
Info seq [hh:mm:ss:mss] response:
{
"seq": 0,
"type": "response",
"command": "open",
"request_seq": 1,
"success": true,
"performanceData": {
"updateGraphDurationMs": *
}
}
After request
//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib*
PolledWatches::
/home/src/projects/node_modules: *new*
{"pollingInterval":500}
FsWatches::
/home/src/projects: *new*
{}
/home/src/projects/project: *new*
{}
/home/src/projects/project/tsconfig.json: *new*
{}
/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts: *new*
{}
FsWatchesRecursive::
/home/src/projects/project: *new*
{}
/home/src/projects/project/node_modules: *new*
{}
/home/src/projects/project/src: *new*
{}
Projects::
/home/src/projects/project/tsconfig.json (Configured) *new*
projectStateVersion: 1
projectProgramVersion: 1
autoImportProviderHost: false
ScriptInfos::
/home/src/projects/project/src/file.ts (Open) *new*
version: SVC-1-0
containingProjects: 1
/home/src/projects/project/tsconfig.json *default*
/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts *new*
version: Text-1
containingProjects: 1
/home/src/projects/project/tsconfig.json
Before request
Info seq [hh:mm:ss:mss] request:
{
"command": "getCombinedCodeFix",
"arguments": {
"scope": {
"type": "file",
"args": {
"file": "/home/src/projects/project/src/file.ts"
}
},
"fixId": "installTypesPackage"
},
"seq": 2,
"type": "request"
}
TI:: Creating typing installer
TI:: [hh:mm:ss:mss] Global cache location '/home/src/Library/Caches/typescript', safe file path '/home/src/tslibs/TS/Lib/typingSafeList.json', types map path /home/src/tslibs/TS/Lib/typesMap.json
TI:: [hh:mm:ss:mss] Processing cache location '/home/src/Library/Caches/typescript'
TI:: [hh:mm:ss:mss] Trying to find '/home/src/Library/Caches/typescript/package.json'...
TI:: [hh:mm:ss:mss] Finished processing cache location '/home/src/Library/Caches/typescript'
TI:: [hh:mm:ss:mss] Npm config file: /home/src/Library/Caches/typescript/package.json
TI:: [hh:mm:ss:mss] Npm config file: '/home/src/Library/Caches/typescript/package.json' is missing, creating new one...
TI:: [hh:mm:ss:mss] Updating types-registry npm package...
TI:: [hh:mm:ss:mss] npm install --ignore-scripts types-registry@latest
TI:: [hh:mm:ss:mss] Updated types-registry npm package
TI:: typing installer creation complete
//// [/home/src/Library/Caches/typescript/package.json]
{ "private": true }
//// [/home/src/Library/Caches/typescript/node_modules/types-registry/index.json]
{
"entries": {
"vscode": {
"latest": "1.3.0",
"ts2.0": "1.0.0",
"ts2.1": "1.0.0",
"ts2.2": "1.2.0",
"ts2.3": "1.3.0",
"ts2.4": "1.3.0",
"ts2.5": "1.3.0",
"ts2.6": "1.3.0",
"ts2.7": "1.3.0"
}
}
}
TI:: [hh:mm:ss:mss] Sending response:
{
"kind": "event::typesRegistry",
"typesRegistry": {
"vscode": {
"latest": "1.3.0",
"ts2.0": "1.0.0",
"ts2.1": "1.0.0",
"ts2.2": "1.2.0",
"ts2.3": "1.3.0",
"ts2.4": "1.3.0",
"ts2.5": "1.3.0",
"ts2.6": "1.3.0",
"ts2.7": "1.3.0"
}
}
}
Info seq [hh:mm:ss:mss] response:
{
"response": {
"changes": [],
"commands": [
{
"type": "install package",
"file": "/home/src/projects/project/src/file.ts",
"packageName": "@types/node"
},
{
"type": "install package",
"file": "/home/src/projects/project/src/file.ts",
"packageName": "@types/node"
},
{
"type": "install package",
"file": "/home/src/projects/project/src/file.ts",
"packageName": "@types/vscode"
}
]
},
"responseRequired": true
}
After request
Before request
Info seq [hh:mm:ss:mss] request:
{
"command": "applyCodeActionCommand",
"arguments": {
"command": {
"type": "install package",
"file": "/home/src/projects/project/src/file.ts",
"packageName": "; echo 'hello' #"
}
},
"seq": 3,
"type": "request"
}
TI:: [hh:mm:ss:mss] '; echo 'hello' #':: Package name '; echo 'hello' #' contains invalid characters
TI:: [hh:mm:ss:mss] Sending response:
{
"kind": "action::packageInstalled",
"projectName": "/home/src/projects/project/tsconfig.json",
"id": 1,
"success": false,
"message": "'; echo 'hello' #':: Package name '; echo 'hello' #' contains invalid characters"
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "setTypings",
"body": {
"kind": "action::packageInstalled",
"projectName": "/home/src/projects/project/tsconfig.json",
"id": 1,
"success": false,
"message": "'; echo 'hello' #':: Package name '; echo 'hello' #' contains invalid characters"
}
}
Info seq [hh:mm:ss:mss] response:
{
"response": {},
"responseRequired": true
}
After request
Before request
Info seq [hh:mm:ss:mss] request:
{
"command": "applyCodeActionCommand",
"arguments": {
"command": {
"type": "install package",
"file": "/home/src/projects/project/src/file.ts",
"packageName": "react'test"
}
},
"seq": 4,
"type": "request"
}
TI:: [hh:mm:ss:mss] 'react'test':: Package name 'react'test' contains invalid characters
TI:: [hh:mm:ss:mss] Sending response:
{
"kind": "action::packageInstalled",
"projectName": "/home/src/projects/project/tsconfig.json",
"id": 2,
"success": false,
"message": "'react'test':: Package name 'react'test' contains invalid characters"
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "setTypings",
"body": {
"kind": "action::packageInstalled",
"projectName": "/home/src/projects/project/tsconfig.json",
"id": 2,
"success": false,
"message": "'react'test':: Package name 'react'test' contains invalid characters"
}
}
Info seq [hh:mm:ss:mss] response:
{
"response": {},
"responseRequired": true
}
After request
Before request
Info seq [hh:mm:ss:mss] request:
{
"command": "applyCodeActionCommand",
"arguments": {
"command": {
"type": "install package",
"file": "/home/src/projects/project/src/file.ts",
"packageName": "a/b/c"
}
},
"seq": 5,
"type": "request"
}
TI:: [hh:mm:ss:mss] 'a/b/c':: Package name 'a/b/c' contains invalid characters
TI:: [hh:mm:ss:mss] Sending response:
{
"kind": "action::packageInstalled",
"projectName": "/home/src/projects/project/tsconfig.json",
"id": 3,
"success": false,
"message": "'a/b/c':: Package name 'a/b/c' contains invalid characters"
}
Info seq [hh:mm:ss:mss] event:
{
"seq": 0,
"type": "event",
"event": "setTypings",
"body": {
"kind": "action::packageInstalled",
"projectName": "/home/src/projects/project/tsconfig.json",
"id": 3,
"success": false,
"message": "'a/b/c':: Package name 'a/b/c' contains invalid characters"
}
}
Info seq [hh:mm:ss:mss] response:
{
"response": {},
"responseRequired": true
}
After request
Before running PendingInstalls callback:: count: 0
After running PendingInstalls callback:: count: 0