Emergency Console
Why an emergency console
Section titled “Why an emergency console”When an alert tells you “something weird is running on host X”, you usually want to look right now. Installing SSH/RDP everywhere = wide attack surface, ongoing maintenance, and no forensic trail. The emergency console opens a short-lived, scoped shell over the existing agent WS tunnel — no inbound ports, no permanent credentials on the host, complete server-side audit trail.
Auth flow (identical on every OS)
Section titled “Auth flow (identical on every OS)”- Operator clicks Open Emergency Console on an agent.
- Dashboard asks for reason (≥20 chars) + TOTP code.
- Hub API validates role (admin/owner), TOTP, and rate-limit (3 sessions / hour / user).
- Hub signs an Ed25519 console-session token (15 min TTL, nonce, agent-bound) and pushes a
console_startframe to the agent over the existing WebSocket. - Agent verifies signature + nonce + TTL, spawns a local shell, opens a keystroke stream back to the hub.
- Browser opens
wss://api.monsys.ai/ws/console/<session_id>— bidirectional binary stream.
Every I/O byte is written to console_audit_log on the hub (direction=‘I’ for input, ‘O’ for output) with sequence_num and agent_id. At session end a SHA256 of the full log (in sequence_num order) is pinned on console_sessions.audit_hash as forensic proof.
Linux side — bash --restricted as monsys-console
Section titled “Linux side — bash --restricted as monsys-console”At agent install:
useradd -r -s /bin/false -d /var/lib/monsys/console monsys-console.bashrcwith red[MONSYS-CONSOLE]prompt +HISTTIMEFORMAT
Per session:
setpriv --reuid=monsys-console --regid=monsys-console \ --init-groups --inh-caps=-all \ -- /bin/bash --restricted \ --rcfile /var/lib/monsys/console/.bashrcRestricted bash blocks cd, output redirection with >, and PATH= rewrites. Caps are stripped. The sudoers fragment for monsys-console is empty → no privilege escalation possible.
Windows side — ConPTY + PowerShell with JEA
Section titled “Windows side — ConPTY + PowerShell with JEA”On Windows, a pseudo-console (ConPTY, native API since Win10 1809) gives real terminal semantics (colors, resize, ctrl-c), and Just Enough Administration (JEA) enforces the cmdlet whitelist server-side.
At agent install (install.ps1):
- Creates module
MonsysJEAunder$Env:ProgramFiles\WindowsPowerShell\Modules\ - Writes Role Capability File
MonsysConsole.psrcwith the whitelist - Writes Session Configuration File
monsys-console-jea.psscwithRunAsVirtualAccount=$true,LanguageMode=NoLanguage,SessionType=RestrictedRemoteServer Register-PSSessionConfiguration -Name monsys-console-jea
Per session:
powershell.exe -NoLogo -NoProfile -NoExit \ -ConfigurationName monsys-console-jeaStdin/stdout of the ConPTY pair is mapped onto the WS stream.
What the operator CAN do (whitelist)
Section titled “What the operator CAN do (whitelist)”Forensic-IR set, ~60 cmdlets:
| Category | Cmdlets |
|---|---|
| Process | Get-Process, Stop-Process, Wait-Process, Debug-Process |
| Service | Get/Stop/Start/Restart/Suspend/Resume-Service, Set-Service |
| Event log | Get-EventLog, Get-WinEvent (no Clear-EventLog) |
| Network | Get-NetTCPConnection, Get-NetUDPEndpoint, Get-NetAdapter, Get-NetIPAddress, Get-NetIPConfiguration, Get-NetRoute, Get-DnsClient*, Resolve-DnsName, Test-Connection, Test-NetConnection |
| Containment | Disable/Enable-NetAdapter, New/Remove/Get-NetFirewallRule, Set-NetFirewallProfile |
| Users | Get-LocalUser, Get-LocalGroup, Get-LocalGroupMember (no Add/Set/Remove) |
| Filesystem | Get-ChildItem, Get-Item, Get-Content, Get-ItemProperty, Get-FileHash, Test-Path, Resolve-Path, Get-Acl, Get-AuthenticodeSignature |
| Tasks | Get-ScheduledTask, Disable-ScheduledTask |
| WMI/CIM | Get-CimInstance, Get-CimClass (no Invoke-CimMethod) |
| Perf | Get-Counter, Get-ComputerInfo, Get-HotFix |
| Pipeline | ForEach-Object, Where-Object, Select-Object, Sort-Object, Group-Object, Measure-Object, Compare-Object, Format-*, Out-*, Select-String |
| Power | Restart-Computer, Stop-Computer |
What the operator CANNOT do (JEA blocks server-side)
Section titled “What the operator CANNOT do (JEA blocks server-side)”Invoke-Expression,Invoke-Command— no arbitrary code executionNew-LocalUser,Add-LocalGroupMember,Set-LocalUser— no new privileged accountsRemove-Item,Format-Volume— no destructionClear-EventLog— no forensic-evidence tamperingSet-Aclon critical paths — no permission changes.exeexecutables outside the whitelistNew-PSSession— no pivoting to other hosts- Scripts or variable expansion (
LanguageMode=NoLanguage) — only direct cmdlet invocations
Try a blocked cmdlet, get a server-side red error:
The term 'Remove-Item' is not recognized as the name of a cmdlet, function, ...JEA transcripts are also written locally to $Env:ProgramData\monsys\console-transcripts\ as a secondary forensic trail. The primary trail stays console_audit_log on the hub.
Session end
Section titled “Session end”Session ends on:
- Browser disconnect (operator closes tab)
- 15 min TTL reached → auto-close
exit/Exit-PSSession- Tenant suspended → hub closes all open sessions immediately
At every close a SHA256 of the full console_audit_log (in sequence_num order) is computed and pinned on the session row. That hash can be compared later to prove the log was not altered.
Permissions summary
Section titled “Permissions summary”| Linux | Windows | |
|---|---|---|
| Shell | bash --restricted | powershell (LanguageMode=NoLanguage) |
| User context | monsys-console (low-priv) | JEA RunAs virtual account (ephemeral) |
| Whitelist | Restricted bash path | JEA RoleCapability (~60 cmdlets) |
| Filesystem destruction | Blocked (no redirects) | Remove-Item not in whitelist |
| Privilege escalation | No sudoers entry for monsys-console | Virtual account, not in admin group |
| Audit (hub) | console_audit_log with SHA256 seal | same |
| Audit (host fallback) | n/a | $ProgramData\monsys\console-transcripts\ |