Upstream proxy with per-phishlet routing
Feature #17. Some identity providers block datacenter IPs outright (Google flags AWS exits); others risk-flag residential logins for corporate accounts (Microsoft Entra). The fork lets each node configure one upstream proxy with per-domain-suffix routing: matched traffic egresses through the proxy, everything else goes direct.
Configuration
Via the console
eg> proxy
current: off
eg> proxy set socks5 <user> <pass> <proxy-host> <proxy-port> google.com,gstatic.com,googleusercontent.com
eg> proxy on
eg> proxy route add mail.google.com # hot-add a routed suffix
eg> proxy route del mail.google.com
eg> proxy off # disable without forgetting the config
Hot-applied — no service restart. Persisted in the node config.
Via the mTLS API
POST /proxy (partial update — omitted keys keep their values):
{
"type": "socks5",
"host": "<proxy-host>",
"port": 48726,
"username": "<user>",
"password": "<pass>",
"enabled": true,
"routes": ["google.com", "gstatic.com", "googleusercontent.com"]
}
GET /proxy returns the state with the password masked. Note: a masked password echoed
back in a later POST clobbers it — send the real password when you intend to change
credentials.
How routing works
The dialer checks, in order:
- Per-phishlet forcing — if the destination host belongs to an enabled
phishlet that sets
proxy: truein its YAML, the connection always goes through the exit proxy (no suffix matching needed):proxy: true # force this phishlet's whole upstream through the exitUse it for origins that reject datacenter egress IPs outright (Cloudflare-protected logins: claude, gitlab, chatgpt, cloudflare dash). Hosts shared by several phishlets route through the proxy if ANY owner forces it. Hot-reload applies immediately — the selector reads the live phishlet state on every dial (debug log:
via exit proxy (phishlet X force)). - Global domain-suffix
routes— everything else matches the suffix list:
claude phishlet fetch of claude.ai → proxy: true force → SOCKS5 residential exit
google phishlet fetch of accounts.google.com → suffix match → SOCKS5 residential exit
ms365 phishlet fetch of login.live.com → no match → node IP (direct)
Both the proxy transport and the CONNECT path honor the selector (the HTTP client uses the proxied dialer for CONNECT when a proxy is set).
Field doctrine
| Target | Egress | Why |
|---|---|---|
| residential SOCKS5 (routes) | datacenter exits are pre-flagged; residential passes the lookup | |
| ms365 (corporate/work accounts) | direct | a residential login on a work account looks more suspicious to Entra Conditional Access |
| Cloudflare-protected logins (claude, gitlab, chatgpt, cloudflare) | residential SOCKS5 (proxy: true) |
CF challenges datacenter IPs in a loop the victim can never clear |
Keep tlsfp: chrome set whenever the exit is used — uTLS presents a Chrome
ClientHello from the node so the origin sees a consistent (residential IP +
Chrome TLS) story. Check GET /proxy still shows tlsfp after any proxy
edit — the field can silently end up empty, which disables uTLS entirely.
Verify after configuring: render the phishlet’s identifier page and check the egress IP the identity provider would see (a routed fetch through a residential exit should show the exit’s IP, not the node’s).