Do not open a public issue for a vulnerability. Use the private report of GitHub: open https://github.com/PodUtils/podnet/security/advisories/new and write these items:
- the version (
podnet --version) and the system (podnet status); - the steps that show the fault;
- what an attacker can do, and what the attacker must have first.
The maintainer replies in the report. A fix comes in a new release, and the report becomes a public advisory after the release.
Only the latest release and main get fixes. A prerelease (a tag with a
suffix, for example v0.3.0-beta.1) is replaced by the next release.
.github/workflows/deny.ymlrunscargo denyon each push and each day: advisories, licenses, bans and sources (deny.toml).- An advisory stops CI. Update the crate, or replace it. If no fixed version
exists and podnet does not use the affected part, add the advisory to
[advisories] ignoreindeny.tomlwith the reason and the date it was read. - Dependabot opens the updates of crates, actions and images each week.
The release files have checksums, provenance and a signature. See "Release
binaries" in README.md.
The releases up to v0.3.0-beta.12 have the old name cfrs, and the
identity of the repository Azathothas/cfrs signs them. README.md gives
their file names and their identity.
- TLS between the visitor and the tunnel ends at the Cloudflare edge. The edge sees each request and each response of the origin in clear text.
- The hostname of a quick tunnel (
*.trycloudflare.com) is public. Anyone who has the URL can send requests to the origin. A quick tunnel has no access control.podnet shareadds a PIN with--password(and always in proxy mode).podnet serveandpodnet tunneladd nothing. podnet serveandpodnet sharepublish files to the internet. Serve only the files that you want to give to all users.- The WARP service sees the destination, the time and the volume of each connection that goes through WARP, and the content of each connection that has no TLS of its own.
- The edge, the quick-tunnel service and the WARP API see the public IP address of the host.
- When the system resolver gives no address, podnet asks DNS over HTTPS at
Cloudflare (1.1.1.1, 1.0.0.1) and Google (8.8.8.8, 8.8.4.4). These servers
see the names that podnet resolves. A pin (
--resolve) or a proxy sends no name to them.
The WebSocket-to-TCP relay of --transport relay and of the reverse mode
(podnet relay) is a service of the owner of podnet
(tcp.ssh.relay.ajam.dev and the hosts of its pool). Another relay is set
with --relay or PODNET_RELAY.
--transport relay: the relay opens TCP to the edge. It sees the edge address and port, the public IP address of the host, the time and the volume. It does not see the tunnel: the TLS to the edge (with the edge roots of podnet) is inside the WebSocket.- The reverse mode (
relay node,relay connect): the relay carries the bytes of each session as they are. There is no encryption from end to end in podnet. Use a protocol with its own encryption through it (SSH, TLS). - The token goes only to the hosts of one deployment: the hosts under the
parent domain of the first host (
src/relay_pool.rs). A fixed token (PODNET_RELAY_TOKEN) goes to each host of the list. - The relay can end a session at any time. It ends each session at 64 MiB or after 12 h. podnet replaces an edge connection before that (60 MiB, 11 h 30 min).
podnet listens only when a command asks for it. A tunnel connects out to the edge; it does not listen. The commands below listen:
| Command | Default address | Who can connect |
|---|---|---|
podnet warp proxy |
tcp://127.0.0.1:40000 |
Each user of the host (loopback TCP). Use --listen unix:/path for the owner only. |
podnet net proxy |
unix:/tmp/podnet-vnet.socks |
The owner only (socket mode 0600). |
podnet net switch |
unix:/tmp/podnet-vnet.switch |
The owner only (socket mode 0600, and the user of the peer, SO_PEERCRED). A TCP listener takes each peer that sends the shared token (PODNET_SWITCH_TOKEN or --switch-token-file); with no token it does not start. The token is not encrypted. The switch drops a packet whose source address is of another peer. |
podnet net proxy --control NAME, --import VIRT, the shim |
abstract names \0NAME, \0podnet-vnet/... |
The same user only (SO_PASSCRED, SO_PEERCRED). An abstract name has no file permissions. |
podnet net tailscale |
unix:/tmp/podnet-socks.sock |
The owner only (socket mode 0600). |
podnet connect -L |
no default | tcp://port binds 127.0.0.1. A Unix socket has mode 0600. |
podnet pod --serve |
no default | A Unix socket has mode 0600. A TCP address is open to each user who can reach it. |
podnet pod, podnet warp run (seccomp backend) |
$XDG_RUNTIME_DIR/podnet-netns-inbound/<port>.sock, else $TMPDIR/podnet-netns-inbound-<uid>/<port>.sock |
The owner only (socket mode 0600, directory of the user, mode 0700). One socket for each port that the program listens on. podnet refuses a directory of another user, and a directory that the group or others can write. |
A TCP address that is not a loopback address is open to the network. podnet does not refuse it, because some hosts permit no other listener.
tests/cli.rs (the_listen_defaults_agree_with_the_manual) checks the
defaults in this table against podnet man --json.
| Secret | Where podnet keeps it |
|---|---|
| Named-tunnel token and secret | In memory only. Give it in TUNNEL_TOKEN, a file (--token-file, TUNNEL_TOKEN_FILE), a config file or a cloudflared credentials file. --token also works, but other users of the host can read a command line. |
| WARP device token, MASQUE private key, WARP+ license | The identity file (podnet man files), mode 0600, in a directory with mode 0700. podnet writes it to a new file and then renames it. podnet refuses an identity file that belongs to another user, or that the group or other users can read. |
| Proxy user and password | In the proxy variables or -x. |
| Relay token | PODNET_RELAY_TOKEN, or a minted token in relay-token-HOST.json (mode 0600) in the state directory. podnet does not use a cache file that is a link, of another user, readable by others or over 64 KiB. |
| Relay pair (node, connect and stop tokens) | relay/NAME.json in the state directory, mode 0600. |
These rules apply to each secret:
- No log line, error line or failure report holds it. The
Debugtext ofCredentials, the WARPDeviceandAccount, andProxyshows<hidden>. Each error line and each line of the failure report goes throughreport::mask, which hides the user and the password of each URL, the value of each proxy variable that has a user part, and the token. tests/cli.rs(the_log_holds_no_secret) runs podnet withRUST_LOG=traceand a token, a WARP identity and a proxy password. The test fails when one of them is in the output.
Each TLS connection that podnet opens as a client checks the certificate and
the host name. src/trust.rs gives the roots:
- The file of
--ca-file, else the file ofSSL_CERT_FILE. podnet uses this file alone. - Else
podnet-ca.pembeside the binary, the first system bundle that podnet can read, and the Mozilla roots compiled into podnet (webpki-roots, version intrust::ROOTS_CRATE_VERSION; a test compares it withCargo.lock).
The edge also trusts the Cloudflare roots of the fact roots of the edge of
pod-services (PodUtils/podcrates), the certificates of
src/cloudflare/cf-edge-roots.pem. The first of them ends on 2029-08-15
(trust::edge_roots_first_end; a test of pod-services reads it from the
certificates). The MASQUE connection of WARP checks the
key of the server against the key that the WARP API gave, and does not
connect without one.
Only an origin can turn off the check: noTLSVerify in a config file, or
--no-tls-verify. A test (trust::tests::no_other_code_sets_its_own_verifier)
fails when other code sets its own certificate verifier.
When a certificate is expired or its validity starts later, the error line also gives the clock of the host: a wrong clock is a frequent cause in a container.
Text from a peer (an edge error, an HTTP body, a proxy reply) can hold
control characters that a terminal acts on. report::clean removes each
escape sequence, BEL, CR, and the other control characters except tab and
line feed. Each error line and the failure report go through it. The log
writer (tracing-subscriber) escapes control characters in the log fields.
A test (report::tests::the_error_line_has_no_control_characters) sends
ESC, BEL, CR and C1 controls through the error path.
- No third party has audited podnet.
- The Cap'n Proto encoding, the RPC subset, the HTTP/2 edge transport, the
MASQUE client and the YAML reader are written for podnet. Each parser of
bytes from the network has a libFuzzer target in
fuzz/(.github/workflows/fuzz.yml), but fuzzing finds only some defects. - podnet is for Linux only. It does not read the certificate stores of Windows or macOS, and no release is built for them.
- The
/tmppaths of the Unix sockets are known to each user. Another user can make the path first. Then the bind fails; podnet does not use the file of the other user. Use--listen unix:with a path in a private directory.