Metadata-Version: 2.4
Name: wireshell
Version: 0.14.3
Summary: Passwordless, CA-free ssh/sshd on the WireSeal wire: auto/tcp/udp transport, SOCKS -D, safe rendering.
Author-email: cc <wireseal@proton.me>
License: Apache-2.0
Project-URL: Homepage, https://git.disroot.org/wireseal/wireshell
Project-URL: Repository, https://git.disroot.org/wireseal/wireshell.git
Keywords: ssh,sshd,noise-protocol,roaming,mosh,remote-shell,wireseal,passwordless,socks
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
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: Topic :: System :: Networking
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography<48,>=41
Requires-Dist: noiseprotocol<0.4,>=0.3
Dynamic: license-file

# wireshell

**A passwordless SSH replacement whose sessions survive changing networks.**

Open a shell on a server. Close your laptop. Walk from wifi to cellular. Open it
again — you're still in the same shell, mid-command, nothing lost. No reconnect,
no re-login, no `known_hosts` prompt. `wssh myhost` is the whole client: no
passwords, no certificate authority, no key-exchange ceremony. A key *is* the
identity.

Your shell lives on the server, like tmux — so when the link dies (a flight, hours
offline, a dead router) wireshell just reattaches and repaints the exact screen the
moment the network comes back.

```bash
pip install wireshell
wsshd            # server: binds [::]:2022, generates its identity, serves
wssh myhost      # client: that's it
```

Tested on real hardware:

- wifi → LTE handovers across NAT, with `vim` and `top` held open through the change
- long network drops on both client and server
- long sleep (suspend/resume) on both client and server

---

## Why wireshell

- **It roams.** Sessions bind to your key, not your IP address. Change networks
  mid-session and the shell follows you — mosh's best trick, but without needing
  SSH to log in or tmux to persist. It's one tool.
- **No ceremony.** No passwords, no CA, no `ssh-keygen` dance, no host-key prompts
  to click past. You run `wssh myhost` and you're in.
- **Persistent by default.** The shell runs server-side. Detach, reattach, survive
  a multi-hour outage — built in, not bolted on.
- **Self-sovereign access control.** Users self-enroll into a pending queue; you
  approve them by fingerprint. No directory service, no coordination server, no
  cloud — just local files you own.
- **Fast.** A roughly one-round-trip Noise handshake plus predictive local echo
  make it feel quicker than SSH, most noticeably on high-latency links.
- **Self-contained.** Pure-Python payload; the single compiled dependency comes
  from your distribution. One `pip install`, or a `.deb` that runs on any
  Python 3.11+ and any CPU architecture.
- **Batteries included.** Port forwarding (`-L`/`-D`/`-R`), a SOCKS5 proxy, file
  copy (`wscp`), tmux-style named sessions, and an admin tool (`wsshctl`) — all in
  the box.

---

## Quick start

**Server** — just run it:

```bash
wsshd
```

It binds `[::]:2022`, creates or loads its identity under `$WSSH_HOME` (announcing
which), and serves. Everything else is optional and can live in a config file it
finds automatically (`$WSSH_HOME/wsshd.conf` or `/etc/wireshell/wsshd.conf`).

**Client** — find your key, ask to join, connect:

```bash
wssh myhost --whoami          # prints the key the server will see for you
wssh myhost --request alice   # queue for approval under the handle "alice"
#   on the server:  wsshctl approve alice
wssh myhost                   # you're in — and you stay in
```

That's the entire flow: no password to set, no key to copy into a file by hand, no
CA to stand up.

---

## Roaming and transports

The handshake always runs over TCP; the session then runs over one of:

- **auto** (default) — TCP bootstrap, then a ~1s authenticated UDP probe. If UDP
  answers, the session runs over UDP **with roaming**: it survives IP changes and
  reattaches. If UDP is blocked, it runs over the TCP stream already open, without
  roaming. The probe is authenticated with the session keys, so it can't be spoofed
  to steer the choice.
- **`--udp`** — force UDP; fail fast if it's blocked.
- **`--tcp`** — force TCP; works through anything TCP does; no roaming.

Roaming needs UDP because a TCP connection is pinned to its address. `auto` gives
you roaming wherever UDP is open and graceful fallback where it isn't — you don't
have to think about it.

## Session persistence

Your shell lives on the server, not in the client. When the transport drops, the
shell keeps running (background jobs included) and the server holds it. Once you've
connected, `wssh` reconnects on its own for as long as it takes — a flight, a link
dead for hours — then reattaches by your key and repaints the exact screen when the
network returns (Ctrl-C quits while waiting). If the client process itself is gone,
run `wssh myhost` again and you reattach the same way.

Keep several shells at once and name them, like tmux:

```bash
wssh myhost              # resume most-recent (or start one)
wssh myhost -t build     # attach/create the session named "build"
wssh myhost --new        # a fresh session alongside the others
wssh myhost --list       # list your sessions
wssh myhost --kill build # remove one
```

Sessions are private to your key. Two inherent limits: a server reboot loses held
sessions (they live in memory, like tmux/screen), and detached scrollback is capped
(you get the live screen plus recent output, not hours of history).

## Access control

Two modes:

- **Classic** — an SSH-style flat file (`--authorized authorized_keys`), one key per
  line, with optional per-key options: `command="..."`, `no-pty`, `from="cidr,..."`,
  `expires="YYYY-MM-DD"`, `permitopen="host:port,..."`.
- **Tier** (`--root DIR --enroll`) — the WireSeal identity engine. An unknown key
  self-registers into a pending queue (`wssh HOST --request <handle>`); you vouch it
  with `wsshctl approve <key> [member|admin]`. Admission is by tier — owner/admin/
  member get a shell; banned and unapproved get nothing. Approvals are live, no
  restart. `wsshctl sessions` shows who's connected and since when (a snapshot with
  no peer addresses recorded). Manage the root with `wsshctl`
  (`pending`/`approve`/`deny`/`ban`/`members`/`review`/`watch`).

## Port forwarding and file copy

```bash
wssh myhost -L 8080:localhost:80      # local port -> target reached from the server
wssh myhost -D 1080                   # local SOCKS5 proxy, dynamic targets
wssh myhost -R 9000:localhost:3000    # reverse: server port -> target reached from you
wscp myhost:/etc/hostname ./          # copy from server
wscp ./build.tar.gz myhost:/tmp/      # copy to server
```

Forwards multiplex over the session, ride whichever transport it negotiated, and
survive UDP roaming.

## Safe rendering

`--safe-render` routes server output through a private terminal emulator and emits
only a safe subset of escape sequences, so a malicious server can't inject terminal
escapes into your terminal. Trade-off: it shows only what the emulator models and
disables predictive echo. Raw passthrough is the default.

---

## Security status

**The WireSeal transport cryptography — the TCP-bootstrap Noise IK handshake and the
UDP session handshake — has NOT been independently reviewed or audited.** The test
suites prove the code survives the specific attacks it was tested against (no
crashes, hangs, or resource exhaustion; escape injection is closed under
`--safe-render`). That is operational hardening, not cryptographic clearance.

Treat wireshell as **promising, real-hardware-tested software for experimentation —
not for high-stakes secrecy** — until the handshake has had independent review.

## Authenticity

Releases are signed with **wsident**, WireSeal's Ed25519 authorship tool:
https://git.disroot.org/wireseal/wsident

Verify against the author's offline cold-key fingerprint — not against any email,
host, or channel:

```
3aa0a2db b80573ff eb319965 05f369ba 3421584e 5c26653f ce079a8a ab55fe50
```

To verify a release, download it alongside `MANIFEST` and `MANIFEST.wsig`, then:

```bash
sha256sum -c MANIFEST
python3 wsident.py verify MANIFEST.wsig \
  3aa0a2dbb80573ffeb31996505f369ba3421584e5c26653fce079a8aab55fe50
```

`AUTHENTIC` means the release is genuinely from the holder of that cold key. See
`AUTHENTICITY.txt` in the tree for the current certificate.

---

Apache-2.0 · cc &lt;wireseal@proton.me&gt; · https://git.disroot.org/wireseal/wireshell
