🦊 FoxPipe v2.1.1
Secure • Simple • Reliable Data Streaming
FoxPipe is a minimalist CLI tool for end-to-end encrypted, optionally compressed data transfer between two machines — no setup, no accounts, just a shared password.
More about me / other projects: abhrankan.netlify.app
v2.0 replaces the v1 handshake (a password-derived key sent implicitly over the wire) with a PAKE-based handshake providing forward secrecy. v2 is not wire-compatible with v1 — both sides must be on v2. A version mismatch fails cleanly with an explicit error rather than silently downgrading.
🚀 Why FoxPipe?
Simple No servers, no login. Just run sender and receiver.
Efficient
Built-in zlib streaming compression reduces bandwidth usage automatically.
Secure by Design Uses a SPAKE2 PAKE handshake (never sends the password or a password hash over the wire) combined with an ephemeral X25519 key exchange for forward secrecy, then AES-256-GCM (AEAD) to encrypt the actual stream.
Resilient Includes chunk limits, decompression guards, session validation, and timeouts.
📥 Installation
Install directly from PyPI:
pip install foxpipe
🛠️ Usage
⚠️ On passwords: omit
-pand FoxPipe will prompt you interactively (viagetpass) — this is the recommended default, since anything passed with-pis visible in your shell history and to other local users viaps. For scripts and automation, use--password-file <path>(reads the first line of a file — keep itchmod 600) instead of-p.
1️⃣ Receiver (Destination)
Start this first:
foxpipe receive 8080 > backup.sql
# Password:
Allow external connections:
foxpipe receive 8080 --public > backup.sql
2️⃣ Sender (Source)
cat backup.sql | foxpipe send 192.168.1.5 8080
# Password:
📦 Advanced Usage
📁 Directory Transfer (Recommended)
# Sender
tar -cf - ./project | foxpipe send 1.2.3.4 9000
# Receiver
foxpipe receive 9000 | tar -xf -
📄 Direct File Transfer
foxpipe send 1.2.3.4 8080 --file image.iso
🚫 Disable Compression
For already compressed files:
foxpipe send 1.2.3.4 8080 --file video.mp4 --no-compress
🤖 Scripted / Non-Interactive Use
Interactive prompts aren't usable in cron jobs or CI. Use --password-file instead of -p — it keeps the password out of shell history and ps:
echo "secure-pass" > pw.txt && chmod 600 pw.txt
# Receiver
foxpipe receive 9000 --password-file pw.txt > backup.sql
# Sender
foxpipe send 1.2.3.4 9000 --password-file pw.txt --file backup.sql
-p/--password is still supported for quick one-off manual use, but prints a warning to stderr and should be avoided on shared/multi-user machines.
🔒 Security Model (v2.0)
- Handshake: SPAKE2 password-authenticated key exchange (symmetric, via the
spake2library) — proves both sides know the shared password without ever sending the password, or anything derived from it alone, over the wire - Forward Secrecy: an ephemeral X25519 key pair is generated fresh for every connection; the PAKE output and the X25519 shared secret are combined via HKDF-SHA256 to derive the session key. A future password leak cannot decrypt previously captured sessions.
- Key Confirmation: before any data streams, each side sends
HMAC-SHA256(K_confirm, direction_label)and verifies the peer's tag (constant-time comparison). A wrong password is caught at the handshake, with zero bytes streamed — not discovered later via a failed AES-GCM decrypt. - Encryption: AES-256-GCM (authenticated encryption per chunk), keyed from the handshake's derived
K_payload - Integrity & Authenticity: provided by AES-GCM (AEAD) for data, and by the handshake's HMAC confirmation step for the session key itself
v1's Scrypt-derived-key handshake is gone. It was vulnerable to offline dictionary attacks (the salt was sent in cleartext, and the same static password-derived key both authenticated and encrypted every session — no forward secrecy). v2's PAKE handshake closes both gaps.
The random
session_idis still sent on the wire but is no longer cryptographically load-bearing — every connection already gets a fresh, unique session key from the PAKE + X25519 exchange, which supersedes what session-ID binding was doing in v1. It's kept as informational metadata only.
🧭 Handshake Flow
sequenceDiagram
participant S as Sender
participant R as Receiver
Note over S,R: 1. Password-Authenticated Key Exchange
S->>R: SPAKE2 message (33 bytes)
R->>S: SPAKE2 message (33 bytes)
Note over S,R: Both derive shared PAKE secret<br/>(password never sent)
Note over S,R: 2. Ephemeral Forward-Secrecy Exchange
S->>R: X25519 ephemeral public key (32 bytes)
R->>S: X25519 ephemeral public key (32 bytes)
Note over S,R: Both derive X25519 shared secret
Note over S,R: 3. Key Derivation
Note over S,R: HKDF-SHA256(PAKE secret + X25519 secret)<br/>→ K_confirm, K_payload
Note over S,R: 4. Key Confirmation
S->>R: HMAC-SHA256(K_confirm, "sender") (32 bytes)
R->>S: HMAC-SHA256(K_confirm, "receiver") (32 bytes)
Note over S,R: Constant-time verify.<br/>Wrong password fails here — zero bytes streamed.
Note over S,R: 5. Encrypted Stream
S->>R: AES-256-GCM chunks (K_payload, counter-based nonce per chunk)
Every step above is exactly what Security Model (v2.0) describes — this is just the same protocol laid out as a sequence rather than prose, to make the "no plaintext password, no data before confirmation" property easier to verify at a glance.
⚠️ Safety Measures
- Max Chunk Size: 10 MB
- Session Timeout: 300 seconds (idle)
- Connection Timeout: 15 seconds
- Safe Streaming Decompression: Protects against zip-bomb style attacks
- DoS Protection: Receiver enforces a global transfer limit (default 5GB).
Adjust using
--limit(e.g.,--limit 100for 100GB).
🧠 Design Notes
- Uses streaming compression (single zlib stream)
- Uses a deterministic, per-session counter nonce (0, 1, 2, ... per chunk, 12 bytes, big-endian) rather than a random one — since the session key is already fresh per connection (see Forward Secrecy above), a counter gives guaranteed nonce uniqueness under that key instead of relying on the (already very large) birthday bound for random 96-bit nonces. The nonce is still sent on the wire per chunk, so this doesn't change the wire format.
- Uses SPAKE2 (symmetric) + ephemeral X25519 + HKDF-SHA256 for session key derivation
- Uses constant-time comparison for the handshake's key-confirmation HMAC
- Avoids buffering entire files → supports large transfers
- Minimal protocol → low overhead, easy to audit
- Handshake messages are fixed-size (SPAKE2 message 33 bytes, X25519 pubkey 32 bytes, confirmation tag 32 bytes) — no length-prefixing needed for the handshake itself
⚡ Quick Example
# Receiver
foxpipe receive 9000 --public > file.txt
# Password:
# Sender
foxpipe send <IP> 9000 --file file.txt
# Password:
📊 Benchmarks
benchmarks/bench.py runs a real sender and receiver over loopback, checks on every run that the received bytes are identical to what was sent, and prints a table. To reproduce:
python3 benchmarks/bench.py --size-mib 256 --runs 5
Measured on an AMD64 Family 25 Model 80 CPU (8 logical CPUs), Windows 11, Python 3.14.3, FoxPipe 2.1.0, 256 MiB payload, median of 5 runs:
| Payload | Mode | zlib ratio | Time (s) | Throughput (MiB/s) |
|---|---|---|---|---|
| log-like text | zlib on | 5.2x | 3.67 | 69.7 |
| log-like text | --no-compress |
5.2x | 0.59 | 435.9 |
| random bytes | zlib on | 1.0x | 8.07 | 31.7 |
| random bytes | --no-compress |
1.0x | 0.69 | 372.8 |
Each time includes a fixed cost of about 0.24 s per transfer (interpreter start, SPAKE2 handshake, shutdown).
How to read this:
- Loopback has no network, so this measures FoxPipe's own CPU cost. It is a ceiling, not a prediction of LAN or internet speed.
- Compression is the bottleneck, not encryption. Without it FoxPipe moves roughly 370–440 MiB/s (about 3–3.7 Gbit/s) here; with zlib on it tops out near 70 MiB/s on compressible text and 32 MiB/s on incompressible data, below what a gigabit link can carry (about 119 MiB/s).
- Rule of thumb derived from these numbers, assuming the link is the only other bottleneck: above roughly 0.5 Gbit/s,
--no-compressis faster even for compressible data. On slower links, compression wins on compressible data because it puts about 5x less on the wire. For already-compressed data it never helps. - The two
--no-compressrows do identical work yet differ by about 15%, so treat the figures as good to roughly ±15%. - These are one machine's results. Sender and receiver share its cores, so core count matters. Run the script on yours.
⚠️ Limitations
- Single connection only
- No resume support
- No file metadata (name/size handled externally)
🦊 Philosophy
Build simple tools that are hard to misuse and easy to trust.
Metadata
Release files for foxpipe 2.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| foxpipe-2.1.1.tar.gz | 10.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| foxpipe-2.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.8 kB
Release files / foxpipe-2.1.1.tar.gz
| Download URL | foxpipe-2.1.1.tar.gz |
|---|---|
| Size | 10.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8265a741af7d5d68b3db6f829c9d488c875f830ac6d99506ce842ebe4e6d7bfc
|
|
BLAKE2b-256 checksum How to use checksums |
0b66aea7e7320e185f53fbbbd0e03da18b71c1acbdd2577357a162ba914cb480
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / foxpipe-2.1.1-py3-none-any.whl
| Download URL | foxpipe-2.1.1-py3-none-any.whl |
|---|---|
| Size | 10.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a4d3f91f9c20c175820e156628428245eee0588b5950c43243a1bb16682a78b
|
|
BLAKE2b-256 checksum How to use checksums |
5448d5b85862aa37ec4584ec117e116c166bcf083fd19707b709366ac94a692e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Security for foxpipe 2.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.