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 @@