diff --git a/web/packages/teleterm/src/preload.ts b/web/packages/teleterm/src/preload.ts index 0fde69b7b6b8..8faa6a267da2 100644 --- a/web/packages/teleterm/src/preload.ts +++ b/web/packages/teleterm/src/preload.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { contextBridge } from 'electron'; +import { contextBridge, webUtils } from 'electron'; import { ChannelCredentials, ServerCredentials } from '@grpc/grpc-js'; import { GrpcTransport } from '@protobuf-ts/grpc-transport'; @@ -99,6 +99,14 @@ async function getElectronGlobals(): Promise { vnetClient, ptyServiceClient, setupTshdEventContextBridgeService, + // 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 eb55faf740fe..6f41116362a7 100644 --- a/web/packages/teleterm/src/types.ts +++ b/web/packages/teleterm/src/types.ts @@ -111,4 +111,6 @@ export type ElectronGlobals = { readonly setupTshdEventContextBridgeService: ( listener: TshdEventContextBridgeService ) => void; + /** 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 928e27ef900c..4ee94f06c2d4 100644 --- a/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx +++ b/web/packages/teleterm/src/ui/DocumentTerminal/DocumentTerminal.tsx @@ -96,7 +96,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 2fd6f660a42c..506e44198607 100644 --- a/web/packages/teleterm/src/ui/appContext.ts +++ b/web/packages/teleterm/src/ui/appContext.ts @@ -75,6 +75,7 @@ export default class AppContext implements IAppContext { setupTshdEventContextBridgeService: ( service: TshdEventContextBridgeService ) => void; + getPathForFile: (file: File) => string; reloginService: ReloginService; tshdNotificationsService: TshdNotificationsService; headlessAuthenticationService: HeadlessAuthenticationService; @@ -96,6 +97,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 2c673ce9fa80..fea17b77e8b2 100644 --- a/web/packages/teleterm/src/ui/fixtures/mocks.ts +++ b/web/packages/teleterm/src/ui/fixtures/mocks.ts @@ -38,6 +38,7 @@ export class MockAppContext extends AppContext { vnetClient, ptyServiceClient, setupTshdEventContextBridgeService: () => {}, + getPathForFile: () => '', }); } } diff --git a/web/packages/teleterm/src/ui/types.ts b/web/packages/teleterm/src/ui/types.ts index e68c0412c537..bef3fdaa6d21 100644 --- a/web/packages/teleterm/src/ui/types.ts +++ b/web/packages/teleterm/src/ui/types.ts @@ -64,7 +64,8 @@ export interface IAppContext { headlessAuthenticationService: HeadlessAuthenticationService; tshd: TshdClient; vnet: VnetClient; - + /** Exposes Electron's webUtils.getPathForFile. */ + getPathForFile: (file: File) => string; pullInitialState(): Promise; /**