DEV Community

Cover image for I built a dependency-free Remote Exec Server in Python using BusyBox-style symlinks
Abhrankan Chakrabarti
Abhrankan Chakrabarti

Posted on

I built a dependency-free Remote Exec Server in Python using BusyBox-style symlinks

I wanted to see how minimal a remote command execution system could be when restricted entirely to the Python standard library. No external dependencies. No frameworks. Just clean, standard networking primitives.

The result: Remote Exec Server & Client — a two-file system (server.py + client.py) with a BusyBox-style symlink architecture.

👉 github.com/foxhackerzdevs/remote-exec-server


The Problem

I run PARI/GP on a remote machine and wanted to pipe expressions to it from my local terminal — without SSH overhead, without installing PARI/GP locally, and without a heavyweight RPC framework.

The simplest possible solution: an HTTP server that accepts a command + stdin, runs it, and returns stdout.


The BusyBox Idea

BusyBox is a single binary that behaves differently depending on the name it's invoked under. ls, cp, mv — all the same binary, different symlinks.

I applied the same pattern to client.py. You create symlinks named after tools installed on the server:

ln -s client.py gp
ln -s client.py python
ln -s client.py node
Enter fullscreen mode Exit fullscreen mode

When you invoke a symlink, client.py reads sys.argv[0] to determine which command to forward. That command, plus any arguments and stdin, gets packed into an HTTP POST request.


How It Works

server.py (26 lines)

#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
import subprocess, urllib.parse, shlex, os

class MyHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(content_length).decode()

        raw_path = urllib.parse.unquote(self.path.lstrip("/"))
        cmd_parts = shlex.split(raw_path)
        cmd_parts[0] = os.path.basename(cmd_parts[0])

        try:
            result = subprocess.run(
                cmd_parts,
                input=body.encode(),
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                check=True
            )
            output = result.stdout.decode()
        except subprocess.CalledProcessError as e:
            output = f"Error:\n{e.stderr.decode()}"
        except FileNotFoundError:
            output = f"Error: command not found: {cmd_parts[0]}"

        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
        self.wfile.write(output.encode())

if __name__ == "__main__":
    server = HTTPServer(("0.0.0.0", 8000), MyHandler)
    print("Serving on 0.0.0.0:8000")
    server.serve_forever()
Enter fullscreen mode Exit fullscreen mode

client.py (13 lines)

#!/usr/bin/env python3
import http.client, sys, urllib.parse

host = "192.168.56.1:8000"
conn = http.client.HTTPConnection(host)

path = "/" + urllib.parse.quote(sys.argv[0] + " " + " ".join(sys.argv[1:]))
body = sys.stdin.read()

conn.request("POST", path, body=body)
response = conn.getresponse()
print(response.read().decode())
Enter fullscreen mode Exit fullscreen mode

The protocol

The command and arguments travel in the URL path. Stdin travels in the request body. Stdout comes back in the response body. That's the entire protocol.

POST /gp%20-q HTTP/1.1
Host: 192.168.56.1:8000
Content-Type: text/plain

print(nextprime(100))
Enter fullscreen mode Exit fullscreen mode

In Action

# Start the server on the remote machine
python server.py

# On the client machine
ln -s client.py gp
echo "print(nextprime(100))" | ./gp -q
# → 101
Enter fullscreen mode Exit fullscreen mode

The Elephant in the Room: Security

This maps HTTP payloads directly to subprocess.run(). That's RCE by design. The baseline implementation has no authentication, no TLS, no rate limiting.

I built it this way intentionally — as a minimal blueprint that forces the operator to consciously design their own security layer rather than rely on defaults that provide a false sense of security.

Before any real deployment, the README covers:

Command whitelisting:

ALLOWED = {"python", "gp", "node"}
if cmd_parts[0] not in ALLOWED:
    output = f"Command not allowed: {cmd_parts[0]}"
    return
Enter fullscreen mode Exit fullscreen mode

Network isolation: Bind to 127.0.0.1 and route through an SSH tunnel or VPN.

Sandboxing: Run the server inside a Docker container with restricted permissions.


What's Next

The core engine is solid. The roadmap includes:

  • Native API-key and mTLS authentication
  • Output streaming and async request handling
  • Pre-configured Docker deployment

Contributions welcome — especially on the security and async fronts.

👉 github.com/foxhackerzdevs/remote-exec-server

Top comments (0)