A self-hosted rotating egress proxy. It routes outbound traffic through addresses you control, refuses internal destinations at dial time, and logs every request.
Sluice does not fetch, render or extract anything. It is what other things reach the internet through — an HTTP CONNECT and SOCKS5 endpoint that any client already knows how to use.
curl -x "http://$SLUICE_KEY:x@proxy:3128" https://api.ipify.org
Four kinds of egress. A direct connection, an upstream HTTP or SOCKS5 proxy, an SSH tunnel, or a local address bound on your own machine. Each is a route with its own health, and every route is probed against a real endpoint — a successful dial proves a socket bound, not that traffic from it comes back.
Rotation with stickiness. Settings ride in the proxy username, so a client that can only be told a proxy URL can still choose behaviour per request:
$SLUICE_KEY__rot.request a different route each request
$SLUICE_KEY__sess.job42 the same route for everything tagged job42
$SLUICE_KEY__route.nyc1 pin one route
A default-deny guard at dial time. Loopback, RFC1918, link-local including cloud metadata, carrier-grade NAT and the node's own addresses are refused, whichever route or protocol a caller picked. The guard resolves DNS itself and dials the address it validated, so a name that resolves differently the second time does not get a second answer.
Local address management. Given a block routed to your host, Sluice puts addresses on the interface itself and keeps them in step with the routes table — adding what is missing, removing what is orphaned, repairing a wrong mask.
A dashboard and an audit log. Projects own API keys, each key carries its own rotation and its own allow-list exemption to the default-deny guard, and every request is recorded with its route, bytes and outcome.
A sidebar dashboard: live route health, a rotation self-test that runs real requests through the selection path, projects with per-key settings and allow-lists, a filterable audit log, and a sign-in that explains what a sluice is.
Sluice is one binary and one SQLite file.
docker run -p 3128:3128 -p 1080:1080 -p 8080:8080 \
-v sluice-data:/data ghcr.io/jekyo/sluice:latest
The admin password is generated on first start and printed once. Set
SLUICE_ADMIN_PASSWORD to choose your own.
On jekyo, jekyo up reads the jekyo.yaml in this repo.
| Variable | Default | |
|---|---|---|
SLUICE_ADDR |
:8080 |
dashboard and API |
SLUICE_PROXY_ADDR |
:3128 |
HTTP CONNECT |
SLUICE_SOCKS_ADDR |
:1080 |
SOCKS5 |
SLUICE_DB |
/data/sluice.db |
|
SLUICE_ADMIN_EMAIL |
admin@localhost |
|
SLUICE_ADMIN_PASSWORD |
generated | printed once at first start |
SLUICE_ADDR_BLOCKS |
unset | ranges usable as a source address |
SLUICE_PROBE_URL |
https://api.ipify.org |
what health checks ask |
SLUICE_LOG_RETENTION_DAYS |
30 |
Binding an outbound socket to an address only works if the address is really on an interface. If your block is on-link — the usual case for a range on the same segment as your host — the upstream router resolves each address by ARP, so an address nothing answers for is a silent timeout rather than an error.
Declare the ranges Sluice may use:
SLUICE_ADDR_BLOCKS="185.235.78.0/24,gw=185.235.78.1,dev=enp1s0f1"
Semicolon-separated for several; only the CIDR is required. This is read from
the environment at start and cannot be changed from the dashboard, because the
process that acts on it holds NET_ADMIN over the host's network namespace.
Two things are worth knowing before you rely on this:
The mask matters and fails silently. An address added with the wrong prefix length sits on the interface looking correct while every connection from it times out. Sluice defaults to the mask your host's own address carries, which is right whenever the range is part of your host's subnet.
A new address is not usable the moment it exists. The upstream router has to learn it, which can take anywhere from seconds to a few minutes. The dashboard waits and tells you when it is actually reachable rather than reporting success the instant the row is written.
Under Kubernetes this needs host networking, and the address work runs in a
small addrd sidecar holding NET_ADMIN so the proxy itself stays unprivileged
and non-root. A capability does not survive execve for a non-root UID, so
granting it to the proxy would not have worked anyway without running it as root.
Sluice is an SSRF engine by design; the interesting question is what it refuses. See SECURITY.md for the threat model and how to report a vulnerability.
See CONTRIBUTING.md.
MIT. See LICENSE.