socksscope
A SOCKS5 front-end to better handle and restrict the scope of your traffic during an engagement.
Wraps a SOCKS5 proxy you already have via --upstream, or acts as a new SOCKS5 proxy with --local. Every CONNECT is judged against a ruleset of domain names, addresses and ports that is specified as arguments at startup.
socksscope additionally allows you to pick where domain names are resolved and which DNS server answers them. Through an existing SOCKS5 proxy only DNS via TCP can be used for this. See section 'DNS and SOCKS5' below.
This tool utilizes dnspython.
This is a pentesting tool. Only point it at systems and networks you are authorized to test.
Why this tool exists
-
Four reasons to be honest (which are also my personal use-cases)
- During engagements I always end up with a lot of SOCKS5 proxies (
ssh,sshcatch,chisel,ligolo, or the C2 directly) so sometimes it makes sense to restrict some of them to the network they belong to in order to prevent mistakes (especially when coworkers are involved) - The network at the other end of a SOCKS5 proxy requires some unique DNS settings (just some specific hosts or a specific DNS server) that I don't want all other SOCKS5 proxies to share and I don't want to (or cannot) reconfigure the jumphost
- When using a tool via
proxychainswhere I can't find a way to deactivate telemetry or other default requests that otherwise are sent via the proxy - Also I did some experiments with AI agents and wanted to be sure to restrict the network connectivity (similar to the first coworker case I guess)
- During engagements I always end up with a lot of SOCKS5 proxies (
-
My solution:
socksscope- Open a (local) SOCKS5 proxy that wraps an existing SOCKS5 port or acts standalone
- Allows you to restrict the connections made through it
- Specify rules for domain names, addresses and ports on the command line or in a file
- Rate limits and a connection cap that queues instead of dropping
- Allows a flexible approach to DNS resolving
- Specify a DNS server to use (also through the tunnel when DNS via TCP is available)
- Specify domain name mappings like
/etc/hosts
Install
pipx install socksscope # or: pip install socksscope
socksscope -h
Or from source:
git clone https://github.com/LorenzMap/socksscope
cd socksscope
pip install .
./socksscope.py -h
It is a single file with one dependency (dnspython), so copying socksscope.py
onto a host and installing dnspython works too.
Developed and tested on Python 3.12; needs at least 3.10.
Examples
Wrap an SSH dynamic forward, resolve domain names through it and block every connection except the ones in the private 10.0.0.0/8 network.
# Tunnel
ssh -ND 1080 user@jumphost
# Proxy
socksscope.py -l 1081 -u 1080 --dns u:10.0.0.53 --allow 10.0.0.0/8
13:10:11 listening on 127.0.0.1:1081 -> socks 127.0.0.1:1080, dns 10.0.0.53:53/tcp (upstream)
13:10:11 rule allow * (default)
13:10:11 rule allow :1-65535 (default)
13:10:11 rule allow 10.0.0.0/8
13:10:11
13:10:11 repeated connections to a host are counted, not logged
13:10:11 each host is summarised and reset 10s after it goes quiet
13:10:11 =====================
...
# Client (allowed if intranet.corp.local resolved to an address in 10.0.0.0/8)
proxychains curl http://intranet.corp.local/
# or
curl -x socks5h://127.0.0.1:1081 http://intranet.corp.local/
Allow web ports only on one domain name. WATCH OUT that this does not block IP address connections to the IPs that the domain resolves to! (To restrict that use explicit IP address rules or check the --resolve-rules example.)
socksscope.py -u 1080 --allow 'corp.local' --allow :80 --allow :443
13:11:24 listening on 127.0.0.1:1081 -> socks 127.0.0.1:1080, dns upstream
13:11:24 rule allow 0.0.0.0/0 (default)
13:11:24 rule allow ::/0 (default)
13:11:24 rule allow corp.local
13:11:24 rule allow :80
13:11:24 rule allow :443
13:11:24
13:11:24 repeated connections to a host are counted, not logged
13:11:24 each host is summarised and reset 10s after it goes quiet
13:11:24 =====================
...
Resolve the names on the local system but send the traffic through the tunnel.
socksscope.py -u 1080 --dns l:8.8.8.8 --allow '*.corp.local' --allow :443
Standalone with rate and connection count restrictions.
socksscope.py --local --allow 10.10.0.0/16 --rate 1M --max-conns 20
Check a scope file against a few targets before trusting it.
socksscope.py --allow @scope.txt \
--test-ruleset admin.corp.local:445 \
--test-ruleset 10.10.0.7 \
--test-ruleset 8.8.8.8:53
Only allow connections to the specified domains and enforce that ALSO on IP addresses by resolving the domain name rules to their IP addresses.
socksscope.py -u 1080 --dns u:10.0.0.53 \
--allow intranet.corp.local --allow fileserver.corp.local \
--allow :443 --resolve-rules
How it works
Configure socksscope using command line arguments. See -h (short help) and
--help (full help) as well as the examples section above.
Rules
With no rules at all nothing is restricted. --block rules obviously
block all connections to the specified domain names, addresses or ports
while everything else is allowed.
On the other hand --allow rules block everything except the domain
names, addresses or ports that are specified.
Name, address and port are judged separately and all three have to pass.
If multiple rules affect the same domain name, address or port, the rule
that is the most specific wins (deeper subdomain, smaller subnet).
An exact tie goes to block.
A rule is read from its shape:
| value | means |
|---|---|
corp.local |
the domain name (and all its subdomains) |
*.corp.local |
subdomains only (not the domain root) |
* |
every domain name |
10.0.0.0/8 |
a network |
10.0.0.5 |
a single address |
10.0.0.1-10.0.0.50 |
a range, both ends included |
0.0.0.0/0 |
every IPv4 address |
fe80::/64 |
a network |
::/0 |
every IPv6 address |
:443 |
a port |
:8000-8100 |
a port range, both ends included |
--allow and --block arguments are repeatable as many times as you want.
You can specify a @FILE that loads a list of rules (one entry per line, #
comments, ! to negate a rule).
# scope.txt given as '--allow @scope.txt'
corp.local
10.10.0.0/16
192.168.1.10-192.168.1.50
!192.168.1.42 # excluded host
:443
--test-ruleset shows the whole decision without opening a listener:
$ socksscope.py --local --allow 'corp.local' --allow :443 --block admin.corp.local \
--test-ruleset www.corp.local --test-ruleset www.corp.local:443 --test-ruleset admin.corp.local:443
=====================
www.corp.local:80 => DENY (port not allowed by ruleset)
www.corp.local:443 => ALLOW -> www.corp.local
admin.corp.local:443 => DENY (block admin.corp.local)
When using --test-ruleset you may be asked whether DNS queries should be sent to
resolve the domain names. Read the question, think about whether queries like that are
acceptable in your engagement, and then answer.
DNS
This subsection describes what the different DNS arguments do. One important note first:
Where a domain name gets resolved decides how effective some rules are!
For example, using an upstream SOCKS5 without specifying a --dns resolves the
domain names on the upstream, so socksscope cannot enforce its address rules for that
request. The section 'DNS and SOCKS5' below explains why this happens and why for
this upstream use-case only DNS via TCP can be used.
If you want to skip that problem entirely and don't need domain name resolution at all:
--block '*' refuses every domain name, so every request has to carry an address
and the address rules apply to all of them.
--dns says which side of the tunnel a domain name is resolved on and,
optionally, which server to ask over there:
--dns SIDE[:SERVER[:PORT][:tcp|udp]]
The side is u (upstream) or l (local). Without a SERVER the default
system configuration of the specified side is used. If SERVER is specified
socksscope queries the DNS itself.
--dns |
who answers | the query goes | transport |
|---|---|---|---|
| (nothing) | u, or l with --local |
||
u |
the upstream's resolver | upstream's default configuration | - |
l |
local system's resolver | local system's default configuration | - |
u:10.0.0.53 |
socksscope via 10.0.0.53 | through the tunnel | tcp |
u:10.0.0.53:tcp |
socksscope via 10.0.0.53 | through the tunnel | tcp |
u:[::1]:5353:tcp |
socksscope via ::1 on port 5353 |
through the tunnel | tcp |
u:10.0.0.53:udp |
refused, see 'DNS and SOCKS5' below | ||
l:8.8.8.8 |
socksscope via 8.8.8.8 | local system's network | udp |
l:8.8.8.8:udp |
socksscope via 8.8.8.8 | local system's network | udp |
l:8.8.8.8:tcp |
socksscope via 8.8.8.8 | local system's network | tcp |
l:8.8.8.8:5353 |
socksscope via 8.8.8.8 on port 5353 | local system's network | udp |
SERVER must be an address, never a domain name. Resolving the resolver would need
a resolver, and which side should answer that question is exactly the
confusion this argument exists to remove. Look it up once yourself instead:
dig +short dns.corp.local # from here
proxychains dig +tcp +short dns.corp.local # from the other end of the tunnel
Additionally: The tool you use through socksscope has to hand the domain name to the proxy
instead of trying to resolve it itself. For example, use socks5h:// with curl and set
network.proxy.socks_remote_dns = true in Firefox if you have trouble.
Hosts Mapping
--hosts is checked before all of the DNS handling and answers without any query. It takes
mappings of name=ADDRESS or 'ADDRESS name [name ...]'. Files can be specified (similar to /etc/hosts).
--hosts intranet.corp.local=10.0.0.7
--hosts "10.0.0.8 db.corp.local db"
--hosts @hosts.txt
Resolve Rules
By default domain name rules only restrict the domain name and do not affect IP address rules. For example, a host may be blocked by a domain name rule but still be reachable by its IP.
--resolve-rules solves that. Every domain name rule is resolved at startup
and again at a fixed interval (--resolve-rules-every, 300s by default) so
socksscope can apply the addresses received as address rules. A blocked domain
name then blocks its addresses too. Works with --test-ruleset as well.
Resolving like this needs a resolver of our own, so anything but a plain
--dns u. Wildcard rules (*.corp.local, *) cannot be resolved and stay
name-only, socksscope says so at startup.
--resolve-rules results in active traffic to the specified DNS server! So it's
probably best to use it only against public DNS servers, or internal ones where you
know this is acceptable during an engagement. socksscope asks before it sends the
first queries, --yes-resolve-rules answers that prompt for you.
Connection Limits
--rate is one budget shared by all connections, --rate-per-conn gives every
connection its own. Both take bytes per second in a 1M or 512k style.
--max-conns caps how many connections run at the same time. The rest queue
instead of failing, a waiting client simply sits in the SOCKS5 handshake without
a reply until a slot frees up. --queue-timeout limits how long it waits there.
DNS and SOCKS5
While the tool feels intuitive in most aspects (at least to me), one stands out as confusing and I want to give some explanation for it.
The SOCKS5 protocol supports UDP, but most endpoints/servers do not (including
ssh -D for example) (2026-08). Accordingly, DNS requests through SOCKS5 proxies
are not sent as UDP requests from the client through the tunnel. Instead the
domain name itself is sent in place of an IP address in the CONNECT request. The
endpoint/server receives it, resolves the domain name using its own configuration
or cache, and then establishes the TCP connection to the destination.
For the domain name rules and host to IP mapping of socksscope this is fine because we can simply read that domain name from the CONNECT request.
However, every time we want to actively resolve something through the tunnel
using our own local logic we are limited to DNS via TCP requests. This affects
the DNS queries of your tools as well as those from --resolve-rules, resulting
in the limitation that --dns u:SERVER:udp is refused at startup. Asking over UDP
means asking from here, which is --dns l:SERVER - the data still goes through
the upstream, only the query does not.
Word of Warning
socksscope is not a firewall or privacy tool. It only sees what a client sends through it. Nothing stops a client from opening a socket directly, so the scope is enforced on the tools you point at it, not on the host. While it's possible to use socksscope securely, it is a pentesting/redteaming tool and NOT a privacy tool. There are a lot of ways to misconfigure socksscope!
--resolve-rules can be noisy. It queries every domain name rule again and
again in fixed intervals. That is fine against a public DNS server, but may
not be against an internal one that somebody is watching.
It fails closed. A domain name rule that cannot be resolved at startup stops the tool, and so does a domain name rule that stays unconfirmable for three refresh intervals. Better than judging traffic by an address we cannot verify.
Options
socksscope.py -h prints a short help with the arguments needed to get going.
The full reference below is socksscope.py --help.
usage: socksscope.py [-h] [--help] [-l [HOST:]PORT] [--listen-auth USER:PASS]
[--local] [-u [HOST:]PORT] [--upstream-auth USER:PASS]
[-v] [-q] [--version] [--allow RULE] [--block RULE]
[--test-ruleset HOST[:PORT]] [--resolve-rules]
[--yes-resolve-rules] [--resolve-rules-every SEC]
[--dns SIDE[:SERVER[:PORT][:tcp|udp]]] [--hosts ENTRY]
[--rate SIZE] [--rate-per-conn SIZE] [--max-conns N]
[--queue-timeout SEC]
socksscope - a SOCKS5 front-end that lets you manage your traffic and keep it
inside your engagement scope. Can wrap an existing SOCKS5 port (--upstream)
or act independently (--local).
Every CONNECT is judged against a ruleset of domain names, addresses and ports,
given as arguments or in files at startup. Additionally you can specify a DNS
server to use and throttle connection speeds.
While it's possible to use socksscope securely, this is a pentesting/redteaming
tool and NOT a privacy tool. There are a lot of ways to misconfigure socksscope!
Watch out for unexpected rulesets when combining IP address and domain name rules.
(Check the README on Github for more information!)
options:
-h show a short help message and exit
--help show the full help and exit
-l [HOST:]PORT, --listen [HOST:]PORT
SOCKS5 port socksscope.py opens (default:
127.0.0.1:1081) - (restricted) SOCKS5 port where the
client programs connect to
--listen-auth USER:PASS
optional credentials for the SOCKS5 port socksscope
opens
--local no SOCKS5 proxy to wrap, run socksscope.py
independently - connect out from this host while
enforcing the ruleset
-u [HOST:]PORT, --upstream [HOST:]PORT
the existing SOCKS5 proxy that will be wrapped
(default: 127.0.0.1:1080)
--upstream-auth USER:PASS
optional credentials for the upstream proxy
-v, --verbose log every connection with additional information
-q, --quiet log warnings only
--version show the version and exit
ruleset:
--allow RULE domain name, IP address or port rule to allow
(repeatable) - Rules: domain.tld | *.domain.tld | * |
IP | IP/NET | IP-IP | :PORT | :PORT-PORT - @FILE loads
a rules file - ranges include both ends - '!' inverts
a rule
--block RULE same as --allow but blocked instead (repeatable) -
syntax exactly like --allow
--test-ruleset HOST[:PORT]
print how a target would be judged, then exit
(repeatable) - a target without a PORT is judged as
:80
--resolve-rules resolve domain name rules and apply them as IP address
rules (results in repeating queries)
--yes-resolve-rules answer the startup --resolve-rules confirmation with
yes
--resolve-rules-every SEC
interval to re-resolve domain name rules (see
--resolve-rules) - if a domain name rule is
unconfirmed for three intervals socksscope exits - use
0 to resolve only once at startup (default: 300)
DNS:
--dns SIDE[:SERVER[:PORT][:tcp|udp]]
specify where DNS queries should be resolved and what
protocol to use - check the README on Github for
explanations of all combinations - SIDE=[u|upstream]
to resolve through the wrapped SOCKS5 - SIDE=[l|local]
to resolve via the host socksscope is running on -
[:SERVER[:PORT]] optionally specify a DNS server -
[:tcp|:udp] optionally specify the DNS transport
protocol
--hosts ENTRY static mapping used before any DNS (like /etc/hosts)
(repeatable) - 'name=ADDRESS' or 'ADDRESS name' or
'@FILE' to load a list
limits:
--rate SIZE total bytes/s over all connections (e.g. 1M, 512k)
--rate-per-conn SIZE bytes/s for a single connection
--max-conns N connections to run at once - the rest queue instead of
failing
--queue-timeout SEC give up queueing after this long, 0 waits forever
(default: 60)
For more detailed examples, reasonings behind design decisions as well as an in-depth
explanation of socksscope's DNS resolving (especially when wrapping a SOCKS5 port and
actively resolving domain name rules using --resolve-rules) check the README on Github.
examples:
socksscope.py -u 1080 --dns u:10.0.0.53 --allow @scope.txt
socksscope.py -u 1080 --resolve-rules --allow 'intranet.corp.local' --allow :443
socksscope.py --local --allow 10.0.0.0/8 --rate 1M --max-conns 20
socksscope.py --allow @scope.txt --test-ruleset admin.corp.local:445
Testing
- the test suite lives in
tests/(pytest) - run it from a virtualenv with the project and its dev dependencies installed
(
pip install -e ., thenpip install --group devon pip 25.1+ or simplypip install pytest pytest-xdist coverage) - run it via
python -m pytestortests/test.sh,-n 8runs it in parallel - for the coverage of the tests run
tests/test.sh cov - the throttling tests are timing based, so a busy machine can make them flap; re-run before believing a failure there
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file socksscope-0.2.0.tar.gz.
File metadata
- Download URL: socksscope-0.2.0.tar.gz
- Upload date:
- Size: 30.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c168d6066aee3b4b1d17ee357291a7939c789bdca4d0bc8770f0ed7987872909
|
|
| MD5 |
26d28c53d9e9ea2f996b9e03ccd13e9e
|
|
| BLAKE2b-256 |
0a7d274f7ba2ce9465f762b1d9a7b8810e0709d19cc30da792e893632c8e2ae1
|
File details
Details for the file socksscope-0.2.0-py3-none-any.whl.
File metadata
- Download URL: socksscope-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3df7c5dd938d71c5bf64959e84a66483a0757a9a9dd3248c6bcfc37f52fa3588
|
|
| MD5 |
05eb3e61e9fd41ed7ca039362160fbb3
|
|
| BLAKE2b-256 |
7000c14e8f55a6e14f36fe3edd665febd590d47339bd3b3436fe074417d9d1d4
|