Skip to content

Commit 8ea6614

Browse files
committed
Add proxy support
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
1 parent d6cc23f commit 8ea6614

8 files changed

Lines changed: 100 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- Add `--proxy` flag for explicit proxy configuration (supports HTTP, HTTPS,
14+
and SOCKS5 schemes). Useful for routing traffic through TOR
15+
(e.g. `socks5://127.0.0.1:9050`).
16+
- Add `proxy` configuration option in config file (both top-level and per-bin).
17+
- Proxy resolution priority: `--proxy` flag > config file > environment
18+
variables (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`).
19+
1120
## [2.1.1] - 2025-09-08
1221

1322
### Fixed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ Display a paste:
9292

9393
privatebin show https://privatebin.net/?420fc9597328c72f#EezApNVTTRUuEkt1jj7r9vSfewLBvUohDSXWuvPEs1bF
9494

95+
Create a paste through a SOCKS5 proxy (e.g. TOR):
96+
97+
cat resume.txt | privatebin --proxy socks5://127.0.0.1:9050 create
98+
9599
## Documentation
96100

97101
For detailed information on all CLI commands and features, check out

client.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type (
5252
customHTTPHeaderFields map[string]string
5353
userAgent string
5454
tlsConfig *tls.Config
55+
proxyURL *url.URL
5556
}
5657

5758
Option func(c *Client)
@@ -173,6 +174,12 @@ func WithTLSConfig(tlsConfig *tls.Config) Option {
173174
}
174175
}
175176

177+
func WithProxyURL(proxyURL url.URL) Option {
178+
return func(c *Client) {
179+
c.proxyURL = &proxyURL
180+
}
181+
}
182+
176183
func NewClient(endpoint url.URL, options ...Option) *Client {
177184
client := &Client{
178185
endpoint: endpoint,
@@ -183,7 +190,7 @@ func NewClient(endpoint url.URL, options ...Option) *Client {
183190
option(client)
184191
}
185192

186-
client.httpClient = defaultPooledClient(client.tlsConfig)
193+
client.httpClient = defaultPooledClient(client.tlsConfig, client.proxyURL)
187194

188195
return client
189196
}

cmd/privatebin/cfg.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type (
3737
GZip *bool `json:"gzip"`
3838
SkipTLSVerify *bool `json:"skip-tls-verify"`
3939
Formatter string `json:"formatter"`
40+
Proxy string `json:"proxy"`
4041
ExtraHeaderFields map[string]string `json:"extra-header-fields"`
4142
}
4243

@@ -48,6 +49,7 @@ type (
4849
GZip bool `json:"gzip"`
4950
SkipTLSVerify bool `json:"skip-tls-verify"`
5051
Formatter string `json:"formatter"`
52+
Proxy string `json:"proxy"`
5153
ExtraHeaderFields map[string]string `json:"extra-header-fields"`
5254
}
5355
)
@@ -112,6 +114,10 @@ func loadCfgFile(path string) (*Cfg, error) {
112114
binCfg.SkipTLSVerify = &cfg.SkipTLSVerify
113115
}
114116

117+
if binCfg.Proxy == "" {
118+
binCfg.Proxy = cfg.Proxy
119+
}
120+
115121
if binCfg.ExtraHeaderFields == nil {
116122
binCfg.ExtraHeaderFields = cfg.ExtraHeaderFields
117123
}

cmd/privatebin/main.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var (
6262
insecure bool
6363
confirmBurn bool
6464
skipTLSVerify bool
65+
proxy string
6566

6667
rootCmd = &cobra.Command{
6768
Use: "privatebin",
@@ -136,6 +137,23 @@ var (
136137
)
137138
}
138139

140+
proxyAddr := binCfg.Proxy
141+
if proxy != "" {
142+
proxyAddr = proxy
143+
}
144+
145+
if proxyAddr != "" {
146+
proxyURL, err := url.Parse(proxyAddr)
147+
if err != nil {
148+
return fmt.Errorf("cannot parse proxy url %q: %w", proxyAddr, err)
149+
}
150+
151+
clientOptions = append(
152+
clientOptions,
153+
privatebin.WithProxyURL(*proxyURL),
154+
)
155+
}
156+
139157
host, err := url.Parse(binCfg.Host)
140158
if err != nil {
141159
return fmt.Errorf("cannot parse %q bin %q host: %w", binCfg.Name, binCfg.Host, err)
@@ -306,6 +324,7 @@ func init() {
306324
rootCmd.PersistentFlags().StringVarP(&cfgPath, "config", "c", "", "the config file (default is $HOME/.config/privatebin/config.json)")
307325
rootCmd.PersistentFlags().StringVarP(&binName, "bin", "b", "", "the name of the privatebin instance to use (default \"\")")
308326
rootCmd.PersistentFlags().StringSliceVarP(&extraHeaderFields, "header", "H", []string{}, "extra HTTP header fields to include in the request sent")
327+
rootCmd.PersistentFlags().StringVar(&proxy, "proxy", "", "proxy URL to use for requests (e.g. socks5://127.0.0.1:9050 for TOR)")
309328

310329
createCmd.Flags().StringVar(&expire, "expire", "", "the time to live of the paste")
311330
createCmd.Flags().BoolVar(&openDiscussion, "open-discussion", false, "enable discussion on the paste")

doc/privatebin.1.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ section: 1
1111
# SYNOPSIS
1212
**privatebin** [-h | -\-help] [-v | -\-version] [-\-bin=\<name\>]\
1313
\ \ \ \ \ \ \ \ \ \ \ [-\-config=\<filename\>] [-\-header=\<key=value\>]\
14-
\ \ \ \ \ \ \ \ \ \ \ [-\-output=\<format\>] \<command\> [\<args\>]
14+
\ \ \ \ \ \ \ \ \ \ \ [-\-output=\<format\>] [-\-proxy=\<url\>]\
15+
\ \ \ \ \ \ \ \ \ \ \ \<command\> [\<args\>]
1516

1617
# DESCRIPTION
1718
A minimalist, open source command line interface for **PrivateBin**
@@ -37,6 +38,12 @@ instances.
3738
**-o, -\-output** \<format\>
3839
: The output format can be \"\" or \"json\" (default \"\").
3940

41+
**-\-proxy** \<url\>
42+
: Proxy URL to use for requests. Supports HTTP, HTTPS, and SOCKS5
43+
schemes (e.g. socks5://127.0.0.1:9050 for TOR). This flag overrides
44+
the proxy value from the configuration file and the **HTTP_PROXY**,
45+
**HTTPS_PROXY**, and **ALL_PROXY** environment variables.
46+
4047
# COMMANDS
4148

4249
**privatebin-create(1)**
@@ -54,6 +61,20 @@ Create a paste on the default privatebin instance:
5461

5562
$ cat example.txt | privatebin create
5663

64+
Create a paste through a SOCKS5 proxy (e.g. TOR):
65+
66+
$ cat example.txt | privatebin --proxy socks5://127.0.0.1:9050 create
67+
68+
# ENVIRONMENT
69+
70+
**HTTP_PROXY**, **HTTPS_PROXY**, **ALL_PROXY**
71+
: When no **-\-proxy** flag is provided and no **proxy** configuration
72+
value is set, the standard proxy environment variables are honored.
73+
74+
**NO_PROXY**
75+
: A comma-separated list of host names or IP addresses for which the
76+
proxy should not be used.
77+
5778
# SEE ALSO
5879
**privatebin.conf**(5)
5980

doc/privatebin.conf.5.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ instance configured in the **config.json**.
3737
**skip-tls-verify** _bool_ (default: false)
3838
: Skip TLS certificate verification when connecting to the privatebin instance.
3939

40+
**proxy** _string_
41+
: Proxy URL to use for all requests. Supports HTTP, HTTPS, and SOCKS5
42+
schemes (e.g. "socks5://127.0.0.1:9050" for TOR). When set, overrides
43+
the **HTTP_PROXY**, **HTTPS_PROXY**, and **ALL_PROXY** environment
44+
variables. Can be overridden per-bin or by the **-\-proxy** CLI flag.
45+
4046
**extra-header-fields** _object<string, string>_
4147
: The extra HTTP header fields to include in the request sent.
4248

@@ -72,6 +78,12 @@ instance configured in the **config.json**.
7278
**skip-tls-verify** _bool_
7379
: Skip TLS certificate verification when connecting to the privatebin instance.
7480

81+
**proxy** _string_
82+
: Proxy URL to use for requests to this bin instance. Supports HTTP,
83+
HTTPS, and SOCKS5 schemes. Overrides the top-level **proxy** value
84+
and the proxy environment variables. Can be overridden by the
85+
**-\-proxy** CLI flag.
86+
7587
**extra-header-fields** _object<string, string>_
7688
: The extra HTTP header fields to include in the request sent.
7789

@@ -121,6 +133,18 @@ A bit more complete configuration file:
121133
"burn-after-reading": true
122134
}
123135

136+
Configuration using a SOCKS5 proxy (e.g. TOR):
137+
138+
{
139+
"proxy": "socks5://127.0.0.1:9050",
140+
"bin": [
141+
{
142+
"name": "",
143+
"host": "https://privatebin.net"
144+
}
145+
]
146+
}
147+
124148
# FILES
125149

126150
_~/.config/privatebin/config.json_

utils.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"encoding/base64"
2121
"net"
2222
"net/http"
23+
"net/url"
2324
"runtime"
2425
"time"
2526
)
@@ -52,15 +53,20 @@ func decode64(s string) ([]byte, error) {
5253
return base64.RawStdEncoding.DecodeString(s)
5354
}
5455

55-
func defaultPooledClient(tlsConfig *tls.Config) *http.Client {
56+
func defaultPooledClient(tlsConfig *tls.Config, proxyURL *url.URL) *http.Client {
5657
dial := &net.Dialer{
5758
Timeout: 30 * time.Second,
5859
KeepAlive: 30 * time.Second,
5960
DualStack: true,
6061
}
6162

63+
proxyFunc := http.ProxyFromEnvironment
64+
if proxyURL != nil {
65+
proxyFunc = http.ProxyURL(proxyURL)
66+
}
67+
6268
transport := &http.Transport{
63-
Proxy: http.ProxyFromEnvironment,
69+
Proxy: proxyFunc,
6470
DialContext: dial.DialContext,
6571
MaxIdleConns: 100,
6672
IdleConnTimeout: 90 * time.Second,

0 commit comments

Comments
 (0)