-
Notifications
You must be signed in to change notification settings - Fork 33
POC: Electron - Unidirectional flow to limit terminal access #3144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
3a7ea42 to
e27ba3c
Compare
ZedLi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great proof of concept and really would help remove another attack vector! Mainly have some relatively minor comments and the main question of how we handle the mapping of events back to certain escape sequences
| this.#setupTerminal(fitAddon, xterm, termContainer); | ||
| this.#setupTerminal(fitAddon, xterm, termContainer, { | ||
| autoSSH, | ||
| sessionId: model?.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could've just been destructured from the model earlier
| if (id === null) { | ||
| this.activeTerminalId = null; | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a purpose to this extra if for setting id to null? Seems like we could check to see if there's a non-null terminal id for the set check and then just save regardless of the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it to avoid the check of validating terminal Id, since the value is null. But yes better would be just do this instead
setActiveTerminalId(id) {
if (id !== null && !this.terminals.has(id)) {
throw new Error(`No terminal registered with id: ${id}`);
}
this.activeTerminalId = id;
}
| this.activeTerminalId = null; | ||
| } | ||
|
|
||
| getActiveTerminalId() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these have been actual getters and setters backed by a private field?
| const terminalShell = this.getTerminalShell(); | ||
| const options = this.getTerminalOptions(validatedPayload); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did these need separate functions? It seems like they're also very short functions that aren't reused and could be inlined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah it does not need to be separate functions, we can remove them
| if (!session) { | ||
| throw new Error(`No session found with id: ${sessionId}`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we validating if the session is found by DC? We should still be able to open a terminal even if it wasn't started by our DC, this would completely error out now otherwise if we went to a session opened by CLI for example
|
|
||
| class TerminalManager { | ||
| constructor() { | ||
| this.terminals = new Map(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to have a terminals field for the future but right now we only ever can ever have one terminal ongoing at a time so this should only have 0 or 1 in its map.
If we keep it, we should leave a comment.
|
|
||
| ipcMain.once(removeChannel, cleanup); | ||
|
|
||
| ptyProcess.once('exit', cleanup); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this line, if the process has exited, why do we try to kill it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, we don't need to kill it once the process has already exited as it is already dead. We can just keep cleanup of terminals and channels on exit.
| /** | ||
| * Convert keyboard input into sanitized data for PTY write | ||
| */ | ||
| getSecureDataFromInput(input) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the plan for other controls like alt/opt or cmd? For example it seems I can no longer go back one word at a time in the terminal, will we map all of these? How about escape sequences such as here?
This is an area we likely need to add unit tests if so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just to cover base cases, but yeah we would need to cover all key mappings similar to xterm. We may end up extracting logic from xterm's evaluateKeyboardEvent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we also lost copy/paste functionality but was something handled by xterm, the previous onData:
Adds an event listener for when a data event fires. This happens for example when the user types or pastes into the terminal. The event value is whatever string results, in a typical setup, this should be passed on to the backing pty.
Capturing the clipboard contents can be done with electron's clipboard, although it might need to be tested if the keyboard event could be captured and the clipboard contents replaced? It looks like electron deprecated access from the renderer but I wonder about injection browser APIs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like text selection, and being able to copy that selection, from the terminal itself wouldn't work either which is a pretty big feature of using any terminal
| const id = payload.id; | ||
| const cols = payload.cols; | ||
| const rows = payload.rows; | ||
| const sessionId = payload.sessionId || null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we default to null? Seems like a strange default when it likely would be undefined otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh there's no particular reason to keep it null. I moved SSH command generation using session to main process much later and didn't do cleanup and missed this. undefined would make sense.
Description
Unrestricted Terminal Access - Electron security issue
Current electron architecture has a bidirectional flow :
The main issue is enabling write access to Renderer as it could write ANY data to the PTY and exposing user's full system access.
Exmaple:
This POC is to move towards unidirectional flow where Renderer does not have any write access to PTY and only has read access. We will capture user's input through Electron's
before-input-eventhandler. It intercepts keyboard input at the main process before it reaches the renderer.Also created a Terminal Manager Service to:
before-input-eventcaptures keyboard input, it provides raw keyboard event data that doesn't directly match what terminals expect. We need key mapping to translate it to terminal understandable formatScreenshots (if appropriate)
How to Test
Checklist
a11y-testslabel to run a11y audit tests if neededPCI review checklist
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.