From ae097624da884a556e9d7466fcd79a05429e09b1 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 21 Aug 2026 10:39:23 +0200 Subject: [PATCH 1/2] docs: add a security policy with a private reporting route Modly had no SECURITY.md and no private channel for vulnerability reports, which left email as the only route for researchers. Private vulnerability reporting is now enabled on the repository; this points people at it and sets expectations around it. The policy leads with a threat model and lets the scope follow from it, so that an excluded report comes with the reason it was excluded. Two assumptions are deliberate: workflow files are untrusted input because sharing them is normal, and any web page the user has open is an untrusted caller of the loopback API. The second is why the network-exposure exclusion is narrowed to deliberate exposure only -- a page in the user's own browser needs none. Every claim was checked against the code. The policy does not call the installer signed (no platform signs it), says nothing about PyTorch (we do not ship it), and does not excuse social engineering on the strength of UI warnings that do not exist. --- .github/SECURITY.md | 101 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000..b41e3fc6 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,101 @@ +# Security Policy + +## Scope + +Modly is designed to run locally. It is an Electron desktop application that +spawns a Python backend bound to `127.0.0.1`, and it runs AI models on the +user's own machine. Our threat model assumes: + +- The user installed Modly through a supported channel: the installer published + on the project's GitHub releases page, or a manual install following the + README. +- The user has not installed untrusted extensions. Extensions are arbitrary + Python code and are trusted as much as any other software the user chooses to + install. +- The user may open and run workflow files authored by someone else. Sharing + workflows is a normal thing to do, so a workflow is untrusted input. +- **Any web page the user has open in a browser is an untrusted caller of the + local API.** The backend listens on loopback, but a page in the user's browser + is on the same machine — it must not be able to read, write, or trigger + anything through Modly. +- Model weights are downloaded from the repositories Modly ships or from + repositories the user explicitly chooses. +- Python dependencies are at the versions Modly installs during first-run setup. + +A report is in scope only if it affects a user operating within this threat +model. + +## What We Consider a Vulnerability + +We want to hear about issues where a reasonable user — someone who does not +install untrusted extensions — can be harmed by Modly itself. + +The clearest examples: + +- A **workflow file** that such a user might plausibly open and run, using only + built-in nodes and installed extensions, that leads to code execution, + file access outside the expected directories, or data exfiltration. +- A **web page** that, simply by being open while Modly is running, can reach + the local API to read files, write files, or start work on the user's machine. +- An **extension manifest** that escapes its own directory, or that causes code + outside the extension to be loaded into a privileged context. +- A flaw in the **auto-update** mechanism on the platforms where it is enabled + (Windows and Linux; macOS updates manually): unverified or improperly verified + update payloads, or signature checks that fail open. +- Reaching **Node or main-process privileges** from renderer content, or + otherwise defeating the `contextIsolation` boundary between the renderer and + the preload bridge. + +When submitting a report, please include a clear description of why this is a +problem for a typical local Modly user. Reports without this context are +difficult to act on. + +## What We Do Not Consider a Security Vulnerability + +Please report the following through regular GitHub issues instead. Filing them +as security reports will likely cause them to be deprioritized or closed. + +- **Issues that require the user to deliberately expose the backend to the + network.** Modly binds to `127.0.0.1` and offers no option to do otherwise. If + you put a reverse proxy or a port forward in front of it, you have chosen to + expose it and are responsible for securing that deployment. Note that this + exclusion does *not* cover attacks from a web page on the user's own machine — + those need no exposure and are in scope, as described above. +- **Issues that require a specific third-party extension to be installed.** + Extensions are third-party code. Report those to the maintainer of the + extension. +- **Malicious content inside model weights the user chooses to download.** + Modly fetches weights from the repository named by an extension or by the + user. Report those to the repository host; if an extension points at a + malicious repository, report it to that extension's maintainer. +- **Vulnerabilities that depend on dependency versions we neither ship nor + recommend.** +- **Crashes, hangs, or memory exhaustion** from a heavy mesh, a large image, or + a runaway workflow. Annoying, but not a security issue in our model. File a + regular bug. +- Automated scanner output submitted without a working reproduction. + +## Supported Versions + +Modly is pre-1.0 (currently 0.x). Security fixes ship in the most recent +release only. Please confirm the issue on the latest version before reporting. + +## Reporting + +If you believe you have found an issue that falls within the scope above, please +report it privately via GitHub's +[Report a vulnerability](https://github.com/lightningpixel/modly/security/advisories/new) +feature rather than opening a public issue, discussion, or Discord message. + +Please include: + +- A description of the vulnerability and the affected component. +- Reproduction steps, ideally with a minimal workflow file or proof of concept. +- The Modly version, install method, and operating system. +- An explanation of how this affects a typical local user as described in the + threat model. + +We aim to acknowledge valid reports within 3 business days, and we will +coordinate a fix and a disclosure timeline with you. Reporters are credited in +the resulting advisory and in the release notes unless they prefer to remain +anonymous. From 92d4c1b7bf9c59cdb58384224c4f57bc97f57452 Mon Sep 17 00:00:00 2001 From: OPTI-james Date: Wed, 26 Aug 2026 21:07:03 +0100 Subject: [PATCH 2/2] Fix Windows deadlock: decode extension subprocess pipes as UTF-8 With bare text=True the host decodes worker stdout/stderr with the locale codec (cp1252 on Windows), which lacks bytes like 0x8f from tqdm's block glyphs. The stderr reader thread then dies with UnicodeDecodeError, the pipe fills, and the worker blocks forever on its next write - generations wedge silently mid-run (and HF weight downloads stall the same way). Decode both pipes as UTF-8 with errors=replace and set PYTHONIOENCODING=utf-8 for the child so both ends of the pipe agree. Co-Authored-By: Claude Fable 5 --- api/services/extension_process.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/services/extension_process.py b/api/services/extension_process.py index 60ce6787..94a49c63 100644 --- a/api/services/extension_process.py +++ b/api/services/extension_process.py @@ -88,6 +88,9 @@ def _build_env(self) -> dict: env["SSL_CERT_FILE"] = certifi.where() except ImportError: pass + # Keep the child's stdio UTF-8 so it matches the UTF-8 decode on + # our side of the pipes (Windows would otherwise pick cp1252). + env.setdefault("PYTHONIOENCODING", "utf-8") return env def _start(self) -> None: @@ -104,12 +107,18 @@ def _start(self) -> None: # older reader thread cannot poison startup for the new process. run_queue: queue.Queue = queue.Queue() self._queue = run_queue + # Explicit UTF-8: with bare text=True Windows decodes the pipes + # with the locale codec (cp1252), which lacks bytes like 0x8f + # from tqdm's block glyphs — the reader thread then dies mid-run + # and the child deadlocks writing to the full stderr pipe. self._proc = subprocess.Popen( [str(python), str(_RUNNER_PATH)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + encoding="utf-8", + errors="replace", bufsize=1, env=self._build_env(), )