Revamp image proxy to use Cloudflare WARP with WireGuard userspace implementation #23
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
DuckDuckGo's image proxy returns HTTP 404 for formats it can't decode (SVG, some WebP). This replaces it with an in-app proxy using Cloudflare WARP over WireGuard.
New Rust Crate:
letterbox-proxyapi.cloudflareclient.com, X25519 keypair generation, credential persistenceproxy_init,proxy_fetch_image,proxy_fetch_images_batch,proxy_status,proxy_shutdown,proxy_clear_cacheKotlin Integration
ImageProxyService: Coroutine-based wrapper for Rust FFIUserPreferencesRepository: AddedProxyModeenum (WARP/DUCKDUCKGO/DIRECT), Cloudflare consent preferenceorg.joefang.letterbox.ffi.proxyUsage
Documentation
docs/image-proxy-design.md: Architecture, data flow, component designdocs/remote-images.md: Updated to describe both proxy modesTesting
77 tests (41 proxy + 36 core), covering provisioning API serialization, WireGuard transport, smoltcp device behavior, HTTP content-type validation.
Original prompt
The DuckDuckGo image proxy is broken for many image files like SVG. If the server cannot decode a file, it returns HTTP 404. I want you to revamp the whole "image proxy" design where you run a local WireGuard userspace implementation with Cloudflare Warp account that are created per-user during the first time the user clicks "load image" through proxy and persisted thereafter based on the provided Cloudflare API design. Atop of the WG layer, you would have a userspace TCP/IP implementation and then you would have the typical HTTP library atop. You would expose an image proxy API to the Kotlin layer through the FFI interface that accepts an URL and you would design the API to allow for maximum parallelism since an email can contain many small images and you want to avoid one image blocking the rest of them. Follow best practices in both Kotlin and Rust to write efficient, high performance code. Write idiomatic patterns with comprehensive test (unit test+integration+end-to-end) and documentation coverage and create a document to record the design decisions you made while implementing the new image proxy architecture. ===design docs=== This can be built without running your own proxy infrastructure by embedding a Rust “fetcher” that (1) provisions a per-user Cloudflare WARP WireGuard identity and (2) routes only image HTTP(S) requests through an in-process userspace TCP/IP stack (smoltcp) carried over a WireGuard engine (Mullvad’s GotaTun). The key constraint is that WireGuard libraries implement the tunnel protocol but not the TCP/IP stack, so smoltcp (or equivalent) is what lets you originate TCP connections without a TUN/VPN service.[1][2][3]
Architecture overview
Cloudflare WARP provisioning
Cloudflare WARP account/device provisioning is done via HTTPS calls to
api.cloudflareclient.com(the same endpoint family used by wgcf), and Cloudflare’s docs note the WARP client uses a standard HTTPS connection outside the tunnel for operations like registration/settings changes.[6][7][8]Data model (persisted per user)
Persist the minimum you need to recreate the tunnel deterministically:
account_id(device id),access_token,private_key,license_key(matches the script’sAccountData).warp_enabled(matches the script’sConfigurationData).Store these in platform secure storage (Keychain/Keystore) and keep a small JSON mirror only if needed for debugging/restore.
Provisioning flow (mirrors your script)
/{api_version}/regwith JSON including the public key and a “tos” timestamp (as your script does).https://api.cloudflareclient.com+ an API version constant to do so.[8][6]idandtokenplus your locally generated private key (the server never needs your private key)./{api_version}/reg/{account_id}withAuthorization: Bearer {token}and parse:public_keyandendpoint(host + v4/v6).warp_enabledis false, PATCH/{api_version}/reg/{account_id}with{"warp_enabled": true}(same as your script).Legal/consent UI requirement
Embedded tunnel + smolt...
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.