Skip to main content

🦊 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 -p and FoxPipe will prompt you interactively (via getpass) — this is the recommended default, since anything passed with -p is visible in your shell history and to other local users via ps. For scripts and automation, use --password-file <path> (reads the first line of a file — keep it chmod 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

# 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 spake2 library) — 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_id is 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 100 for 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-compress is 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-compress rows 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)

Source distribution for foxpipe 2.1.1
File Size Uploaded
foxpipe-2.1.1.tar.gz 10.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for foxpipe 2.1.1
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

2.1.1 This release

2 release files

2.1.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.9.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page