protox-gatekeeper 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from protox_gatekeeper.core import GateKeeper
2
+
3
+ __all__ = ['GateKeeper']
@@ -0,0 +1,92 @@
1
+ import logging
2
+
3
+ import requests
4
+
5
+ from protox_gatekeeper.session import make_tor_session
6
+ from protox_gatekeeper.verify import is_tor_exit, get_public_ip
7
+ from protox_gatekeeper.ops import download_file as _download
8
+ from protox_gatekeeper.geo import geo_lookup
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class GateKeeper:
14
+ def __init__(self, socks_port: int = 9150, geo=False, timeout: int = 10):
15
+ """
16
+ GateKeeper constructor.
17
+
18
+ Args:
19
+ socks_port (int, optional): The socks port to use. Defaults to 9150.
20
+ geo (bool, optional): Whether to use geo. Defaults to False.
21
+ timeout (int, optional): The timeout to wait for a response. Defaults to 10.
22
+ """
23
+
24
+ self._session: requests.Session
25
+ self.exit_ip: str
26
+ self.clearnet_ip: str
27
+
28
+ # 1) Measure clearnet IP (no proxies)
29
+ clearnet = requests.Session()
30
+ self.clearnet_ip = get_public_ip(session=clearnet, timeout=timeout)
31
+
32
+ # 2) Create Tor session
33
+ self._session = make_tor_session(port=socks_port)
34
+
35
+ # 3) Verify Tor routing
36
+ if not is_tor_exit(session=self._session, timeout=timeout):
37
+ raise RuntimeError('Tor verification failed. Execution aborted.')
38
+
39
+ # 4) Measure Tor exit IP
40
+ self.exit_ip = get_public_ip(session=self._session, timeout=timeout)
41
+
42
+ # 5) Log the transition
43
+ logger.info(f'Tor verified: {self.clearnet_ip} -> {self.exit_ip}')
44
+
45
+ # 6) Location data
46
+ if geo:
47
+ location = geo_lookup(self.exit_ip)
48
+ if location:
49
+ logger.info(f'Tor exit location: {location}')
50
+ else:
51
+ logger.info('Tor exit location: Unavailable')
52
+
53
+ def __repr__(self) -> str:
54
+ return f'<GateKeeper: {self.clearnet_ip} -> tor_exit: {self.exit_ip}>'
55
+
56
+ def __enter__(self) -> "GateKeeper":
57
+ return self
58
+
59
+ def __exit__(self, exc_type, exc, tb):
60
+ self._session.close()
61
+
62
+ @property
63
+ def session(self) -> requests.Session:
64
+ """ Exposes the verified session if needed. """
65
+ return self._session
66
+
67
+ @property
68
+ def tor_exit(self) -> str:
69
+ """ Returns the Tor exit IP address. """
70
+ return self.exit_ip
71
+
72
+ def download(self, url: str, target_path: str, timeout: int = 30,
73
+ chunk_size: int = 8192):
74
+ """
75
+ Attempts to download the given url to the target path.
76
+
77
+ Args:
78
+ url (str): The url to download.
79
+ target_path (str): The target path.
80
+ timeout (int, optional): The timeout to wait for a response.
81
+ chunk_size (int, optional): The chunk size to use for download.
82
+ """
83
+ logger.info(
84
+ f'[Tor {self.tor_exit}] downloading {url} -> {target_path}')
85
+
86
+ return _download(
87
+ session=self._session,
88
+ url=url,
89
+ target_path=target_path,
90
+ timeout=timeout,
91
+ chunk_size=chunk_size
92
+ )
@@ -0,0 +1,26 @@
1
+ import logging
2
+
3
+ import requests
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ def geo_lookup(ip: str) -> str | None:
9
+ try:
10
+ r = requests.get(
11
+ url=f'https://ipapi.co/{ip}/json',
12
+ timeout=10,
13
+ headers={'User-Agent': 'GateKeeper/0.1.0'}
14
+ )
15
+ if r.status_code != 200:
16
+ return None
17
+
18
+ data = r.json()
19
+ city = data.get('city')
20
+ country = data.get('country')
21
+ if city and country:
22
+ return f'{city}, {country}'
23
+
24
+ except Exception as e:
25
+ logger.info(f'Unable to get geolocation for {ip}: {e}')
26
+ return None
@@ -0,0 +1,26 @@
1
+ import os
2
+
3
+ import requests
4
+
5
+
6
+ def download_file(
7
+ session: requests.Session,
8
+ url: str,
9
+ target_path: str,
10
+ timeout: int,
11
+ chunk_size: int
12
+ ) -> None:
13
+ if not isinstance(session, requests.Session):
14
+ raise TypeError('A verified requests.Session is required.')
15
+
16
+ dir_path = os.path.dirname(target_path)
17
+ if dir_path:
18
+ os.makedirs(dir_path, exist_ok=True)
19
+
20
+ response = session.get(url, stream=True, timeout=timeout)
21
+ response.raise_for_status()
22
+
23
+ with open(target_path, 'wb') as f:
24
+ for chunk in response.iter_content(chunk_size=chunk_size):
25
+ if chunk:
26
+ f.write(chunk)
@@ -0,0 +1,11 @@
1
+ import requests
2
+
3
+
4
+ def make_tor_session(port: int) -> requests.Session:
5
+ socks = f'socks5h://127.0.0.1:{port}'
6
+ s = requests.Session()
7
+ s.proxies = {
8
+ 'http': socks,
9
+ 'https': socks,
10
+ }
11
+ return s
@@ -0,0 +1,14 @@
1
+ import requests
2
+
3
+
4
+ def get_public_ip(session: requests.Session, timeout: int) -> str:
5
+ r = session.get(url='https://api.ipify.org/', timeout=timeout)
6
+ r.raise_for_status()
7
+ return r.text.strip()
8
+
9
+
10
+ def is_tor_exit(session: requests.Session, timeout: int) -> bool:
11
+ r = session.get(url='https://check.torproject.org/api/ip', timeout=timeout)
12
+ r.raise_for_status()
13
+ data = r.json()
14
+ return bool(data.get('IsTor', False))
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: protox-gatekeeper
3
+ Version: 0.1.1
4
+ Summary: Fail-closed Tor session enforcement for Python HTTP(S) traffic
5
+ Author: Tom Erik Harnes
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Tom Erik Harnes
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.10
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: requests
32
+ Requires-Dist: pysocks
33
+ Dynamic: license-file
34
+
35
+ # ProtoX GateKeeper
36
+
37
+ **ProtoX GateKeeper** is a small, opinionated Python library that enforces
38
+ **fail‑closed Tor routing** for HTTP(S) traffic.
39
+
40
+ The goal is simple:
41
+
42
+ > If Tor is not active and verified, **nothing runs**.
43
+
44
+ GateKeeper is designed to be *fire‑and‑forget*: create a client once, then perform network operations with a hard guarantee that traffic exits through the Tor network.
45
+
46
+ ---
47
+
48
+ ## What GateKeeper Is
49
+
50
+ - A **Tor‑verified HTTP client**
51
+ - A thin wrapper around `requests.Session`
52
+ - Fail‑closed by default (no silent clearnet fallback)
53
+ - Observable (exit IP, optional geo info)
54
+ - Suitable for scripts, tooling, and automation
55
+
56
+ ---
57
+
58
+ ## What GateKeeper Is NOT
59
+
60
+ - ❌ A Tor controller
61
+ - ❌ A crawler or scanner
62
+ - ❌ An anonymization silver bullet
63
+ - ❌ A replacement for Tor Browser
64
+
65
+ GateKeeper enforces transport routing only. You are still responsible for *what* you do with it.
66
+
67
+ ---
68
+
69
+ ## Requirements
70
+
71
+ - A locally running Tor client
72
+ - SOCKS proxy enabled (default: `127.0.0.1:9150`)
73
+
74
+ On Windows this usually means **Tor Browser** running in the background.
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ### From source (development)
81
+
82
+ ```bash
83
+ pip install -e .
84
+ ```
85
+
86
+ (Recommended while developing or testing.)
87
+
88
+ ---
89
+
90
+ ## Basic Usage
91
+
92
+ ```python
93
+ import logging
94
+ from protox_gatekeeper import GateKeeper
95
+
96
+ logging.basicConfig(
97
+ level=logging.INFO,
98
+ format='[%(levelname)s] %(name)s - %(message)s'
99
+ )
100
+
101
+ gk = GateKeeper(geo=True)
102
+
103
+ gk.download(
104
+ "https://httpbin.org/bytes/1024",
105
+ "downloads/test.bin"
106
+ )
107
+ ```
108
+
109
+ ### Example output
110
+
111
+ ```
112
+ [INFO] gatekeeper.core - Tor verified: 89.xxx.xxx.xxx -> 185.xxx.xxx.xxx
113
+ [INFO] gatekeeper.core - Tor exit location: Brandenburg, DE
114
+ [INFO] gatekeeper.core - [Tor 185.xxx.xxx.xxx] downloading https://httpbin.org/bytes/1024 -> downloads/test.bin
115
+ ```
116
+
117
+ This confirms:
118
+ - clearnet IP was measured
119
+ - Tor routing was verified
120
+ - all traffic used the Tor exit shown
121
+
122
+ ---
123
+
124
+ ## API Overview
125
+
126
+ ### `GateKeeper(...)`
127
+
128
+ ```python
129
+ gk = GateKeeper(
130
+ socks_port=9150,
131
+ geo=False
132
+ )
133
+ ```
134
+
135
+ **Parameters**:
136
+ - `socks_port` *(int)* – Tor SOCKS port (default: `9150`)
137
+ - `geo` *(bool)* – Enable best‑effort Tor exit geolocation (optional)
138
+
139
+ Raises `RuntimeError` if Tor routing cannot be verified.
140
+
141
+ ---
142
+
143
+ ### `download(url, target_path)`
144
+
145
+ Downloads a resource **through the verified Tor session**.
146
+
147
+ ```python
148
+ gk.download(url, target_path)
149
+ ```
150
+
151
+ - `url` – HTTP(S) URL
152
+ - `target_path` – Full local file path (directories created automatically)
153
+
154
+ ---
155
+
156
+ ## Design Principles
157
+
158
+ - **Fail closed**: no Tor → no execution
159
+ - **Single verification point** (during construction)
160
+ - **No global state**
161
+ - **No logging configuration inside the library**
162
+ - **Session reuse without re‑verification**
163
+
164
+ Logging is emitted by the library, but **configured by the application**.
165
+
166
+ ---
167
+
168
+ ## Logging
169
+
170
+ GateKeeper uses standard Python logging:
171
+
172
+ ```python
173
+ import logging
174
+ logging.basicConfig(level=logging.INFO)
175
+ ```
176
+
177
+ The library does **not** call `logging.basicConfig()` internally.
178
+
179
+ ---
180
+
181
+ ## Security Notes
182
+
183
+ - Tor exit IPs may rotate over time
184
+ - Geo information is best‑effort and may be unavailable (rate‑limits, CAPTCHAs)
185
+ - GateKeeper guarantees routing, not anonymity
186
+
187
+ ---
188
+
189
+ ## License
190
+
191
+ MIT License
192
+
193
+ ---
194
+
195
+ ## Status
196
+
197
+ - Version: **v0.1.1**
198
+ - Phase 1 complete
199
+ - API intentionally minimal
200
+
201
+ Future versions may add optional features such as:
202
+ - circuit rotation
203
+ - ControlPort support
204
+ - higher‑level request helpers
205
+
206
+ Without breaking the core contract.
207
+
@@ -0,0 +1,11 @@
1
+ protox_gatekeeper/__init__.py,sha256=HisDC-NCbMlkrNkrLRdANILoBqw03ssqFp6BTsXm6Us,75
2
+ protox_gatekeeper/core.py,sha256=Z4LEY1WcSbjMDU3OS1rf7kPOTry9NJq5-quzACxPvKg,3062
3
+ protox_gatekeeper/geo.py,sha256=AdjHfZqjP0w5ujHFI_WV7c5XWnDTzqkVL32KeJQojYo,641
4
+ protox_gatekeeper/ops.py,sha256=Kmr8lU8Ohe81URHHNUlUJ0S71aZ1uGgyiztLBPsOUO4,690
5
+ protox_gatekeeper/session.py,sha256=0qGZF44EHr7CP-bSM0awtq4F-5LWiIb4w87mV7Yocfk,235
6
+ protox_gatekeeper/verify.py,sha256=bsHK3f16sRV8cHo30AY82SB1vb_Svu-US7IhghTSQ6E,452
7
+ protox_gatekeeper-0.1.1.dist-info/licenses/LICENSE,sha256=EE75Vy9_csDDBRXBF4uVQWxYu1YpzXTcmaeuVcPOjl4,1093
8
+ protox_gatekeeper-0.1.1.dist-info/METADATA,sha256=g3ib-yUF_vfoi6oD3S6z9BfkIfP9-YylLl7cXnCq1a4,5215
9
+ protox_gatekeeper-0.1.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
10
+ protox_gatekeeper-0.1.1.dist-info/top_level.txt,sha256=KOVL4YUpiWQLGjCvx2SmP8VSsA6hPcEvKCd4k6kPl8s,18
11
+ protox_gatekeeper-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tom Erik Harnes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ protox_gatekeeper