Article summary — original

Cloudflare Open-Sources Privacy Proxy CLI Tool pvcli to Streamline Protocol Debugging Cloudflare has open-sourced pvcli (privacy-client) under the Apache-2.0 license to simplify testing and debugging privacy-preserving network protocols such as Oblivious HTTP. Inspired by curl, the command-line utility automates manual binary encoding, encryption, and multi-hop routing previously required when troubleshooting complex privacy infrastructure. Cloudflare released pvcli under the Apache-2.0 license on GitHub to ease development across privacy-preserving protocols. Debugging Oblivious HTTP (OHTTP) manually required parsing binary key configurations (RFC 9458), assembling Binary HTTP frames (RFC 9292), and writing custom scripts for multi-hop encryption. pvcli automates end-to-end OHTTP execution into a single command with familiar curl-like syntax, supporting relay headers and mTLS authentication. Beyond OHTTP, the tool aims to unify CONNECT proxying, MASQUE, and Privacy Pass, with planned additions for post-quantum cryptography, Chunked OHTTP, and expanded MASQUE proxying. Motivation and Challenges of Debugging OHTTP Cloudflare's Privacy team operates services powering products like Apple's Private Relay, Microsoft's Edge Secure Network VPN, and Flo Health's Anonymous Mode. In protocols like OHTTP, requests pass through non-colluding relays and gateways to dissociate client identity from payload content. Debugging this multi-party architecture previously meant parsing hexadecimal public key configurations by hand, manually formatting Binary HTTP according to RFC 9292, generating encrypted wrapper requests, and pinpointing minute binary-level mistakes such as misplaced length-prefix bytes. Streamlined Debugging and Feature Set pvcli abstracts manual binary manipulation and encryption into single commands while maintaining verbose logging capabilities using flags like -v. Designed under the principle of least surprise with arguments patterned after curl, the tool enables developers to configure relays using --first-hop and gateways using --proxy, pass custom relay headers via --first-hop-header, and perform client authentication using mutual TLS (mTLS) with --first-hop-client and --first-hop-key. Broader Protocol Vision and Roadmap Rather than focusing exclusively on OHTTP, pvcli is built to consolidate multiple privacy protocols into a unified tool, including CONNECT proxying, MASQUE, and soon Privacy Pass. Cloudflare is accepting open-source contributions, with near-term roadmaps including TCP over HTTP/3 and UDP/IP over HTTP/2 and HTTP/3 for MASQUE, alongside post-quantum cryptography, Chunked OHTTP support, timing metrics, and improved logging for OHTTP.

Article summary — zh

Cloudflare开源隐私代理命令行工具pvcli,简化OHTTP等协议调试 Cloudflare宣布在Apache-2.0许可下开源隐私代理命令行工具pvcli(privacy-client),旨在解决Oblivious HTTP(OHTTP)等隐私保护协议在排障和测试过程中流程复杂、手动编解码繁琐的问题。 Cloudflare正式在Apache-2.0许可下开源隐私协议命令行工具pvcli。 该工具解决了传统OHTTP排障需手动解析公钥十六进制串、构建二进制HTTP(BHTTP)及处理加密封装的繁琐痛点,将其简化为单条命令。 pvcli借鉴了curl的命令行参数设计,支持向中继传递自定义标头、配置mTLS双向认证等功能。 该工具的目标不仅限于OHTTP,还将整合CONNECT代理、MASQUE以及即将推出的Privacy Pass等多种隐私保护协议。 开发背景与传统排障痛点 随着支持Apple Private Relay、Microsoft Edge Secure Network VPN和Flo Health匿名模式等产品的隐私团队规模扩大,多样化的客户需求导致研发和故障排障复杂度剧增。在以往排查OHTTP请求时,工程师需要通过curl拉取网关公钥,依照RFC 9458手动比对十六进制字符解析公钥配置,再根据RFC 9292借助自建脚本拼装二进制HTTP并进行加密封装。由于BHTTP采用长度前缀格式,手动拼装极易因空格等微小字节偏差导致失败,整体调试极其脆弱繁琐。 pvcli的设计与功能特性 pvcli将多步骤的公钥解析、加密与封包整合成一条命令行指令,并遵循“最小惊讶原则”,大量继承了类似curl的操作参数以降低使用门槛。用户可以通过`--ohttp`指定中继(`--first-hop`)和网关代理(`--proxy`),借助`-v`查看详细排障日志,还能通过`--first-hop-header`向中继节点发送专用请求头,或通过证书与密钥参数启用mTLS以实现客户端与中继的安全认证。 开源协作与后续规划 现有开源生态中虽有Rust和Go实现的OHTTP基础工具,但缺乏综合性解决方案。pvcli的目标是统一管理多种隐私协议,除OHTTP外,还计划全面覆盖CONNECT代理、MASQUE和Privacy Pass。未来路线图中,团队计划为MASQUE增加HTTP/3上的TCP代理以及HTTP/2和HTTP/3上的UDP/IP代理,并针对OHTTP增加后量子密码学支持、分块传输(Chunked OHTTP)、延迟统计与日志改进。

Saved original article

Debugging privacy-preserving protocols is hard. Oblivious HTTP has several different steps across four different parties, not to mention binary HTTP encoding and details spread across many draft RFCs. We've taken what we've learned operating protocols like Oblivious HTTP at the scale of millions of requests per second, and wrapped it up in a nice, clean CLI tool — that we are open-sourcing today. We call it our **privacy-client**, or **pvcli**. We’re releasing it under the Apache-2.0 License, and it is [open for contributions](https://github.com/cloudflareresearch/pvcli). Here’s a single line of code that executes a full Oblivious HTTP request with a relay, gateway and origin. Don't worry if you don't know what that means, we'll cover it below. We’ll explain why we built this tool, and show just how handy it can be. Let’s take a closer look at our motivation for creating pvcli. Over time, the Privacy team’s product suite and customer base grew. We added products like Privacy Proxy and Privacy Gateway, which power Apple’s [Private Relay](https://blog.cloudflare.com/icloud-private-relay/), Microsoft’s [Edge Secure Network VPN](https://blog.cloudflare.com/cloudflare-now-powering-microsoft-edge-secure-network/), Flo Health’s [Anonymous Mode](https://www.cloudflare.com/case-studies/flo-health/), and more. With it came an increasing amount of special customer requirements, domain knowledge, and complexity. As a result, we saw increased friction in development and incident response. To see this in action, let’s look at how one of our products implements [Oblivious HTTP](https://developers.cloudflare.com/privacy-gateway/), also known as OHTTP. First, a quick primer. OHTTP provides users with a privacy guarantee: no one can know both *who* made a request and *what* they’re requesting. To achieve this, OHTTP requires two servers, a relay and a gateway, operated by two non-colluding parties. Below is a sequence diagram of OHTTP, where our customer owns the relay and Cloudflare owns the gateway. At a high level, OHTTP can be broken down into these steps: It involves quite a bit of back-and-forth, as you can see: Each step is a potential point of failure that we have to consider while debugging! In particular, we saw certain kinds of problems when debugging OHTTP. As a result, we decided to place all of our privacy protocols in one tool. It has a clean interface that’s already familiar, displays every single step of the protocol in order, and is flexible enough to support new protocols and architectures. To see the difference this makes, let’s see an OHTTP debugging scenario — before and after pvcli. Say we operate an OHTTP relay that sits in front of a customer's gateway. The customer has asked us to do an end-to-end test with a request: Recall the OHTTP steps from earlier. The first step is to fetch the public key from the gateway. We use curl to fetch it from the customer gateway and get this back: That’s a big binary string in hex. To make sense of it, we look at OHTTP [RFC 9458 §3](https://www.rfc-editor.org/rfc/rfc9458#name-key-configuration-encoding) and parse it manually: `0029` is 41 in decimal, telling us this public key entry has 41 bytes associated with it.`55` is the public key ID.`0020` identifies the asymmetric encryption method we can use. In this case, DHKEM(X25519, HKDF-SHA256).`b9bb667e2230dc01c6d6cc047f94a1083beb185c63e50ec09f7692a5a0832540` is the public key.`0004` tells us there are 4 bytes of symmetric cryptographic IDs that follow.`0001` and We repeat this process for however many public keys are in the binary string. Next, we convert our original HTTP request into binary HTTP, referencing [RFC 9292](https://www.rfc-editor.org/rfc/rfc9292). We manually craft the binary with the help of some bespoke scripts: We verify each field: `02` means it's an indeterminate-length request`04504f5354` is `POST``056874747073` is https`117461726765742e6f687474702e696e666f` is `target.ohttp.info` Finally, we form a wrapper HTTP request that will hold our OHTTP request. To do so, we spend some more time writing another makeshift script that encrypts the binary HTTP request in the manner OHTTP specifies, using the public key from earlier. We create a header, which is the concatenation of public key ID, asymmetric encryption method ID, and symmetric encryption method IDs. Then, we concatenate header and encrypted binary HTTP request, resulting in: We put those bytes into the body of our wrapper HTTP request, and send it to our relay. We get back a response. What does that mean? We reach out to the customer to ask if they can share logs from their gateway. In the meantime, we double-check the bits we've crafted. The decoded public keys look fine. The binary HTTP request... Oh! We see: BHTTP is length prefixed. That means we specify a length (0x0a is 10 in decimal), and then 10 bytes follow. But here, 11 bytes follow. There is an extra 20 before the 00. 20 represents a space character, so we must have accidentally added that when building the body. We remove the extra character, resend, and it works! With pvcli, all of that is now a single command: It handles all the binary parsing and encrypting for us, and prints logs in case we want to dive deeper: What used to be a fragile process — involving manipulating bits, gluing together scripts, and referencing long RFCs — is now one command. To install: pvcli takes a lot of inspiration from curl. We designed it with the “principle of least surprise” in mind. As a result, a lot of the arguments are the same as curl’s! Try a quick GET request to our [cdn-cgi endpoint](https://developers.cloudflare.com/fundamentals/reference/cdn-cgi-endpoint/): If you’re curious about what is happening under the hood, you can use `-v` to get detailed logs: Now, about that OHTTP command from earlier: you use --ohttp to tell pvcli to construct an OHTTP request. You pass in the relay as the --first-hop and the gateway as the --proxy. The target will be an echo server, so you can see what the target would see. In this command, we filled in the arguments with a relay, gateway, and target from [ohttp.info](http://ohttp.info). Try running the command yourself! We’ve encountered many cases where we wanted to pass headers to the relay, rather than the target. You are able to do that with `--first-hop-header`: Similarly, we’ve also had cases where we wanted to authenticate to the relay with [mTLS](https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/), to ensure that the correct client is talking with the correct relay. To do that, you can use `--first-hop-client` and `--first-hop-key`. And it just works. Need to test a full Oblivious HTTP request with a relay, a gateway, arbitrary headers, and mTLS? Or perhaps only request through a gateway? Or maybe you just want to see the OHTTP key configuration? pvcli can do it with a single command, debugging included. There are some great tools for OHTTP that already exist. Martin Thomson’s [Rust implementation](https://github.com/martinthomson/ohttp) and Chris Wood’s [Go implementation](https://github.com/chris-wood/ohttp-go) were incredibly helpful when we built out our original OHTTP implementation a few years ago. But pvcli is not only focused on OHTTP. We’re looking to add as many privacy-preserving protocols as we can to the tool. So while there are other OSS tools out there for debugging OHTTP, nothing combines OHTTP, CONNECT proxying, MASQUE and Privacy Pass (coming soon) all in one place. Oblivious HTTP is an amazing protocol, and we would love to see you use it. We hope that this tool helps people debug OHTTP and write their own OHTTP implementations. We are accepting contributions! To get started, clone the repo at [https://github.com/cloudflareresearch/pvcli](https://github.com/cloudflareresearch/pvcli), and submit a pull request. If you're looking for ways to contribute, here are some things on our to-do list. For MASQUE, we plan to add support for proxying TCP over HTTP/3, and UDP and/or IP over HTTP/2 and HTTP/3. For OHTTP, we plan to support post-quantum cryptography, add timing/latency information, support Chunked OHTTP, and improve logging. [Contact us](https://www.cloudflare.com/lp/privacy-edge/) if you are interested in using Cloudflare’s OHTTP Relays and Gateways.