From 0a7c4164141c48dd0441df6e3df5ab2d655f2d79 Mon Sep 17 00:00:00 2001 From: Prateek Singh Date: Fri, 7 Aug 2026 19:07:01 -0400 Subject: [PATCH] fix(mcp): guard buffer reads at one chokepoint instead of per verb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retesting v0.1.126 found a sixth credential door. select_range accepted any range inside a private key and get_selection returned it as text, so a file that open_file, read_tab, find_in_tab, search_project and resources/read all correctly refused was readable one selection at a time. get_selection had been exempted deliberately, on the reasoning that a selection is human-made so reading it back implies consent. select_range makes that false: the CLIENT chooses the range. apply_edit was a quieter form of the same thing — it reads the buffer as a match oracle, so "no match" versus an approval card naming the match reports what a file contains, one probe at a time, without ever returning text. The important part is not the sixth door. It is why there kept being another: v0.1.125 the deny-list existed in two hand-maintained copies -> unify LIST v0.1.126 the unified list ran in 1 of 4 read paths -> add 3 checks v0.1.127 a fifth and sixth path nobody had enumerated Every one of those fixes was correct and none converged, because what kept going stale was the ENUMERATION, not the list. Each new verb had to remember to ask, and a guard that depends on remembering is not a guard. So the check moved to where buffer text is FETCHED. McpBridge::tabTextFor is now the only way it leaves the host; select_range refuses to even stage a selection over a credential file, so no future verb inherits a leak already set up for it. A test lints mcp_bridge.cpp and fails the build on a direct m_host.tabText / m_host.selection call. A source-reading test is unusual and it earns its place here: the per-verb tests can only cover verbs that exist today, and this defect has twice arrived through one that was not on a list. It carries a vacuity guard, so a rename that breaks the pattern fails rather than passing silently. Also fixed: - read_tab's marker said "[truncated at 5 MB]" whatever cap applied — a constant left from before max_bytes existed. Now "[truncated: showing N of M characters]". The v0.1.126 notes claimed read_tab "always reports truncated and total_chars"; that holds for the editor<->sidecar wire and not for the tool result, which is plain text. Docs corrected; the shipped v0.1.126 entry left as-is rather than quietly rewritten. - select_range advertised tab_id in its inputSchema and rejected it with -32602. The v0.1.126 sweep matched only the single-line reject_extras form and select_range is the one written multi-line. Rather than fix the one, a test now walks the entire tool list and asserts every advertised tab_id is genuinely accepted — a schema that lies makes correct clients fail. - list_open_tabs published the field as `id` while every verb takes `tab_id`. Both names ship for one release. 76/76 C++ suites, 66 sidecar protocol tests, both Cargo feature sets. Every fix red-state verified by restoring the pre-fix code; the two security tests were confirmed to fail independently, which is the property that makes the lint worth having. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W6BGY2dFUSujZeS3vTDikq --- CHANGELOG.md | 21 ++++ CMakeLists.txt | 9 +- README.md | 23 ++--- docs/docs.html | 22 ++--- docs/index.html | 76 ++++++++------- docs/llms.txt | 4 +- docs/mcp.html | 37 +++++-- notepatra-mcp/Cargo.lock | 2 +- notepatra-mcp/Cargo.toml | 2 +- notepatra-mcp/mcpb/manifest.json | 2 +- notepatra-mcp/src/tools.rs | 5 + notepatra-mcp/src/transport/mock.rs | 1 + notepatra-mcp/src/transport/mod.rs | 37 +++++-- notepatra-mcp/src/transport/socket.rs | 1 + notepatra-mcp/tests/protocol.rs | 99 +++++++++++++++++-- release_notes/v0.1.127.md | 67 +++++++++++++ src/mcp_bridge.cpp | 95 ++++++++++++++---- src/mcp_bridge.h | 22 +++++ test_mcp_bridge.cpp | 133 ++++++++++++++++++++++++++ 19 files changed, 552 insertions(+), 106 deletions(-) create mode 100644 release_notes/v0.1.127.md diff --git a/CHANGELOG.md b/CHANGELOG.md index db66cd6..d1a8698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver --- +## [0.1.127] — 2026-08-07 + +**A sixth credential door, found by retesting v0.1.126 — and the reason there keeps being another one.** `select_range` let a client highlight any range of a private key and `get_selection` read it back, so the file was readable in chunks while five other doors correctly refused. This release stops adding guards to a hand-maintained list and moves the check to the one place buffer text is fetched. + +### Security +- **`select_range` + `get_selection` returned credential-file contents** that `open_file`, `read_tab`, `find_in_tab`, `search_project` and `resources/read` all refused. `get_selection` was exempted on the reasoning that a selection is human-made, so reading it back implies consent — but `select_range` lets the *client* choose the range, which makes that reasoning false. `apply_edit` was a quieter form of the same thing: it reads the buffer as a match oracle, so "no match" versus an approval card naming the match reports what a file contains, one probe at a time, without ever returning text. +- **The fix is structural, not a sixth guard.** Three consecutive releases shipped this same defect shape — v0.1.125 unified two divergent copies of the deny *list*, v0.1.126 added the check to four verbs, and the retest then found a fifth and sixth. Every fix was correct and none converged, because what kept going stale was the **enumeration**. Buffer text now leaves the host through exactly one accessor, `McpBridge::tabTextFor`, which refuses before returning; `select_range` refuses to *stage* a selection over a credential file at all. A test lints `mcp_bridge.cpp` and fails the build on any direct `m_host.tabText` / `m_host.selection` call, so a verb added later cannot quietly reintroduce the class. + +### Fixed +- **`read_tab`'s truncation marker claimed a 5 MB cap whatever cap actually applied.** Ask for `max_bytes: 100` and the text still ended `[truncated at 5 MB]`. The tool result is plain text with no side channel, so the counts the bridge sends were never reaching the caller at all. The marker now reads `[truncated: showing N of M characters]`. +- **`select_range` advertised `tab_id` in its `inputSchema` and then rejected it** with `-32602 unexpected argument`, so a client generating calls from the published schema failed. The v0.1.126 sweep matched only the single-line `reject_extras` form and `select_range` is the one written multi-line. A new test now walks the entire tool list and asserts every advertised `tab_id` is genuinely accepted, rather than trusting a per-verb check. +- **`list_open_tabs` published the field as `id` while every verb takes `tab_id`.** Both names are emitted for one release so v0.1.126 clients keep working. + +### Corrected +- The v0.1.126 notes said `read_tab` "always reports `truncated` and `total_chars`". That is true of the editor↔sidecar wire and **not** of the MCP tool result, which is plain text. The docs now describe the marker instead. The v0.1.126 entry below is left as shipped rather than quietly rewritten. + +### Internal +- 98 C++ suites and 66 sidecar protocol tests; all three fixes red-state verified by restoring the pre-fix code. The lint and the behavioural test were confirmed to fail independently — the lint catches a raw call site even when no one has thought to test the verb it appears in. + +--- + ## [0.1.126] — 2026-08-07 **A retest of v0.1.125 confirmed all four Windows fixes and found nine more defects underneath.** The severe one is the same leak from a different door: v0.1.125 taught `search_project` to skip credential files, but `open_file` and `read_tab` were never told, so an agent that asked for `~/.ssh/id_rsa` **by name** still got the whole key back. All nine are fixed. No new editor features. diff --git a/CMakeLists.txt b/CMakeLists.txt index fba4938..e8a9c37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(Notepatra VERSION 0.1.126 LANGUAGES CXX) +project(Notepatra VERSION 0.1.127 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -2662,7 +2662,12 @@ if(NOTEPATRA_BUILD_TESTS AND EXISTS "${CMAKE_SOURCE_DIR}/test_mcp_bridge.cpp") ) target_include_directories(test_mcp_bridge PRIVATE ${CMAKE_SOURCE_DIR}/src) target_compile_definitions(test_mcp_bridge PRIVATE - NOTEPATRA_VERSION="${PROJECT_VERSION}") + NOTEPATRA_VERSION="${PROJECT_VERSION}" + # v0.1.127 (NP-14): the suite LINTS mcp_bridge.cpp for direct + # m_host.tabText / m_host.selection calls, so it needs the source + # path. A test that reads its own subject's source is unusual; the + # alternative was a fourth release of the same credential leak. + NOTEPATRA_SOURCE_DIR="${CMAKE_SOURCE_DIR}") target_link_libraries(test_mcp_bridge PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Concurrent Qt5::Sql Qt5::Test) diff --git a/README.md b/README.md index 8fb5906..41e20aa 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Not a port. Not a wrapper. Something new — **for everyone**. I asked: **what would a small native code editor look like if it was built today, in 2026, when AI is part of every developer's workflow, and ran natively on Linux + macOS + Windows from one codebase?** -The answer: a tiny native executable — ~12 MB bare (~12.6 MB on Linux x64) on every platform — with a Rust-powered core, Scintilla editing engine, and local-first AI integration (cloud backends optional). v0.1.126 downloads: 4.4 MB Linux x64 (tarball, Qt from the system), 27.7 MB on macOS (DMG with bundled Qt), 32.8–43.0 MB on Windows (MSI/zip/setup.exe with bundled Qt DLLs). An editor that can fix your broken JSON with regex in milliseconds — and when regex isn't enough, it asks your AI to figure it out — local by default, six cloud backends one click away when you want a frontier model. No telemetry. No subscription. No mandatory API key. +The answer: a tiny native executable — ~12 MB bare (~12.6 MB on Linux x64) on every platform — with a Rust-powered core, Scintilla editing engine, and local-first AI integration (cloud backends optional). v0.1.127 downloads: 4.4 MB Linux x64 (tarball, Qt from the system), 27.7 MB on macOS (DMG with bundled Qt), 32.8–43.0 MB on Windows (MSI/zip/setup.exe with bundled Qt DLLs). An editor that can fix your broken JSON with regex in milliseconds — and when regex isn't enough, it asks your AI to figure it out — local by default, six cloud backends one click away when you want a frontier model. No telemetry. No subscription. No mandatory API key. Notepatra started on Linux — because that's where the gap was. But great tools shouldn't have borders. **Notepatra runs on Linux, Windows, and macOS.** Same codebase. Same features. No one gets left behind. @@ -287,7 +287,7 @@ Full tool reference, Claude Desktop / Agents SDK snippets, security model, and h **Why this hybrid?** - **C++** because Qt and QScintilla are C++ — zero friction for UI - **Rust** because file I/O, text processing, and parsing must never crash — Rust's ownership system guarantees memory safety -- **Result**: the speed of C++, the safety of Rust. The bare executable is **~12 MB** on every platform (~12.6 MB Linux x64, similar on macOS / Windows). Latest v0.1.126 download sizes: **4.4 MB** Linux x64 tar.gz · **4.1 MB** Linux ARM64 tar.gz · **27.7 MB** macOS DMG (with bundled Qt) · **43.0 MB** Windows MSI · **32.8 MB** Windows NSIS · **37.4 MB** Windows portable zip. _Installed footprint on Windows is ~75-85 MB after the MSI extracts bundled Qt + QScintilla DLLs — normal for any Qt-based installer._ +- **Result**: the speed of C++, the safety of Rust. The bare executable is **~12 MB** on every platform (~12.6 MB Linux x64, similar on macOS / Windows). Latest v0.1.127 download sizes: **4.4 MB** Linux x64 tar.gz · **4.1 MB** Linux ARM64 tar.gz · **27.7 MB** macOS DMG (with bundled Qt) · **43.0 MB** Windows MSI · **32.8 MB** Windows NSIS · **37.4 MB** Windows portable zip. _Installed footprint on Windows is ~75-85 MB after the MSI extracts bundled Qt + QScintilla DLLs — normal for any Qt-based installer._ --- @@ -307,7 +307,7 @@ irm https://notepatra.org/install.ps1 | iex That's it. Auto-detects your OS, downloads the right binary, installs it, adds to PATH, creates shortcuts. -### Or download manually — [Latest release: v0.1.126](https://github.com/singhpratech/notepatra/releases/latest) +### Or download manually — [Latest release: v0.1.127](https://github.com/singhpratech/notepatra/releases/latest) | Platform | Download | Size | What's inside | |---|---|---|---| @@ -332,11 +332,11 @@ For one-time-install-then-every-user-sees-it on a shared machine, or silent push | OS | Artefact | Silent admin install | |---|---|---| -| 🪟 **Windows** | [`notepatra-x.x.x.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-0.1.126.msi /quiet` — installs to `C:\Program Files\Notepatra\`, adds system PATH, registers HKCR file associations, all-users Start Menu. WiX-built, MajorUpgrade-aware, SCCM-friendly. | +| 🪟 **Windows** | [`notepatra-x.x.x.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-0.1.127.msi /quiet` — installs to `C:\Program Files\Notepatra\`, adds system PATH, registers HKCR file associations, all-users Start Menu. WiX-built, MajorUpgrade-aware, SCCM-friendly. | | 🍎 **macOS** | [`Notepatra.dmg`](https://github.com/singhpratech/notepatra/releases/latest) | Mount + `sudo cp -R "/Volumes/Notepatra/Notepatra.app" /Applications/` from a deployment script. Or open the DMG manually and drag to `/Applications` (admin password). Notarised + stapled. | -| 🐧 **Debian / Ubuntu / Mint / Pop!_OS** (x64 + ARM64) | [`notepatra_0.1.126_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra_0.1.126_amd64.deb` — installs to `/opt/notepatra/` + symlink at `/usr/bin/notepatra`, hicolor icons, `.desktop` registration. ARM64: replace `amd64` → `arm64`. | -| 🐧 **Fedora / RHEL / CentOS Stream / Rocky / Alma** (x64 + ARM64) | [`notepatra-0.1.126-1.x86_64.rpm`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo dnf install ./notepatra-0.1.126-1.x86_64.rpm` — same layout as the .deb. ARM64: replace `x86_64` → `aarch64`. Bundles QScintilla 2.14.1 alongside the binary because Fedora ships an incompatible packaging. | -| 🐧 **Arch / openSUSE Tumbleweed / Manjaro / EndeavourOS / other glibc 2.38+** | [`Notepatra-0.1.126-x86_64.AppImage`](https://github.com/singhpratech/notepatra/releases/latest) | `chmod +x Notepatra-0.1.126-x86_64.AppImage && sudo cp Notepatra-0.1.126-x86_64.AppImage /opt/notepatra.AppImage && sudo ln -s /opt/notepatra.AppImage /usr/local/bin/notepatra`. Requires glibc 2.38+ (Ubuntu 24.04+, Fedora 40+, Arch, Tumbleweed). Older distros: use the .deb / .rpm. | +| 🐧 **Debian / Ubuntu / Mint / Pop!_OS** (x64 + ARM64) | [`notepatra_0.1.127_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra_0.1.127_amd64.deb` — installs to `/opt/notepatra/` + symlink at `/usr/bin/notepatra`, hicolor icons, `.desktop` registration. ARM64: replace `amd64` → `arm64`. | +| 🐧 **Fedora / RHEL / CentOS Stream / Rocky / Alma** (x64 + ARM64) | [`notepatra-0.1.127-1.x86_64.rpm`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo dnf install ./notepatra-0.1.127-1.x86_64.rpm` — same layout as the .deb. ARM64: replace `x86_64` → `aarch64`. Bundles QScintilla 2.14.1 alongside the binary because Fedora ships an incompatible packaging. | +| 🐧 **Arch / openSUSE Tumbleweed / Manjaro / EndeavourOS / other glibc 2.38+** | [`Notepatra-0.1.127-x86_64.AppImage`](https://github.com/singhpratech/notepatra/releases/latest) | `chmod +x Notepatra-0.1.127-x86_64.AppImage && sudo cp Notepatra-0.1.127-x86_64.AppImage /opt/notepatra.AppImage && sudo ln -s /opt/notepatra.AppImage /usr/local/bin/notepatra`. Requires glibc 2.38+ (Ubuntu 24.04+, Fedora 40+, Arch, Tumbleweed). Older distros: use the .deb / .rpm. | > All artefacts ship with cosign `.sig` + `.pem` for keyless Sigstore verification and SLSA build provenance. See **[Verify your download](#verify-your-download)** below. @@ -346,9 +346,9 @@ For teams that **can't or won't send code to public LLM endpoints** — regulate | OS | Artefact | Silent admin install | |---|---|---| -| 🪟 **Windows** | [`notepatra-local-ai-0.1.126.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-local-ai-0.1.126.msi /quiet` — installs to `C:\Program Files\Notepatra Local AI\`, distinct UpgradeCode so SCCM treats it as its own product. Add/Remove Programs shows "Notepatra Local AI". | -| 🐧 **Debian/Ubuntu x64** | [`notepatra-local-ai_0.1.126_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.126_amd64.deb` | -| 🐧 **Debian/Ubuntu ARM64** | [`notepatra-local-ai_0.1.126_arm64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.126_arm64.deb` | +| 🪟 **Windows** | [`notepatra-local-ai-0.1.127.msi`](https://github.com/singhpratech/notepatra/releases/latest) | `msiexec /i notepatra-local-ai-0.1.127.msi /quiet` — installs to `C:\Program Files\Notepatra Local AI\`, distinct UpgradeCode so SCCM treats it as its own product. Add/Remove Programs shows "Notepatra Local AI". | +| 🐧 **Debian/Ubuntu x64** | [`notepatra-local-ai_0.1.127_amd64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.127_amd64.deb` | +| 🐧 **Debian/Ubuntu ARM64** | [`notepatra-local-ai_0.1.127_arm64.deb`](https://github.com/singhpratech/notepatra/releases/latest) | `sudo apt install ./notepatra-local-ai_0.1.127_arm64.deb` | **The binary physically cannot reach `api.openai.com`, `api.anthropic.com`, `openrouter.ai`, `api.mistral.ai`, `generativelanguage.googleapis.com`, or any other public LLM endpoint.** Every `QNetworkAccessManager` request goes through an allowlist that only accepts: @@ -360,7 +360,7 @@ For teams that **can't or won't send code to public LLM endpoints** — regulate Local Ollama, local llama.cpp, self-hosted Ollama on the LAN, and any other OpenAI-compatible server you have installed locally or on your private network — **all continue to work** in the cloud-free build. Only public-cloud LLM endpoints are blocked. The cloud-URL paste box is stripped from the UI as well, so users can't even type a public host. Auditors can confirm by running `strings notepatra | grep -c openai.com` — zero hits. -On Linux the two flavors share the same `notepatra` binary name on disk; `apt` Conflicts ensures only one of `notepatra` / `notepatra-local-ai` is installed at a time, swap transactionally with `sudo apt install ./notepatra-local-ai_0.1.126_amd64.deb`. On Windows the two MSIs are independent products (different UpgradeCode + ProductName + install dir) so they can coexist if needed; admins typically push one or the other based on policy. `notepatra --version` self-identifies the build by name — only the bare lite build carries an edition suffix: `Notepatra Lite v0.1.126` for the lite build and `Notepatra v0.1.126` for the full build (DuckDB bundled), plus `Notepatra Local AI Lite v0.1.126` / `Notepatra Local AI v0.1.126` for the cloud-free (local-ai) builds; the same name shows in the window title bar and the About dialog. +On Linux the two flavors share the same `notepatra` binary name on disk; `apt` Conflicts ensures only one of `notepatra` / `notepatra-local-ai` is installed at a time, swap transactionally with `sudo apt install ./notepatra-local-ai_0.1.127_amd64.deb`. On Windows the two MSIs are independent products (different UpgradeCode + ProductName + install dir) so they can coexist if needed; admins typically push one or the other based on policy. `notepatra --version` self-identifies the build by name — only the bare lite build carries an edition suffix: `Notepatra Lite v0.1.127` for the lite build and `Notepatra v0.1.127` for the full build (DuckDB bundled), plus `Notepatra Local AI Lite v0.1.127` / `Notepatra Local AI v0.1.127` for the cloud-free (local-ai) builds; the same name shows in the window title bar and the About dialog. ### Verify your download @@ -622,6 +622,7 @@ Notepatra follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic | Version | Date | Highlights | |---|---|---| +| [**v0.1.127**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.127) | 2026-08-07 | **Five doors refused a private key. The sixth handed it over in chunks.** Retesting v0.1.126 found `select_range` + `get_selection` returning credential-file contents that `open_file`, `read_tab`, `find_in_tab`, `search_project` and `resources/read` all correctly refused — `get_selection` had been exempted on the reasoning that a selection is human-made, but `select_range` lets the *client* choose the range, which makes that reasoning false. `apply_edit` was a quieter form of the same thing: reading the buffer as a match **oracle**, so "no match" versus a card naming the match reports a file's contents one probe at a time. **The fix is structural.** This is the third release with the same shape — v0.1.125 unified two hand-maintained copies of the deny *list*, v0.1.126 added the check to four verbs, and the retest found a fifth and sixth. Every fix was correct and none converged, because what kept going stale was the **enumeration**. Buffer text now leaves the editor through exactly one accessor that refuses before returning, `select_range` will not even *stage* a selection over a credential file, and a test lints the bridge source and fails the build on any direct buffer read that bypasses it. **Also fixed:** `read_tab`'s marker claimed `[truncated at 5 MB]` whatever cap actually applied (now `[truncated: showing N of M characters]`, and the v0.1.126 claim that it "always reports truncated and total_chars" is corrected — true of the wire, not the tool result) · `select_range` advertised `tab_id` in its schema and rejected it with `-32602`, so a test now walks the whole tool list asserting every advertised selector is accepted · `list_open_tabs` emitted `id` where verbs take `tab_id`, both ship for one release. | | [**v0.1.126**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.126) | 2026-08-07 | **v0.1.125 taught `search_project` to skip credential files — it never told `open_file` or `read_tab`, so an agent that asked for `~/.ssh/id_rsa` by name still got the whole key back.** A deep retest confirmed all four v0.1.125 Windows fixes on real hardware and found nine more defects underneath. `open_file`, `read_tab`, `find_in_tab` and both legs of `search_project` now check the same deny-list — v0.1.125 had guarded only the filesystem walk, so the same file sitting in an open tab was read straight out of the buffer. **`format_json` invented data and reported success:** it called the JSON panel's auto-*fixer*, so `[1,2` came back as `[1,2]` with `isError:false` — a truncated config file made syntactically valid and semantically invented. The MCP path validates strictly now; the panel keeps its fixer. **Fixed:** a search with zero matches was reported as "No workspace folder is open", which is a different fact about the world · `workspace_searched`/`scope` were sent by the bridge and dropped by the Rust struct, so no client ever saw the fields v0.1.125 promised · without `--socket` every response was fabricated with no marker, so a dropped flag produced an assistant describing three files that do not exist · `find_in_tab` rejected a `title` that `read_tab` accepted · an invalid pagination cursor was ignored and the full list returned. **Changed:** tabs now have a stable `id` — `tab_index` is positional and re-points at a different document when a tab closes, and out-of-range was the *lucky* case · `read_tab` takes `max_bytes` and always reports `truncated` and `total_chars`. | | [**v0.1.125**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.125) | 2026-08-07 | **A one-word MCP search could return the contents of your SSH private key — fixed, along with three other defects found by testing the MCP server on Windows.** No new editor features; every change is a fix. The credential deny-list existed in **two** hand-maintained copies that had drifted in *both* directions (`ai_tools.cpp` had `*.tfvars`/`*.tfstate`/`.pypirc`/the `.env` family; `git_hunk_apply.cpp` had `*.jks` and an unanchored `id_rsa`; neither was a superset) and `search_project` consulted **neither** — so `read_file` refused `~/.ssh/id_rsa` while a one-word search returned its lines. `src/path_denylist.h` is now the single list, built as the union, with all three callers routed through it. **`search_project` also walked the entire home directory:** `FileExplorer`'s constructor set `m_rootPath = QDir::homePath()` as a *display placeholder*, and four guards read it as "the workspace" — including the AI CSV sandbox and a security guard that had therefore never once closed. `workspaceRoot()` now stays empty until a folder is opened, and a workspace-less search reports `workspace_searched` and `scope` so a partial search cannot pass for a complete one. **Fixed:** `goto_line` past EOF reported `{"line": 99999, "ok": true}` from the top of the document, so an assistant aiming at the end of a file wrote at the beginning behind a correct-looking approval card · `insert_text` with `col` but no `line` silently dropped `col` and wrote at the cursor · the sidecar hung on every unrecognised argument (`--version` printed nothing and blocked on stdin; `--sokcet` silently started the **mock** server so the client received fabricated tabs) · the AI workspace root flapped on every Ctrl+Tab, swapping chat history and cancelling pending write approvals · **Windows binaries reported version 0.1.0** to winget, SCCM, Intune and File Properties for 124 releases — the `.rc` is generated from CMake now, with a CI step reading `VersionInfo` back off the shipped `.exe`. Every new test red-state verified by restoring the pre-fix code, not by negating a condition. | | [**v0.1.124**](https://github.com/singhpratech/notepatra/releases/tag/v0.1.124) | 2026-07-28 | **Invisible characters are now visible — 527 of them, where Notepad++ draws 113 — and two Find/Replace bugs that were destroying text are fixed.** A file containing a zero-width space used to look exactly like a file without one: symbol support was only whitespace dots and EOL markers, so every zero-width, bidirectional and exotic-space codepoint was undrawable. **View → Show Symbol** is rebuilt to Notepad++'s eight-item structure in its order (Show Space and Tab · Show End of Line · Show Non-Printing Characters · Show Control Characters & Unicode EOL · Show Every Other Invisible Character · Show All Characters · Show Indent Guide · Show Wrap Symbol). The non-printing (49) and control/Unicode-EOL (64) tables are transcribed from Notepad++'s source byte-for-byte, abbreviations included (`ZWNBSP` for U+FEFF, `OSPM` for U+1680, `SGCI` for U+0099). **"Show Every Other Invisible Character"** adds the 414 codepoints Notepad++'s fixed tables miss — variation selectors, the TAG block U+E0020–E007F, the Hangul fillers, the combining grapheme joiner. Blobs can show codepoints (`U+200B`) instead of abbreviations, and every Show Symbol setting now persists across restarts and applies to all tabs. Defaults match Notepad++ — everything off except the control-character display, so non-printing characters are **off out of the box**. **Fixed:** Replace All silently deleted any replacement containing `$` (handed to the regex engine as a substitution template, so `$100` parsed as capture group 100 and expanded to nothing) · Replace reported replacements it had not made and overwrote unrelated selections · Find in Files' default `*.*` filter skipped extensionless files like `Makefile` and `.gitignore` · Mark All highlighted the wrong spans (offsets computed into a lowercased copy, and lowercasing is not length-preserving in UTF-8) · indent guides rendered as a white-on-black barber pole in Dark and Monokai. Three new suites (75 total Full), each red-state verified. | diff --git a/docs/docs.html b/docs/docs.html index d21f886..9a2521c 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -299,7 +299,7 @@
Notepatra Notepatra - v0.1.126 docs + v0.1.127 docs