Skip to content

Add HTTPS support via cpp-httplib's mbedtls backend#1011

Merged
aittalam merged 4 commits into
mainfrom
add-https-support
Jul 2, 2026
Merged

Add HTTPS support via cpp-httplib's mbedtls backend#1011
aittalam merged 4 commits into
mainfrom
add-https-support

Conversation

@aittalam

@aittalam aittalam commented Jul 1, 2026

Copy link
Copy Markdown
Member

What

Adds HTTPS/TLS support to llamafile, enabling:

  • HTTPS model downloads-hf ggml-org/models, --hf-repo/--hf-file, --model-url https://... now work in llamafile and llama-server (previously threw HTTPS is not supported, so downloading from Hugging Face only worked in upstream llama.cpp, not here)
  • TLS serving--ssl-cert-file/--ssl-key-file now start an HTTPS server
  • https clients in server-models (server_http_proxy)

How

llama.cpp gets TLS from cpp-httplib's OpenSSL backend (system OpenSSL, or vendored BoringSSL/LibreSSL at cmake time), none of which fit the cosmocc make build. But the cpp-httplib version vendored by our llama.cpp pin is multi-backend: it has a native Mbed TLS backend (CPPHTTPLIB_MBEDTLS_SUPPORT, umbrella macro CPPHTTPLIB_SSL_ENABLED).

So we keep the exact HTTP library llama.cpp uses and enable that backend against the Mbed TLS fork already vendored in third_party/mbedtls — the same TLS stack llamafile ≤ 0.9.3 used for its HTTPS (llamafile/curl.cpp, localscore). Zero new vendored code.

  • third_party/mbedtls/include/mbedtls/*.h (new): 12 one-line shims mapping the canonical <mbedtls/x.h> include paths onto the fork's third_party/mbedtls/x.h headers.
  • llama.cpp/BUILD.mk + llamafile/BUILD.mk: set CPPHTTPLIB_MBEDTLS_SUPPORT on every object that can reach httplib.h (the macro changes httplib class layouts, so all TUs must agree — including llamafile/server.cpp.o, which reaches it via server-cors-proxy.h); link mbedtls.a into llama-server and llamafile.
  • Guard fixes in llama.cpp (common/http.h, tools/server/server-http.cpp, tools/server/server-models.cpp): #ifdef CPPHTTPLIB_OPENSSL_SUPPORT#ifdef CPPHTTPLIB_SSL_ENABLED. These look upstreamable — upstream's guards predate httplib's multi-backend refactor.
  • vendor/cpp-httplib/httplib.cpp patch, under __COSMOPOLITAN__: append /zip/third_party/mbedtls/sslroot to httplib's system-CA-dir fallback list, and __static_yoink("ssl_root_support") so the Mozilla root PEMs (already in-tree, bundled by third_party/mbedtls/BUILD.mk) land in the APE zip. Host trust stores are preferred when present (/etc/ssl/..., so corporate CAs keep working); the bundled roots are the fallback — essential on Windows, where the _WIN32 cert-store branches aren't compiled into an APE.
  • llamafile/{args,main}.cpp: the main binary's "missing required -m" gate now also accepts -hf/-hfr/--hf-repo/-mu/--model-url (a remote model to download is a model); TUI banner falls back to the repo/URL string.

Certificate verification is on (MBEDTLS_SSL_VERIFY_REQUIRED path via httplib defaults): TLS 1.2, SNI, hostname verification, entropy from cosmo getrandom.

Why not the same TLS library as llama.cpp (BoringSSL/LibreSSL/OpenSSL)?

We probed it: LibreSSL 4.3.2 does build under cosmocc 4.0.2 — one 2-line patch (crypto/compat/arc4random.h needs a __COSMOPOLITAN__ case) plus configure overrides; verified with a live TLS 1.3 handshake to google.com from an APE test binary. ahgamut/superconfigure also builds OpenSSL proper with cosmocc. So it's feasible, but it means vendoring and maintaining a second ~470-file TLS stack (~16 MB of fat static archives) for no functional gain on the download path. The cpp-httplib mbedtls backend gets us the same HTTP library as upstream with the TLS stack we already ship. LibreSSL stays documented as plan-B if TLS 1.3 or bit-exact upstream parity is ever needed.

Testing

  • llama-server --hf-repo ggml-org/models --hf-file tinyllamas/stories260K.gguf: downloads over HTTPS (hf.co → CDN cross-host redirect exercised), loads, /health ok — same via the main llamafile --server binary with a cold cache, /v1/completions returns tokens
  • Negative: self-signed.badssl.com, wrong.host.badssl.com, expired.badssl.com all rejected with SSL server verification failed; huggingface.co → HTTP 200
  • TLS serving: --ssl-cert-file/--ssl-key-file with a self-signed cert serves https://.../health = ok
  • make check passes; clean round-trip (reset-reposetup → clean build → check) passes with the regenerated patches

🤖 Generated with Claude Code

llamafile built cpp-httplib without any TLS backend, so every https://
operation failed: -hf/--model-url downloads from Hugging Face, https
clients in server-models, and --ssl-cert-file/--ssl-key-file serving.

llama.cpp gets TLS from cpp-httplib's OpenSSL backend (system OpenSSL or
vendored BoringSSL/LibreSSL at cmake time), none of which fit the cosmocc
make build. The vendored cpp-httplib is multi-backend though: enable its
native Mbed TLS backend (CPPHTTPLIB_MBEDTLS_SUPPORT) against the mbedtls
fork already vendored in third_party/mbedtls -- the same TLS stack
llamafile <= 0.9.3 used -- so no new third-party code is vendored.

- third_party/mbedtls/include: shims mapping <mbedtls/*.h> onto the fork
- BUILD.mk: define the macro on every TU that reaches httplib.h (it
  changes httplib class layouts); link mbedtls.a into llama-server and
  llamafile
- llama.cpp guards: CPPHTTPLIB_OPENSSL_SUPPORT -> CPPHTTPLIB_SSL_ENABLED
  in common/http.h, server-http.cpp, server-models.cpp (candidates for
  upstreaming; upstream's guards predate httplib's multi-backend split)
- httplib.cpp: under __COSMOPOLITAN__, fall back to the Mozilla roots
  zipped into the APE (/zip/third_party/mbedtls/sslroot) when the host
  has no system CA store (e.g. Windows), keeping host stores preferred
- llamafile main: accept -hf/--hf-repo/-mu/--model-url in place of -m

Verified: HF downloads (hf.co -> CDN redirect) from llama-server and
llamafile --server; self-signed/wrong-host/expired certs rejected;
TLS serving works; make check and the clean patch round-trip pass.

LibreSSL 4.3.2 was probed as the "same library as llama.cpp" option and
does build under cosmocc (2-line arc4random.h patch, TLS 1.3 handshake
verified) but was not chosen: it would vendor a second TLS stack for no
gain on this path. Documented as plan-B if TLS 1.3 is ever required.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aittalam

aittalam commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Code review

Found 1 issue:

  1. ABI/ODR class-layout mismatch: chatbot_api.cpp and chatbot_main.cpp include httplib.h but are compiled without CPPHTTPLIB_MBEDTLS_SUPPORT, while they link into the same llamafile binary as httplib.cpp (in $(HTTPLIB_OBJS)), which this PR compiles with the macro. This violates the invariant stated in the PR's own comment ("the macro changes httplib class layouts, so all TUs must agree"): CPPHTTPLIB_MBEDTLS_SUPPORT turns on CPPHTTPLIB_SSL_ENABLED (httplib.h L492-495), under which httplib::Result gains int ssl_error_; uint64_t ssl_backend_error_; (L2000-2018) and httplib::Client gains bool is_ssl_ (L2748). chatbot_api.cpp constructs httplib::Client and receives Result by value from Get/Post, whose out-of-line definitions in httplib.cpp use the larger layouts — the callee writes fields the caller didn't allocate room for (e.g. httplib.cpp returns Result{..., last_ssl_error_, last_backend_error_} under the macro). This is memory corruption on the combined TUI+server chat path. Fix: give the chatbot objects the same -DCPPHTTPLIB_MBEDTLS_SUPPORT -isystem third_party/mbedtls/include flags (e.g. via LLAMAFILE_CPPFLAGS or a rule scoped to those objects), since they compile via the generic pattern rule that only uses LLAMAFILE_CPPFLAGS.

Macro added for server.cpp only (the invariant comment):

# ==============================================================================
# Include paths needed for server compilation. server.cpp reaches
# httplib.h via server-cors-proxy.h, so it must see the same
# CPPHTTPLIB_MBEDTLS_SUPPORT macro as the objects built by
# llama.cpp/BUILD.mk (the macro changes httplib class layouts).
LLAMAFILE_SERVER_INCS := \
$(LLAMAFILE_INCLUDES) \
-iquote llama.cpp/tools/server \
-iquote o/$(MODE)/llama.cpp/tools/server \
-DCPPHTTPLIB_MBEDTLS_SUPPORT \
-isystem third_party/mbedtls/include \
-iquote . \
-mcosmo

Pattern rule that compiles the chatbot objects without the macro:

o/$(MODE)/llamafile/%.o: llamafile/%.cpp
@mkdir -p $(@D)
$(CXX) $(CXXFLAGS) $(LLAMAFILE_CPPFLAGS) -c -o $@ $<

httplib::Client constructed in a TU that doesn't see the macro:

httplib::Client cli(host_, port_);
cli.set_read_timeout(300); // 5 minutes for long generations

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

chatbot_api.cpp and chatbot_main.cpp include httplib.h but were compiled
without CPPHTTPLIB_MBEDTLS_SUPPORT while linking into the same binary as
httplib.cpp, which is compiled with it. The macro enlarges httplib class
layouts (Result grows ssl_error_/ssl_backend_error_, Client grows
is_ssl_), so the TUI receiving Result by value from Get/Post would have
the callee writing fields the caller never allocated.

Factor the flags into LLAMAFILE_HTTPLIB_TLS_FLAGS and apply them to the
chatbot objects alongside server.cpp.o, with a comment telling future
httplib includers to do the same.

Verified with a probe TU compiled with the chatbot flag set, linked
against the same httplib.cpp.o + mbedtls.a, exchanging Result by value
with a live server; make check passes.

Reported-by: code review on #1011
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aittalam

aittalam commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Fixed in 96436d5 — good catch, and it violated the invariant this PR itself documented.

chatbot_api.cpp/chatbot_main.cpp (the only remaining TUs that include httplib.h; main.cpp mentions it only in a comment) now get the same flags as httplib.cpp via a shared LLAMAFILE_HTTPLIB_TLS_FLAGS variable, with a comment in BUILD.mk telling future httplib includers to add their objects there. server.cpp.o uses the same variable instead of its inline copy.

Verified beyond the flag audit: a probe TU compiled with the chatbot flag set and linked against the same httplib.cpp.o + mbedtls.a exchanges httplib::Result by value (Get /health + /props, move, body access) with a live llama-server — clean. make check passes.

🤖 Generated with Claude Code

Four tests behind a new pytest 'ssl' marker (toggle with -m ssl /
-m "not ssl"):

- serve over TLS via --ssl-cert-file/--ssl-key-file and connect with a
  verifying client (self-signed cert generated with openssl, SAN for
  localhost/127.0.0.1)
- plain-HTTP request to the TLS port is refused
- download a tiny model (ggml-org/models stories260K, ~1 MiB) from
  Hugging Face over HTTPS into a fresh cache and serve it; the only
  test needing network access
- offline negative test: --model-url against our own self-signed HTTPS
  server must fail with nothing written to the cache, proving the
  downloader enforces certificate verification (the rogue endpoint
  would otherwise happily serve a body)

Also fixes a duplicate-row typo in the README marker table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Allows running the suite offline with -m "not online" while keeping the
rest of the ssl tests selectable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aittalam aittalam merged commit a03a7c8 into main Jul 2, 2026
2 checks passed
@aittalam aittalam deleted the add-https-support branch July 2, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant