{"id":"OSEC-2026-28","aliases":[],"status":"published","title":"Unauthenticated arbitrary file read via path traversal in `/conferences/ocaml/**`","description":"`GET /conferences/ocaml/\u003cpath\u003e` on ocaml.org concatenates the raw request target onto `OCAMLORG_V2_PATH` (`/v2`) and serves it with `Dream.from_filesystem`, bypassing the path validation that `Dream.static` performs. An unauthenticated request containing `..` segments escapes `/v2` and reads arbitrary files as the server process, which runs as `root` in the container.\n\nPlease note that the live deployment of https://ocaml.org is in a dedicated Docker container which does not contain any secrets. Thus the vulnerability is in the ocaml.org source code, but there has been no impact for the live deployment.\n\n## Affected code\n\n`src/ocamlorg_web/lib/handler.ml:1544 (commit ocaml/ocaml.org@31249f2d69fcb243284d3a6a2b91c0a4963f25b5)`:\n\n```ocaml\nlet v2_asset req =\n  let\u003c/\u003e? path =\n    try Some (Config.v2_path ^ Dream.target req) with _ -\u003e None\n  in\n  let\u003c/\u003e? () = if Sys.file_exists path then Some () else None in\n  Filename.(Dream.from_filesystem (dirname path) (basename path) req)\n```\n\nThe route is public (`src/ocamlorg_web/lib/router.ml:186`):\n\n```ocaml\nDream.get \"/conferences/ocaml/**\" Handler.v2_asset;\n```\n\n`Config.v2_path` is `OCAMLORG_V2_PATH`, set to `/v2` in the production image (`Dockerfile:68`). `Dream.target` is the request target as received; nothing rejects `.` or `..` components.\n\n## Timeline\n\n- 2026-10-31 report via https://github.com/ocaml/security-advisories\n- 2026-11-02 fixed via https://github.com/ocaml/ocaml.org/pull/3942 and deployed ocaml.org","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"high","from":"osv"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"published":"2026-11-06T13:30:00Z","modified":"2026-11-06T17:23:11Z","sources":{"osv":[{"id":"OSEC-2026-28","title":"Unauthenticated arbitrary file read via path traversal in `/conferences/ocaml/**`","description":"`GET /conferences/ocaml/\u003cpath\u003e` on ocaml.org concatenates the raw request target onto `OCAMLORG_V2_PATH` (`/v2`) and serves it with `Dream.from_filesystem`, bypassing the path validation that `Dream.static` performs. An unauthenticated request containing `..` segments escapes `/v2` and reads arbitrary files as the server process, which runs as `root` in the container.\n\nPlease note that the live deployment of https://ocaml.org is in a dedicated Docker container which does not contain any secrets. Thus the vulnerability is in the ocaml.org source code, but there has been no impact for the live deployment.\n\n## Affected code\n\n`src/ocamlorg_web/lib/handler.ml:1544 (commit ocaml/ocaml.org@31249f2d69fcb243284d3a6a2b91c0a4963f25b5)`:\n\n```ocaml\nlet v2_asset req =\n  let\u003c/\u003e? path =\n    try Some (Config.v2_path ^ Dream.target req) with _ -\u003e None\n  in\n  let\u003c/\u003e? () = if Sys.file_exists path then Some () else None in\n  Filename.(Dream.from_filesystem (dirname path) (basename path) req)\n```\n\nThe route is public (`src/ocamlorg_web/lib/router.ml:186`):\n\n```ocaml\nDream.get \"/conferences/ocaml/**\" Handler.v2_asset;\n```\n\n`Config.v2_path` is `OCAMLORG_V2_PATH`, set to `/v2` in the production image (`Dockerfile:68`). `Dream.target` is the request target as received; nothing rejects `.` or `..` components.\n\n## Timeline\n\n- 2026-10-31 report via https://github.com/ocaml/security-advisories\n- 2026-11-02 fixed via https://github.com/ocaml/ocaml.org/pull/3942 and deployed ocaml.org","status":"published","published":"2026-11-06T13:30:00Z","modified":"2026-11-06T17:23:11.515443052Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-06T18:05:03.243497Z"}]}}
