Skip to content

Commit 1ac4671

Browse files
committed
src/config: add Configuration class to encapsulate the extension settings
Configuration's get returns the 'go' section of the vscode settings. This wraps the vscode.WorkspaceConfiguration object that would be returned by vscode.workspace.getConfiguration, but this prevents it from returning values from the workspace/workspaceFolder level settings if the queried key is security-sensitive - e.g. use of workspace level settings from the untrusted repository may result in arbitrary binary execution. This CL does not use the new Configuration from the extension yet. For #1094 Change-Id: Ia75389032dfaec300506b26ab839b173ff9e5557 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/283253 Trust: Hyang-Ah Hana Kim <[email protected]> Run-TryBot: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: kokoro <[email protected]> Reviewed-by: Suzy Mueller <[email protected]>
1 parent b15a5cc commit 1ac4671

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

src/config.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*---------------------------------------------------------
2+
* Copyright 2021 The Go Authors. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for license information.
4+
*--------------------------------------------------------*/
5+
6+
import vscode = require('vscode');
7+
8+
const SECURITY_SENSITIVE_CONFIG: string[] = [
9+
'goroot', 'gopath', 'toolsGopath', 'alternateTools'
10+
];
11+
12+
// Go extension configuration for a workspace.
13+
export class Configuration {
14+
constructor(
15+
private isTrustedWorkspace: boolean,
16+
private getConfiguration: typeof vscode.workspace.getConfiguration) { }
17+
18+
// returns a Proxied vscode.WorkspaceConfiguration, which prevents
19+
// from using the workspace configuration if the workspace is untrusted.
20+
public get<T>(uri?: vscode.Uri): vscode.WorkspaceConfiguration {
21+
const cfg = this.getConfiguration('go', uri);
22+
if (this.isTrustedWorkspace) {
23+
return cfg;
24+
}
25+
26+
return new WrappedConfiguration(cfg);
27+
}
28+
}
29+
30+
// wrappedConfiguration wraps vscode.WorkspaceConfiguration.
31+
class WrappedConfiguration implements vscode.WorkspaceConfiguration {
32+
constructor(private readonly _wrapped: vscode.WorkspaceConfiguration) {
33+
// set getters for direct setting access (e.g. cfg.gopath), but don't overwrite _wrapped.
34+
const desc = Object.getOwnPropertyDescriptors(_wrapped);
35+
for (const prop in desc) {
36+
if (typeof prop === 'string' && prop !== '_wrapped') {
37+
const d = desc[prop];
38+
if (SECURITY_SENSITIVE_CONFIG.includes(prop)) {
39+
const inspect = this._wrapped.inspect(prop);
40+
d.value = inspect.globalValue ?? inspect.defaultValue;
41+
}
42+
Object.defineProperty(this, prop, desc[prop]);
43+
}
44+
}
45+
}
46+
47+
public get(section: any, defaultValue?: any) {
48+
if (SECURITY_SENSITIVE_CONFIG.includes(section)) {
49+
const inspect = this._wrapped.inspect(section);
50+
return inspect.globalValue ?? defaultValue ?? inspect.defaultValue;
51+
}
52+
return this._wrapped.get(section, defaultValue);
53+
}
54+
public has(section: string) {
55+
return this._wrapped.has(section);
56+
}
57+
public inspect<T>(section: string) {
58+
return this._wrapped.inspect<T>(section);
59+
}
60+
public update(
61+
section: string, value: any, configurationTarget?: boolean | vscode.ConfigurationTarget,
62+
overrideInLanguage?: boolean): Thenable<void> {
63+
return this._wrapped.update(section, value, configurationTarget, overrideInLanguage);
64+
}
65+
}

test/integration/config.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*---------------------------------------------------------
2+
* Copyright 2021 The Go Authors. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for license information.
4+
*--------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import * as assert from 'assert';
9+
import vscode = require('vscode');
10+
import { Configuration } from '../../src/config';
11+
12+
suite('GoConfiguration Tests', () => {
13+
function check(trusted: boolean, workspaceConfig: { [key: string]: any }, key: string, expected: any) {
14+
const getConfigurationFn = (section: string) => new MockCfg(workspaceConfig);
15+
const cfg = (new Configuration(trusted, getConfigurationFn)).get();
16+
17+
const got0 = JSON.stringify(cfg.get(key));
18+
const got1 = JSON.stringify(cfg[key]);
19+
const want = JSON.stringify(expected);
20+
assert.strictEqual(got0, want, `cfg.get(${key}) = ${got0}, want ${want}`);
21+
assert.strictEqual(got1, want, `cfg[${key}] = ${got1}, want ${want}`);
22+
23+
}
24+
test('trusted workspace', () => {
25+
check(true, { goroot: 'goroot_val' }, 'goroot', 'goroot_val');
26+
check(true, { gopath: 'gopath_val' }, 'gopath', 'gopath_val');
27+
check(true, { toolsGopath: 'toolsGopath_val' }, 'toolsGopath', 'toolsGopath_val');
28+
check(true, { alternateTools: { go: 'foo' } }, 'alternateTools', { go: 'foo' });
29+
30+
check(true, { buildFlags: ['-v'] }, 'buildFlags', ['-v']);
31+
check(true, { languageServerFlags: ['-rpc.trace'] }, 'languageServerFlags', ['-rpc.trace']);
32+
});
33+
34+
test('untrusted workspace', () => {
35+
check(false, { goroot: 'goroot_val' }, 'goroot', null);
36+
check(false, { gopath: 'gopath_val' }, 'gopath', null);
37+
check(false, { toolsGopath: 'toolsGopath_val' }, 'toolsGopath', null);
38+
check(false, { alternateTools: { go: 'foo' } }, 'alternateTools', {});
39+
40+
check(false, { buildFlags: ['-v'] }, 'buildFlags', ['-v']);
41+
check(false, { languageServerFlags: ['-rpc.trace'] }, 'languageServerFlags', ['-rpc.trace']);
42+
});
43+
});
44+
45+
// tslint:disable: no-any
46+
class MockCfg implements vscode.WorkspaceConfiguration {
47+
private map: Map<string, any>;
48+
private wrapped: vscode.WorkspaceConfiguration;
49+
50+
constructor(workspaceSettings: { [key: string]: any } = {}) {
51+
// getter
52+
Object.defineProperties(this, Object.getOwnPropertyDescriptors(workspaceSettings));
53+
this.map = new Map<string, any>(Object.entries(workspaceSettings));
54+
this.wrapped = vscode.workspace.getConfiguration('go');
55+
}
56+
57+
// tslint:disable: no-any
58+
public get(section: string, defaultValue?: any): any {
59+
if (this.map.has(section)) {
60+
return this.map.get(section);
61+
}
62+
return this.wrapped.get(section, defaultValue);
63+
}
64+
65+
public has(section: string): boolean {
66+
if (this.map.has(section)) {
67+
return true;
68+
}
69+
return this.wrapped.has(section);
70+
}
71+
72+
public inspect<T>(section: string) {
73+
const i = this.wrapped.inspect<T>(section);
74+
if (this.map.has(section)) {
75+
i.workspaceValue = this.map.get(section);
76+
}
77+
return i;
78+
}
79+
80+
public update(
81+
section: string, value: any,
82+
configurationTarget?: boolean | vscode.ConfigurationTarget,
83+
overrideInLanguage?: boolean): Thenable<void> {
84+
throw new Error('Method not implemented.');
85+
}
86+
}

0 commit comments

Comments
 (0)