Skip to content

Commit de35c6e

Browse files
committed
fix(editor): execute oxc.path.server in win32 with shell, when its the node_module binary
1 parent d52cba6 commit de35c6e

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

editors/vscode/client/PathValidator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
* The check for malicious characters is not needed, but it's an additional layer of security.
1010
* When using `shell: true` in `LanguageClient.ServerOptions`, it can be vulnerable to command injection.
11-
* Even though we are not using `shell: true`, it's a good practice to validate the input.
11+
* We are using `shell: true` only on Windows when the paths ends with `node_modules/.bin/oxc_language_server`.
1212
*/
1313
export function validateSafeBinaryPath(binary: string): boolean {
1414
// Check for path traversal (including Windows variants)

editors/vscode/client/extension.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,7 @@ export async function activate(context: ExtensionContext) {
137137
outputChannel,
138138
);
139139

140-
async function findBinary(): Promise<string> {
141-
let bin = configService.getUserServerBinPath();
140+
async function findBinary(bin: string | undefined): Promise<string> {
142141
if (workspace.isTrusted && bin) {
143142
try {
144143
await fsPromises.access(bin);
@@ -155,10 +154,20 @@ export async function activate(context: ExtensionContext) {
155154
);
156155
}
157156

158-
const command = await findBinary();
157+
const userDefinedBinary = configService.getUserServerBinPath();
158+
const command = await findBinary(userDefinedBinary);
159159
const run: Executable = {
160160
command: command!,
161161
options: {
162+
// On Windows we need to run the binary in a shell to be able to execute the shell npm bin script.
163+
// This is only needed when the user explicitly configures the binary to point to the npm bin script.
164+
// The extension is shipped with the `.exe` file, we don't need to run it in a shell.
165+
// Searching for the right `.exe` file inside `node_modules/` is not reliable as it depends on
166+
// the package manager used (npm, yarn, pnpm, etc) and the package version.
167+
// The npm bin script is a shell script that points to the actual binary.
168+
// Security: We validated the userDefinedBinary in `configService.getUserServerBinPath()`.
169+
shell: process.platform === 'win32' && command === userDefinedBinary &&
170+
userDefinedBinary?.endsWith('node_modules\\.bin\\oxc_language_server'),
162171
env: {
163172
...process.env,
164173
RUST_LOG: process.env.RUST_LOG || 'info',

editors/vscode/tests/ConfigService.spec.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { strictEqual } from 'assert';
22
import { workspace } from 'vscode';
33
import { ConfigService } from '../client/ConfigService.js';
44
import { testSingleFolderMode, WORKSPACE_FOLDER } from './test-helpers.js';
5+
import path = require('path/win32');
56

67
const conf = workspace.getConfiguration('oxc');
78

@@ -18,20 +19,55 @@ suite('ConfigService', () => {
1819
await Promise.all(keys.map(key => conf.update(key, undefined)));
1920
});
2021

21-
testSingleFolderMode('resolves relative server path with workspace folder', async () => {
22-
const service = new ConfigService();
23-
const nonDefinedServerPath = service.getUserServerBinPath();
22+
const getWorkspaceFolderPlatformSafe = () => {
23+
let workspace_path = WORKSPACE_FOLDER.uri.path;
24+
if (process.platform === 'win32') {
25+
workspace_path = workspace_path.replaceAll('/', '\\');
26+
if (workspace_path.startsWith('\\')) {
27+
workspace_path = workspace_path.slice(1);
28+
}
29+
}
30+
return workspace_path;
31+
};
2432

25-
strictEqual(nonDefinedServerPath, undefined);
33+
suite('getUserServerBinPath', () => {
34+
testSingleFolderMode('resolves relative server path with workspace folder', async () => {
35+
const service = new ConfigService();
36+
const nonDefinedServerPath = service.getUserServerBinPath();
2637

27-
await conf.update('path.server', '/absolute/oxc_language_server');
28-
const absoluteServerPath = service.getUserServerBinPath();
38+
strictEqual(nonDefinedServerPath, undefined);
2939

30-
strictEqual(absoluteServerPath, '/absolute/oxc_language_server');
40+
await conf.update('path.server', '/absolute/oxc_language_server');
41+
const absoluteServerPath = service.getUserServerBinPath();
3142

32-
await conf.update('path.server', './relative/oxc_language_server');
33-
const relativeServerPath = service.getUserServerBinPath();
43+
strictEqual(absoluteServerPath, '/absolute/oxc_language_server');
3444

35-
strictEqual(relativeServerPath, WORKSPACE_FOLDER.uri.path + '/relative/oxc_language_server');
45+
await conf.update('path.server', './relative/oxc_language_server');
46+
const relativeServerPath = service.getUserServerBinPath();
47+
48+
let workspace_path = getWorkspaceFolderPlatformSafe();
49+
strictEqual(relativeServerPath, `${workspace_path}/relative/oxc_language_server`);
50+
});
51+
52+
testSingleFolderMode('returns undefined for unsafe server path', async () => {
53+
const service = new ConfigService();
54+
await conf.update('path.server', '../unsafe/oxc_language_server');
55+
const unsafeServerPath = service.getUserServerBinPath();
56+
57+
strictEqual(unsafeServerPath, undefined);
58+
});
59+
60+
testSingleFolderMode('returns backslashes path on Windows', async () => {
61+
if (process.platform !== 'win32') {
62+
return;
63+
}
64+
const service = new ConfigService();
65+
await conf.update('path.server', './relative/oxc_language_server');
66+
const relativeServerPath = service.getUserServerBinPath();
67+
let workspace_path = getWorkspaceFolderPlatformSafe();
68+
69+
strictEqual(workspace_path[1], ':', 'The test workspace folder must be an absolute path with a drive letter on Windows');
70+
strictEqual(relativeServerPath, `${workspace_path}\\relative\\oxc_language_server`);
71+
});
3672
});
3773
});

0 commit comments

Comments
 (0)