Sandbox containment
An untrusted workload runs where its only route out is roxy. It sends its traffic to roxy's proxy port and trusts roxy's CA. The network does the containing: the proxy settings only tell a well-behaved client where roxy is (threat model).
Steps
-
Take away the workload's route out. Put it in a network namespace, VM or container network where roxy is the only host it can reach. Block everything else, including DNS. roxy resolves names itself, so the workload needs no DNS.
-
Give roxy a route out, and give the workload a route to roxy's proxy port. Let the workload reach the CA port too if it should fetch the CA itself.
listeners: - name: proxy mode: http_proxy bind: 0.0.0.0:3128 ca_server: bind: 0.0.0.0:3130 # serves /roxy-ca.pem -
Point the workload at roxy and make it trust roxy's CA (CA distribution):
export HTTP_PROXY=http://<proxy> HTTPS_PROXY=http://<proxy> export http_proxy=$HTTP_PROXY https_proxy=$HTTPS_PROXY # curl reads only the lower-case http_proxy export SSL_CERT_FILE=/path/roxy-ca.pem # OpenSSL-based tools, Python ssl, Go export REQUESTS_CA_BUNDLE=/path/roxy-ca.pem # Python requests export NODE_EXTRA_CA_CERTS=/path/roxy-ca.pem # Node export CURL_CA_BUNDLE=/path/roxy-ca.pem # curlAdding the certificate to the system trust store also works. Do not put external hosts in
NO_PROXY. -
Optionally, shut roxy for good when the workload misbehaves. A quarantine refuses every request until an operator lifts it. It can be tripped by a rule, or by a run of malformed requests:
listeners: - name: proxy bind: 0.0.0.0:3128 quarantine_on: parse_errors: { count: 20, window: 1m } rules: - id: exfil when: host == "api.anthropic.com" and body.text contains "BEGIN PRIVATE KEY" then: quarantineRun roxy with
--state-dirso the quarantine survives a restart.
With Docker Compose
The workload is only on an internal: true network, which has no route
out. roxy is on that network and on one with a route out:
services:
roxy:
image: ghcr.io/roxy-proxy/roxy:latest
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
volumes: [./roxy.yaml:/etc/roxy/roxy.yaml:ro, roxy-ca:/var/lib/roxy/ca]
networks: [sandbox, egress]
agent:
image: your-workload
networks: [sandbox] # no route out except through roxy
environment:
HTTPS_PROXY: http://roxy:3128
https_proxy: http://roxy:3128
NO_PROXY: roxy # roxy's CA endpoint, reached directly
no_proxy: roxy
networks:
sandbox:
internal: true # no route out: the containment
egress: {}
volumes:
roxy-ca: {}The workload fetches the CA from http://roxy:3130/roxy-ca.pem. A client
that ignores the proxy settings gets nowhere, because there is no route
out.
Other services on the Compose network have private addresses, which roxy
refuses by default. A rule that lets the workload reach one needs
then: { allow: { private_ok: true } }
(address floor).