From 8d268abdbcadbd022edd9e66f4a6d2d6ed35d5c7 Mon Sep 17 00:00:00 2001 From: Cristian Girlea Date: Mon, 26 Oct 2026 16:37:13 +0200 Subject: [PATCH] deps: move the model wheels to numpy 2.5.4, onnxruntime 1.31.0 and tokenizers 0.23.3 --- .github/workflows/tests.yml | 2 +- CHANGELOG.md | 4 ++++ docs/security.md | 2 +- requirements-model.lock | 32 +++++++++++++++----------------- requirements-model.txt | 6 +++--- tests/python/test_jobs.py | 12 ++++++++++++ worker/ai_bridge/jobs.py | 2 ++ 7 files changed, 38 insertions(+), 22 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b960ecd..7ac577f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -136,7 +136,7 @@ jobs: run: | docker run --rm --entrypoint python bridge-worker:ci -c 'import ai_bridge.server' docker run --rm bridge-worker:ci --help - docker run --rm --entrypoint python bridge-worker-onnx:ci -c 'import onnxruntime, tokenizers, numpy, ai_bridge.server' + docker run --rm -e ORT_DISABLE_TELEMETRY=1 --entrypoint python bridge-worker-onnx:ci -c 'import onnxruntime, tokenizers, numpy, ai_bridge.server' docker run --rm bridge-worker-onnx:ci --help # Each image serves an authenticated /healthz; the ONNX one on the lexical backend, since CI has no models. - name: The images answer /healthz diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c6b69..50faaef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to this project are documented in this file. The format foll ## [Unreleased] +### Changed + +- The model wheels move to `numpy` 2.5.4, `onnxruntime` 1.31.0 and `tokenizers` 0.23.3 in `requirements-model.txt` and `requirements-model.lock`, which brings `huggingface-hub` 2.3.0 with `httpx2` in place of `requests` and drops `coloredlogs` and `sympy`. ONNX Runtime 1.31 sends telemetry to Microsoft's collector from import on, so every worker child now starts with `ORT_DISABLE_TELEMETRY=1`, whatever the service's environment says. + ## [0.4.0] - 2026-10-26 ### Added diff --git a/docs/security.md b/docs/security.md index 73f95c5..5419ae9 100644 --- a/docs/security.md +++ b/docs/security.md @@ -4,7 +4,7 @@ This service is designed for trusted application-to-service traffic on a private - Generate a service token of at least 32 non-whitespace ASCII characters. Never commit it. A token reaches only the jobs it submitted: another token's job answers 404 as if it did not exist, and idempotency keys are per token. Give each application, and the MCP server, its own token in `BRIDGE_TOKENS` or `BRIDGE_TOKENS_FILE` (mode 0600 or stricter), so one can be revoked by removing its entry and restarting the worker without touching the others, and a leaked token exposes only its own client's jobs. Capacity is shared, so by default any token can fill the worker and starve the others; set `BRIDGE_OWNER_CAPACITY` below `BRIDGE_CAPACITY` to cap one token's queued, running and retained jobs, which is off by default. A token at its cap gets the same 429 as a full worker, so it learns nothing of the other tokens' load; the cap is the same for every token, so where it times the number of tokens exceeds the capacity the tokens together can still fill the worker: set it at most the capacity divided by the number of tokens where every token must always find room; `/healthz`, `/readyz` and `/metrics` answer every token, and the metrics carry the token names, never the tokens. The job table stores a job's owner by name only. - Enforce end-user authentication and per-job ownership in the PHP application. The example deliberately omits those application-specific policies and publishes no ports. -- Only trusted task implementations are allowed. The service removes `BRIDGE_TOKEN` and `BRIDGE_TOKENS` from its environment before it starts its child processes, and reads `BRIDGE_TOKENS_FILE` without putting its content there, so no token is in either their environment nor their initial environment block (`/proc//environ`), which a removal inside the child would leave as it was; the children never speak HTTP. Everything else in the service environment stays visible to them. A child serves many jobs in turn and keeps its loaded models; it keeps no reference to a finished job's input or result, but the memory they used is freed, not wiped, so a core dump of a long-lived child can still hold earlier jobs' data where a process that died after each job could not. They are not suitable for arbitrary user-provided scripts. +- Only trusted task implementations are allowed. The service removes `BRIDGE_TOKEN` and `BRIDGE_TOKENS` from its environment before it starts its child processes, and reads `BRIDGE_TOKENS_FILE` without putting its content there, so no token is in either their environment nor their initial environment block (`/proc//environ`), which a removal inside the child would leave as it was; the children never speak HTTP. Everything else in the service environment stays visible to them, except that the service sets `ORT_DISABLE_TELEMETRY=1` before it starts each child, overriding any inherited value, so that ONNX Runtime's telemetry uploader (on from import since 1.31) never starts; the only outbound connection a child could open is thus closed before the runtime loads. A child serves many jobs in turn and keeps its loaded models; it keeps no reference to a finished job's input or result, but the memory they used is freed, not wiped, so a core dump of a long-lived child can still hold earlier jobs' data where a process that died after each job could not. They are not suitable for arbitrary user-provided scripts. - Container configuration uses a non-root user, read-only filesystem, dropped capabilities, no-new-privileges, CPU/memory/PID bounds, and an internal-only network for execution. It mounts only this checkout and the optional dependency/model cache, never a Docker socket or home directory. - Dependency acquisition is separate and internet-enabled. Execution installs hash-checked wheels from the local cache without internet access. The model downloader retrieves fixed files at a pinned revision and checks their hashes; it does not execute model-repository code. - CI runs on GitHub's hosted runners only, because the repository is public. GitHub runs a pull request's workflows as the pull request has them, so a fork's pull request executes its own code, workflow edits included; on a disposable runner of GitHub's that holds nothing of the maintainer's, that is the intended boundary. No workflow names a self-hosted runner, and `tests/python/test_workflows.py` fails if one does again; a fork can edit that test along with the workflows, so while any self-hosted runner is still registered for the repository, keep requiring approval for outside contributors' workflow runs, and unregister it so that there is none a pull request could route its code onto. Every action a workflow uses is pinned by commit hash, and the `actions/checkout` step does not persist credentials; the same test fails if either changes. diff --git a/requirements-model.lock b/requirements-model.lock index 67969b2..034462b 100644 --- a/requirements-model.lock +++ b/requirements-model.lock @@ -1,23 +1,21 @@ # Resolved wheel set for CPython 3.12, Linux x86_64 and aarch64 (glibc). No source distributions. -certifi==2026.10.19 --hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b -charset-normalizer==3.5.1 --hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 --hash=sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa -coloredlogs==15.0.1 --hash=sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934 -filelock==4.2.1 --hash=sha256:7be2662e9e826b9bba28afc0f843a123280d799a2ea44bb5d35ccec67905ae44 +anyio==4.15.1 --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 +click==8.5.0 --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 +filelock==4.2.4 --hash=sha256:301241438c57936ef93f4243a94f2559022268bfcf9b375a9a72eb0f8db8358e flatbuffers==25.12.19 --hash=sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4 -fsspec==2026.7.0 --hash=sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279 -hf-xet==1.6.0 --hash=sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f --hash=sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7 -huggingface-hub==0.36.2 --hash=sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270 -humanfriendly==10.0 --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 -idna==3.19 --hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4 -mpmath==1.3.0 --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c -numpy==2.2.6 --hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 --hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 -onnxruntime==1.22.1 --hash=sha256:2d39a530aff1ec8d02e365f35e503193991417788641b184f5b1e8c9a6d5ce8d --hash=sha256:984cea2a02fcc5dfea44ade9aca9fe0f7a8a2cd6f77c258fc4388238618f3928 +fsspec==2026.9.0 --hash=sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f +h11==0.16.0 --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 +hf-xet==1.7.0 --hash=sha256:2814a6e999d13464c4d679b788cc5d784eb5a4edfc638a31f10e9a11ab531ef8 --hash=sha256:fcfd6c22418e57dd5b3aea649e813b2e2cfb2aebf317b210d90f1fe4b3018b52 +httpcore2==2.16.0 --hash=sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f +httpx2==2.16.0 --hash=sha256:fd2c5d7909e8d86fe92d53020c310ca0e4e431b838c207c5754b9bbf0308d49a +huggingface-hub==2.3.0 --hash=sha256:5da369600233568a126eb5578754935b2add9446b42d9e2fc0927d56840ac1a5 +idna==3.20 --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c +numpy==2.5.4 --hash=sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a --hash=sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2 +onnxruntime==1.31.0 --hash=sha256:5e129d6c56abd53e659cb70f00a108d6824086470ff99c2e47a82e5786563db3 --hash=sha256:35758d7606d578ec5b9d65f6e8a1f488013194c3f6097038a3223cb26d35ef9a packaging==26.3 --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c -protobuf==7.36.1 --hash=sha256:97198b77e369a0abd8e262b8f6c7266c55ddb796a3a12c76d7b8881188ed83aa --hash=sha256:43d3d37b1eb24c113b9b7d02008cac44e423f00b611b7781ae998d7623972969 +protobuf==7.36.2 --hash=sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2 --hash=sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e pyyaml==6.0.3 --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 -requests==2.34.2 --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 -sympy==1.14.0 --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5 -tokenizers==0.21.4 --hash=sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff --hash=sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5 +tokenizers==0.23.3 --hash=sha256:376851d22bcf9d650a5c3090bb83e6cf9e895fbf0595369fa4cd43c1f69b5f87 --hash=sha256:ec82e80e65a862275b97c3d90b7a523df8d9519ee48aeb4e9625b2cc909274e0 tqdm==4.70.1 --hash=sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73 +truststore==0.10.5 --hash=sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c typing-extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 -urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 diff --git a/requirements-model.txt b/requirements-model.txt index 76bfdfe..7bc2a15 100644 --- a/requirements-model.txt +++ b/requirements-model.txt @@ -1,4 +1,4 @@ # Optional CPU inference backend. Only wheels are acquired; remote model code is never run. -onnxruntime==1.22.1 -tokenizers==0.21.4 -numpy==2.2.6 +onnxruntime==1.31.0 +tokenizers==0.23.3 +numpy==2.5.4 diff --git a/tests/python/test_jobs.py b/tests/python/test_jobs.py index eeb8a4f..de6d257 100644 --- a/tests/python/test_jobs.py +++ b/tests/python/test_jobs.py @@ -1456,6 +1456,18 @@ def test_a_child_inherits_one_thread_pins(self): pins = [entry for entry in block if entry.startswith((b"OMP_NUM_THREADS=", b"OPENBLAS_NUM_THREADS=", b"RAYON_NUM_THREADS="))] self.assertEqual(sorted(pins), [b"OMP_NUM_THREADS=1", b"OPENBLAS_NUM_THREADS=1", b"RAYON_NUM_THREADS=1"]) + @unittest.skipUnless(os.path.exists("/proc/self/environ"), "needs /proc to read a child's environment block") + def test_a_child_starts_with_onnx_runtime_telemetry_off(self): + # ONNX Runtime 1.31 starts a client on import that posts to Microsoft's collector unless this is set, so + # an operator's value cannot turn it back on. + self.store.close() + with patch.dict(os.environ, {"ORT_DISABLE_TELEMETRY": "0"}): + self.store = JobStore(Settings(concurrency=1, test_tasks=True)) + pid = self.inspect()["pid"] + block = Path(f"/proc/{pid}/environ").read_bytes().split(b"\0") + switch = [entry for entry in block if entry.startswith(b"ORT_DISABLE_TELEMETRY=")] + self.assertEqual(switch, [b"ORT_DISABLE_TELEMETRY=1"]) + @unittest.skipIf(os.name == "nt", "os.kill cannot deliver SIGINT to a process on Windows") def test_a_child_ignores_sigint_idle_and_running(self): pid = self.inspect()["pid"] diff --git a/worker/ai_bridge/jobs.py b/worker/ai_bridge/jobs.py index 42083e8..a1648d3 100644 --- a/worker/ai_bridge/jobs.py +++ b/worker/ai_bridge/jobs.py @@ -446,6 +446,8 @@ def _start_worker(self) -> _Worker | None: parent, child = self._context.Pipe() # numpy's OpenBLAS starts a thread per visible CPU, not per CPU of quota, and all count towards pids_limit. os.environ.update(RAYON_NUM_THREADS="1", OMP_NUM_THREADS="1", OPENBLAS_NUM_THREADS="1") + # ONNX Runtime 1.31 posts telemetry to Microsoft's collector from import on unless this is set. + os.environ["ORT_DISABLE_TELEMETRY"] = "1" process = self._context.Process( target=_serve, args=(child, self.settings.backend, self.settings.model_dir), daemon=True) process.start()