9.0 KiB
RVBox - A Reverse Shell Solution for LLM Agent
-
It's a C/S architecture, the clients are the one connects to the server, and are controlled by it.
-
The clients runs the
rvboxdaemon, actively and persistently connects to a configured server. It registers and identifies itself to the server first, if accepted, then keeps a long connection and accepting commands from the server. -
The server runs the
rvbox-serverdaemon, listening on awss://endpoint, waiting for incomingrvboxclients. An LLM agent on the server machine then can uservccommand to communicate with the server daemon, issuing commands to do all sorts of things:- listing clients,
- issuing/terminating commands on specific client,
- retrieving running/stopping status of a command,
- getting stdout/stderr, or return code from a command,
- inputing into stdin of a command,
-
commands can be issued in two modes: foreground or background, for foreground ones,
rvccommand wait for the return, otherwise,rvcreturns immediately with metadata of the issued command process on the client. -
for each issued command from
rvc, the server daemon attaches anissue_uuidto it, and sends it to the related client, along with these fields:issue_timestamp,shell_type,shell_command_text. The server also records all these fields into its data store, with anothertarget_client_id, to track the full states of every command. -
The client's daemon runs the issued commands, and reports stdout/stderr of all issued commands to the server in real-time, meaning no waiting for new-line flush. Also, multiple commands can be issued to a client daemon, and the client daemon can execute them concurrently. For each running command, the daemon appends new stdout and stderr updates to the server in every short 3-5s window, or only reports the command's status in a longer 10-15s window if no new updates. The server keeps on track of everything, so it can reconstruct the full context/history correctly for each command, when demanded so by
rvc. -
The connection should be guarded by a properly designed heartbeat mechanism on both C/S sides.
-
rvcis just a cli command that communicates with the server daemon through local unixsock. Also, the same server daemon control interface can be accessed through an optional simple HTTP json rpc endpoint, e.g.--json-rpc http://127.0.0.1:6900by default, so that we can interact with the server in a more structured way when complex or large batch operations are needed.
Example usages of rvc on server side
-
Listing clients
$ rvc stat CLIENT Commands Running Connected Time client_a 5 10 days client_b 0 10 min ... -
Stating a client
$ rvc stat client_a Command ID Command Snippet Status Issue Time <uuid_1> "cp /path1/file /path2/" Running 10s ago <uuid_2> "python very_long_computing.py" Running 2 days ago <uuid_3> "sha256sum /path/very_large_file" Hung 10min ago -
Stating all commands of a client. Note this
--allcan return very long history, so it has a optional pager--page <NO>arg. By default the optional--per-pageis at 20, and accepts 100 at max.$ rvc stat client_a --all Page 1/34 Command ID Command Snippet Status Issue Time <uuid_1> "cp /path1/file /path2/" Running 10s ago <uuid_2> "python very_long_computing.py" Running 2 days ago <uuid_3> "sha256sum /path/very_large_file" Hung(I/O) 10min ago <uuid_4> "curl -fsLO https://..." Done 15min ago <uuid_5> "culr -fsLO https://..." Failed(127) 15min ago <uuid_6> "curl -fsLO https://..." Terminated(130) 15min ago ... -
Stating some commands of client_a
$ rvc stat client_a <uuid_1> Command Text: "cp /path1/file /path2/" Status: Running Issue Time: 20xx-01-01T10:01:01Z CWD: "/..." CPU TIME: 0:00.23 RES RAM: 12K$ rvc stat client_a <uuid_2> Command Text: "python very_long_computing.py" Status: Running Issue Time: 20xx-12-30T12:01:01Z CWD: "/..." CPU TIME: 12h34:45 RES RAM: 892M$ rvc stat client_a <uuid_3> Command Text: "sha256sum /path/very_large_file" Status: Hung Reason: I/O Issue Time: 20xx-01-01T10:01:01Z CWD: "/..." CPU TIME: 0:10.24 RES RAM: 125K$ rvc stat client_a <uuid_4> Command Text: "curl -fsLO https://<download_url>" Status: Success Issue Time: 20xx-01-01T10:01:01Z CWD: "/..." CPU TIME: 0:10.24 RES RAM: 125K$ rvc stat client_a <uuid_5> Command Text: "culr -fsLO https://<download_url>" Status: Failure Exit Code: 127 Issue Time: 20xx-01-01T10:01:01Z Return Time: 20xx-01-01T10:01:01Z CWD: "/..."$ rvc stat client_a <uuid_6> Command Text: "curl -fsLO https://<download url>" Status: Terminated Exit Code: 130 Issue Time: 20xx-01-01T10:01:01Z Return Time: 20xx-01-01T10:02:01Z CWD: "/..." -
Issue a new command to a client.
-
By design, the commands triggered/executed by the client daemon should inherit its user/permission settings.
-
By default, the optional
--current-work-dir/--cwdof the issued command is inherited from the client daemon. -
If
--scriptis used to issue a whole script, the script will first be put into the--cwddir, then executed by the client daemon. As soon as it's exited, the script will be reclaimed.
# wait until return by default $ rvc run client_a --cwd '/home/user' 'ls' Desktop Documents Downloads Pictures... # run in background, return the command id immediately $ rvc run --background client_a 'python -c "for i in range(15): print(\'hi\'); import time; time.sleep(1)"' Command ID: <uuid_x> # if the command is too long or the symbol escapings are too complicated, write a whole script then issue. $ cat << 'EOF' > ./py_script heredoc> #!/usr/bin/env python heredoc> print("what's your name?") heredoc> name = input("type your name here: ") heredoc> print(f"Hello, {name}!") heredoc> EOF $ rvc run client_a --script ./py_script --cwd /tmp --background Command ID: <uuid_y> -
-
Get stdout and/or stderr of a command,
- optionally with
--timestampedto get a leading timestamp on each line, - and optionally follow new updates with
--follow/-f, just like thetail -fcommand - the output of
--stdoutand--stderrare paged, with--lines-per-pagedefaults to 100 and--pagedefaults to 1.
$ rvc stat client_a <uuid_x> --stdout --stderr --timestamped --page 2 --lines-per-page 3 Page 2/4 [20xx-01-01T10:21:01Z stdout] hi [20xx-01-01T10:21:02Z stdout] hi [20xx-01-01T10:21:03Z stdout] hi - optionally with
-
Write new content into stdin of a command
# write the string into its stdin, and a \n will be appended to the end automatically $ rvc append client_a <uuid_y> 'Alice' $ rvc stat client_a <uuid_y> --stdout --stderr --timestamped [20xx-01-01T10:23:11Z stdout] type your name here: [20xx-01-01T10:24:09Z stdout] Hello, Alice! # or write a file content into its stdin $ rvc append client_a <uuid_y> --file ./name_file # or patch the current stdin into it $ rvc append client_a <uuid_y> --attach Bob -
Send a signal to a command.
- For unix/linux client daemons, this
killsubcommand behaves just like the normalkillcommand. It sends SIGTERM by default, and can be used to send other signals with args like-1/-HUP,-2/-INT, etc. - On windows client daemons, it should only accept SIGTERM and SIGKILL, equivalent to
taskkill /IMandtaskkill /F /IM, respectively.
$ rvc kill client_a <uuid_3> - For unix/linux client daemons, this
Implementation requirements
- We should first do proper design on the protocol - both the C/S interface and the server daemon unixsock/jsonrpc control interface. protobuf should be a solid choice to do the protocol design.
- Currently we'll implement both C/S daemon in Golang. We'll later implement a Rust version client to adapt on low power devices.
Technical nuances
-
The
rvbox-serverdaemon and thervboxclient daemon should always be responsive. The server daemon should always be dispatching clients, recording the status, and listening for requests fromrvc, etc. And the client daemons should keep on receiving/dispatching incoming commands/requests, monitoring ongoing commands, reporting new updates/status to the server. In ABSOLUTELY NO circumstances should the daemons themselves be crashed, blocked, hung, flooded, unresponsive, etc., due to any possible reason. -
The heartbeat mechanism that keeps and guards the websocket connection should be robust, clean and correct. When connection exceptions/interruptions/hangs happen, the heartbeat mechanism should detect them in time, and trigger the reconnect, re-register route correctly. It should guard on both the inbound and the outbound traffic. It should not disrupt, hog, block, interrupt, mutate the normal traffic in any way.