diff --git a/web/packages/teleterm/src/preload.ts b/web/packages/teleterm/src/preload.ts index 268707c05e6c..069ea13a1641 100644 --- a/web/packages/teleterm/src/preload.ts +++ b/web/packages/teleterm/src/preload.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { contextBridge } from 'electron'; +import { contextBridge, webUtils } from 'electron'; import { ChannelCredentials, ServerCredentials } from '@grpc/grpc-js'; import createTshClient from 'teleterm/services/tshd/createClient'; @@ -83,6 +83,14 @@ async function getElectronGlobals(): Promise { tshClient, ptyServiceClient, subscribeToTshdEvent, + // Ideally, we would call this function only on the preload side, + // but there's no easy way to access the file there (constructing the tshd + // call for a file transfer happens entirely on the renderer side). + // + // However, the risk of exposing this API is minimal because the file passed + // in cannot be constructed in JS (it must be selected in the file picker). + // So an attacker cannot pass a fake file to probe the file system. + getPathForFile: file => webUtils.getPathForFile(file), }; } diff --git a/web/packages/teleterm/src/types.ts b/web/packages/teleterm/src/types.ts index b7b6facf9233..c01e567d1024 100644 --- a/web/packages/teleterm/src/types.ts +++ b/web/packages/teleterm/src/types.ts @@ -71,4 +71,6 @@ export type ElectronGlobals = { readonly tshClient: TshClient; readonly ptyServiceClient: PtyServiceClient; readonly subscribeToTshdEvent: SubscribeToTshdEvent; + /** Exposes Electron's webUtils.getPathForFile. */ + getPathForFile(file: File): string; }; diff --git a/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx b/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx index 1bd38794e8d9..eaef28b4ab77 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx @@ -94,7 +94,7 @@ export function DocumentTerminal(props: { { serverUri: doc.serverUri, login: doc.login, - source: file.path, + source: ctx.getPathForFile(file), destination: destinationPath, }, abortController diff --git a/web/packages/teleterm/src/ui/appContext.ts b/web/packages/teleterm/src/ui/appContext.ts index 2cffc06ba836..68c818b139f8 100644 --- a/web/packages/teleterm/src/ui/appContext.ts +++ b/web/packages/teleterm/src/ui/appContext.ts @@ -77,6 +77,7 @@ export default class AppContext implements IAppContext { * request gets canceled by the client. */ subscribeToTshdEvent: SubscribeToTshdEvent; + getPathForFile: (file: File) => string; reloginService: ReloginService; tshdNotificationsService: TshdNotificationsService; headlessAuthenticationService: HeadlessAuthenticationService; @@ -93,6 +94,7 @@ export default class AppContext implements IAppContext { this.mainProcessClient = mainProcessClient; this.notificationsService = new NotificationsService(); this.configService = this.mainProcessClient.configService; + this.getPathForFile = config.getPathForFile; this.usageService = new UsageService( tshClient, this.configService, diff --git a/web/packages/teleterm/src/ui/fixtures/mocks.ts b/web/packages/teleterm/src/ui/fixtures/mocks.ts index a7646fdf9fa9..91027eb255b2 100644 --- a/web/packages/teleterm/src/ui/fixtures/mocks.ts +++ b/web/packages/teleterm/src/ui/fixtures/mocks.ts @@ -31,6 +31,7 @@ export class MockAppContext extends AppContext { tshClient: tshdClient, ptyServiceClient, subscribeToTshdEvent: () => {}, + getPathForFile: () => '', }); } } diff --git a/web/packages/teleterm/src/ui/types.ts b/web/packages/teleterm/src/ui/types.ts index cc72959e2b30..155f5b8edee9 100644 --- a/web/packages/teleterm/src/ui/types.ts +++ b/web/packages/teleterm/src/ui/types.ts @@ -55,6 +55,7 @@ export interface IAppContext { connectMyComputerService: ConnectMyComputerService; headlessAuthenticationService: HeadlessAuthenticationService; tshd: TshClient; - + /** Exposes Electron's webUtils.getPathForFile. */ + getPathForFile: (file: File) => string; pullInitialState(): Promise; }