From c2edf555f71f84c7426be38e1f61540db6247a78 Mon Sep 17 00:00:00 2001 From: belkassaby Date: Fri, 15 May 2026 13:30:23 -0400 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20add=20SECURITY.md=20=E2=80=94=20thr?= =?UTF-8?q?eat=20model,=20supply-chain=20notes,=20scanner=20findings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents cellpose-js's threat model (browser-only, no Node fs, no eval, no telemetry) and the input-handling surface inherited from onnxruntime-web: caller-supplied modelUrl and wasmPaths. Provides recommended consumer practices (hardcode or allowlist the model URL, serve ORT sidecars same-origin, optionally validate model integrity, pin the peer dep). Also addresses scanner findings on the ort-web peer dep — labels like "Obfuscated code" and "Supply-chain risk" are structural false positives on minified bundles + binary WASM, not actual malware behavior. README now points at SECURITY.md from a new "## Security" section. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 4 ++++ SECURITY.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 SECURITY.md diff --git a/README.md b/README.md index d7e0d8c..8e99f22 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,10 @@ The demo at `examples/demo/` is a complete client that exercises the full pipeli - Model and algorithm: [Cellpose-SAM (Stringer et al., 2025)](https://www.biorxiv.org/content/10.1101/2025.04.28.651001v1). Original implementation: [MouseLand/cellpose](https://github.com/MouseLand/cellpose) — BSD-3. - Inference runtime: [`onnxruntime-web`](https://github.com/microsoft/onnxruntime). +## Security + +See [SECURITY.md](./SECURITY.md) for the threat model, reporting process, and recommended consumer practices. + ## License MIT — see [LICENSE](./LICENSE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ef04202 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,56 @@ +# Security + +## Reporting a vulnerability + +If you believe you've found a vulnerability in `cellpose-js`, please **do not** open a public issue. Email the maintainer directly or file a [private security advisory](https://github.com/belkassaby/Cellpose.js/security/advisories/new). We'll respond within a week. + +## Threat model and input-handling surface + +`cellpose-js` is a thin TypeScript/WebGPU client wrapping [`onnxruntime-web`](https://www.npmjs.com/package/onnxruntime-web). It runs entirely in a browser context — there is no Node.js code path in the published package. The published tarball contains only `dist/*.{js,d.ts,map}` plus README/LICENSE; no scripts run at install or import time. + +### What the package does at runtime + +1. **`fetch(modelUrl)`** — downloads the model file from a URL the caller supplies to `Cellpose.fromPretrained(modelUrl)`. +2. **IndexedDB write/read** — caches the model bytes locally to skip the network on subsequent visits. +3. **Dynamic `import()`** — `onnxruntime-web`'s WebGPU backend imports its own WASM/JSEP sidecar `.mjs` files from a path set by `configureOrt({ wasmPaths })`. +4. **WebGPU + Web Worker** — runs the ONNX graph on the GPU inside a dedicated worker. + +### Inputs that affect what gets fetched + +| Input | Default | Risk if attacker-controlled | +|---|---|---| +| `modelUrl` argument to `Cellpose.fromPretrained(modelUrl, …)` | none — caller must supply | An attacker who controls this string can cause the browser to download arbitrary content (up to 588 MB and beyond). Same-origin policy and CORS still apply, so the response can't be read cross-origin without permission, but a malicious URL can: (1) waste bandwidth, (2) point at a malformed ONNX that crashes the inference worker, (3) cause IndexedDB to fill with attacker-supplied bytes. | +| `wasmPaths` argument to `configureOrt({ wasmPaths })` | `/ort/` | Same risk class as `modelUrl`. The path is used for dynamic ESM `import()`, which requires same-origin or proper CORS — so cross-origin substitution is hard. Same-origin substitution would require an existing site compromise. | + +### What the package does **not** do + +- No Node.js `fs` reads. The browser build never touches the filesystem. +- No `eval`, `Function()`, or other dynamic code execution from string content. +- No telemetry, analytics, or beaconing. +- No persistent storage other than the model byte cache in IndexedDB (consumer-cleared via `clearCachedModel(url)`). + +### Recommended consumer practices + +If you're integrating `cellpose-js` into a product: + +1. **Hardcode the model URL** or restrict the user-facing input to a vetted allowlist (HuggingFace Hub, your own CDN). Don't accept arbitrary URLs from untrusted users. +2. **Serve `cellpose-js` and its ORT WASM sidecars same-origin** to avoid CORS and dynamic-import edge cases. The reference setup is in [`examples/demo/vite.config.ts`](./examples/demo/vite.config.ts) and is also documented in the README. +3. **Validate model integrity** if your threat model includes a compromised HF Hub URL. `onnxruntime-web`'s session-create will reject malformed graphs, but won't detect a substituted-but-valid model. If integrity matters, check the model's SHA-256 client-side before passing it to `Cellpose.fromPretrained()`. +4. **Lock the `onnxruntime-web` version**. `cellpose-js@0.1.x` declares it as a `~1.26.0` peer dep. You may want to pin it to a specific patch in your own `package.json`. + +## Build & supply chain + +- **Source:** [github.com/belkassaby/Cellpose.js](https://github.com/belkassaby/Cellpose.js) (public, MIT). `main` is protected; all changes via PR. +- **Build:** plain `tsc` emits to `dist/`. No bundler, no minification, no transformation beyond TypeScript transpilation. The published JS is reviewable diff-for-diff against the source. +- **CI:** every push and PR runs `tsc --noEmit`, ESLint, Prettier, Vitest, and a build — see [`.github/workflows/ci-cd.yaml`](./.github/workflows/ci-cd.yaml). +- **Publish provenance:** every npm release ships with a signed [sigstore provenance attestation](https://docs.npmjs.com/generating-provenance-statements) linking the tarball back to the GitHub Actions run that built it. Verify with `npm view cellpose-js dist.attestations`. +- **No install scripts.** The published `package.json` has no `preinstall` / `postinstall` / `prepare` hooks. + +## Known scanner findings + +`onnxruntime-web` (cellpose-js's peer dependency) is sometimes flagged by static-analysis scanners with labels like *"Obfuscated code"* or *"Supply-chain risk"*. These are typically structural false positives: + +- ort-web ships minified bundles and binary `.wasm` blobs by design — large minified JS looks "obfuscated" to a scanner but is just minified. +- ort-web also accepts caller-supplied URLs/paths (it's a generic ONNX runtime), which scanners flag as a permissive input surface. That's the documented behavior of any model-loader library; mitigations belong at the consumer layer (allowlists, hardcoded defaults) — see §Recommended consumer practices above. + +A 2026-05-15 scan of ort-web found no evidence of `eval`, dynamic code execution, credential harvesting, exfiltration, or persistence — the reviewer's note explicitly classifies it as "likely non-malicious library code with a contextual input-handling risk." `cellpose-js` itself adds no new such surface. From 2a6dfc9defd52b2ee0ba171d2c5dee90051e409e Mon Sep 17 00:00:00 2001 From: belkassaby Date: Fri, 15 May 2026 13:33:32 -0400 Subject: [PATCH 2/2] docs: format SECURITY.md with prettier --- SECURITY.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index ef04202..0f55980 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -17,10 +17,10 @@ If you believe you've found a vulnerability in `cellpose-js`, please **do not** ### Inputs that affect what gets fetched -| Input | Default | Risk if attacker-controlled | -|---|---|---| +| Input | Default | Risk if attacker-controlled | +| ------------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `modelUrl` argument to `Cellpose.fromPretrained(modelUrl, …)` | none — caller must supply | An attacker who controls this string can cause the browser to download arbitrary content (up to 588 MB and beyond). Same-origin policy and CORS still apply, so the response can't be read cross-origin without permission, but a malicious URL can: (1) waste bandwidth, (2) point at a malformed ONNX that crashes the inference worker, (3) cause IndexedDB to fill with attacker-supplied bytes. | -| `wasmPaths` argument to `configureOrt({ wasmPaths })` | `/ort/` | Same risk class as `modelUrl`. The path is used for dynamic ESM `import()`, which requires same-origin or proper CORS — so cross-origin substitution is hard. Same-origin substitution would require an existing site compromise. | +| `wasmPaths` argument to `configureOrt({ wasmPaths })` | `/ort/` | Same risk class as `modelUrl`. The path is used for dynamic ESM `import()`, which requires same-origin or proper CORS — so cross-origin substitution is hard. Same-origin substitution would require an existing site compromise. | ### What the package does **not** do @@ -48,7 +48,7 @@ If you're integrating `cellpose-js` into a product: ## Known scanner findings -`onnxruntime-web` (cellpose-js's peer dependency) is sometimes flagged by static-analysis scanners with labels like *"Obfuscated code"* or *"Supply-chain risk"*. These are typically structural false positives: +`onnxruntime-web` (cellpose-js's peer dependency) is sometimes flagged by static-analysis scanners with labels like _"Obfuscated code"_ or _"Supply-chain risk"_. These are typically structural false positives: - ort-web ships minified bundles and binary `.wasm` blobs by design — large minified JS looks "obfuscated" to a scanner but is just minified. - ort-web also accepts caller-supplied URLs/paths (it's a generic ONNX runtime), which scanners flag as a permissive input surface. That's the documented behavior of any model-loader library; mitigations belong at the consumer layer (allowlists, hardcoded defaults) — see §Recommended consumer practices above.