Dev.to Security 🔐 Cybersecurity 👁 1 📖 3 min read

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 re

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

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()

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())

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))

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

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

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

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.