From 0a45fb723117697b1639da87a716744c0f9c066e Mon Sep 17 00:00:00 2001 From: OpenSec <323596076+opensec-intelligence@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:36:12 +0530 Subject: [PATCH 1/3] feat(engine): Linux executables with RELRO and immediate binding Linked with -z relro -z now instead of -z norelro -z lazy: the GOT and the other relocated data are read-only after the loader is done. Executables stay at a fixed address (ET_EXEC): the prebuilt heap points into them. closes #24 --- patches/bun.patch | 49 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/patches/bun.patch b/patches/bun.patch index 6072bf0fb..a031d90c9 100644 --- a/patches/bun.patch +++ b/patches/bun.patch @@ -54,14 +54,14 @@ stale and rewrote in turn. .../libuv/win-thread-stack-reservation.patch | 19 + .../page-commit-on-demand-default.patch | 22 + scripts/build/CLAUDE.md | 2 +- - scripts/build/binary-expectations.ts | 32 +- + scripts/build/binary-expectations.ts | 41 +- scripts/build/bun.ts | 18 +- scripts/build/deps/libuv.ts | 7 +- scripts/build/deps/mimalloc.ts | 11 +- scripts/build/deps/webkit.ts | 56 +- scripts/build/download.ts | 28 +- scripts/build/fetch-cli.ts | 39 +- - scripts/build/flags.ts | 47 +- + scripts/build/flags.ts | 53 +- scripts/build/ninja.ts | 1 + scripts/build/rust.ts | 3 + scripts/build/source.ts | 10 + @@ -177,7 +177,7 @@ stale and rewrote in turn. test/napi/napi-app/binding.gyp | 10 + ...eue_async_work_in_finalizer_experimental.c | 87 ++ test/napi/napi.test.ts | 81 ++ - 131 files changed, 6513 insertions(+), 915 deletions(-) + 131 files changed, 6521 insertions(+), 922 deletions(-) create mode 100644 patches/libuv/win-thread-stack-reservation.patch create mode 100644 patches/mimalloc/page-commit-on-demand-default.patch create mode 100644 test/js/node/process/fd-nonblock-fixture.js @@ -359,7 +359,7 @@ index 4d067e9044..41c47fee81 100644 **`isExecutable` must check `isFile()`.** `X_OK` on a directory means traversable — a `cmake/` dir in PATH would shadow the real cmake binary. diff --git a/scripts/build/binary-expectations.ts b/scripts/build/binary-expectations.ts -index f18a60b06b..0ba5981b7f 100644 +index f18a60b06b..fabdf34910 100644 --- a/scripts/build/binary-expectations.ts +++ b/scripts/build/binary-expectations.ts @@ -7,9 +7,9 @@ @@ -470,7 +470,26 @@ index f18a60b06b..0ba5981b7f 100644 const localWebKitLibs = cfg.webkit === "local" && cfg.linux && cfg.abi !== "android" ? ["libicudata.so.*", "libicui18n.so.*", "libicuuc.so.*"] -@@ -523,7 +523,7 @@ export function binaryExpectations(cfg: Config): BinaryExpectations { +@@ -425,14 +425,13 @@ export function binaryExpectations(cfg: Config): BinaryExpectations { + ...(staticInitializers && { staticInitializers }), + elf: { + // Android requires PIE; everywhere else bun is a fixed-address +- // executable (-fno-pic, see flags.ts). No RELRO / BIND_NOW anywhere: +- // flags.ts links `-z norelro -z lazy` (a startup-time choice from +- // the CMake era, kept as is); recorded so a change is deliberate. ++ // executable (-fno-pic, see flags.ts). Pi-Bolt links Linux with ++ // `-z relro -z now` (flags.ts); FreeBSD keeps `-z norelro -z lazy`. + type: android ? "DYN" : "EXEC", + execStack: false, + rwxLoad: false, +- relro: false, +- bindNow: false, ++ relro: cfg.linux, ++ bindNow: cfg.linux, + tlsSegment: !android, + }, + debugInfo: { symtab: true, debugSections: true, compressed: true }, +@@ -523,7 +522,7 @@ export function binaryExpectations(cfg: Config): BinaryExpectations { forbiddenImports: forbiddenImports(cfg), // No initializer audit: PE has no symbol table to name .CRT$XCU entries by. pe: { @@ -803,7 +822,7 @@ index eebc97cc79..e8abff8093 100644 // github-archive dep fetch: download tarball, extract, patch, stamp // ─────────────────────────────────────────────────────────────────────────── diff --git a/scripts/build/flags.ts b/scripts/build/flags.ts -index 3d40f647a4..bbbc26ad1e 100644 +index 3d40f647a4..59befe77c9 100644 --- a/scripts/build/flags.ts +++ b/scripts/build/flags.ts @@ -499,6 +499,13 @@ export const globalFlags: Flag[] = [ @@ -854,20 +873,20 @@ index 3d40f647a4..bbbc26ad1e 100644 + // .pblay is where the region's arenas are (bmalloc::StaticRegionLayout): read-only, since that decides where the engine + // reads and writes. + flag: ["/SECTION:.pbreg,R", "/SECTION:.pblay,R", "/guard:cf"], - when: c => c.windows, -- desc: "18MB stack reserve (JSC uses deep recursion), no error limit", ++ when: c => c.windows, + desc: "The static region's section is read-only until used; its layout is read-only; Control Flow Guard's table (/guard:cf)", + }, + { + // The DLLs the executable imports are system DLLs: from System32 only (LOAD_LIBRARY_SEARCH_SYSTEM32), not from the folder + // the executable was unzipped to. The delay-loaded ones go there too (c-bindings.cpp, __pfnDliNotifyHook2). + flag: "/DEPENDENTLOADFLAG:0x800", -+ when: c => c.windows, + when: c => c.windows, +- desc: "18MB stack reserve (JSC uses deep recursion), no error limit", + desc: "Imported DLLs from System32 only", }, { flag: "/DEBUG:FULL", -@@ -1333,6 +1368,14 @@ export const linkerFlags: Flag[] = [ +@@ -1333,12 +1368,22 @@ export const linkerFlags: Flag[] = [ when: c => c.abi === "android", desc: "Android: bionic loader requires PIE", }, @@ -882,6 +901,16 @@ index 3d40f647a4..bbbc26ad1e 100644 { flag: [ "-Wl,--as-needed", + "-Wl,-z,stack-size=12800000", +- "-Wl,-z,lazy", +- "-Wl,-z,norelro", ++ // Pi-Bolt: RELRO with immediate binding (the GOT read-only after startup), at the cost of resolving the few hundred ++ // imports at startup instead of on first call. ++ "-Wl,-z,relro", ++ "-Wl,-z,now", + // (no --pack-dyn-relocs=relr: DT_RELR needs glibc ≥ 2.36 to load, + // and we wrap symbols for portability down to 2.17. With -no-pie + // there are <500 R_*_RELATIVE entries anyway — not worth the compat diff --git a/scripts/build/ninja.ts b/scripts/build/ninja.ts index e6a4479f14..b7c38f8aa3 100644 --- a/scripts/build/ninja.ts From c56ea74c37ba5eee5f225ed76574461d704aacab Mon Sep 17 00:00:00 2001 From: OpenSec <323596076+opensec-intelligence@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:36:12 +0530 Subject: [PATCH 2/3] docs(readme): the community AUR package closes #25 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 981e29ace..c8bf24dde 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ lower is better. See [Benchmarks](#benchmarks). curl -fsSL https://pi-bolt.opensec.in/install.sh | sh ``` -The same command installs Pi-Bolt on Linux x86-64 and ARM64, on macOS, and on Android in Termux. +The same command installs Pi-Bolt on Linux x86-64 and ARM64, on macOS, and on Android in Termux. On Arch Linux there is also +a community package in the AUR, [`pi-bolt-avx2-bin-release-git`](https://aur.archlinux.org/packages/pi-bolt-avx2-bin-release-git) +(the x86-64 AVX2 build, signature checked): `paru -S pi-bolt-avx2-bin-release-git`. On Windows, in PowerShell: From 793588ecb6d7d977496798dc6a0ce87b15fc1b6f Mon Sep 17 00:00:00 2001 From: OpenSec <323596076+opensec-intelligence@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:42:54 +0530 Subject: [PATCH 3/3] chore(release): Pi-Bolt 0.9.1: compile, rollback, built-in search, doctor, MCP server --- VERSION | 2 +- docs/releases/0.9.1.md | 111 +++++++++++++++++++++++++++++++++++++++++ npm/bin/pi-bolt | 2 +- npm/package.json | 2 +- 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 docs/releases/0.9.1.md diff --git a/VERSION b/VERSION index ac39a106c..f374f6662 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0 +0.9.1 diff --git a/docs/releases/0.9.1.md b/docs/releases/0.9.1.md new file mode 100644 index 000000000..bb75bcf65 --- /dev/null +++ b/docs/releases/0.9.1.md @@ -0,0 +1,111 @@ +Pi-Bolt 0.9.1 adds `pi-bolt compile`, `pi-bolt update --rollback` and a beta channel, built-in `grep` and `find`, `pi-bolt +doctor`, Pi as an MCP server (`pi-bolt --mcp`), more of the Agent Client Protocol for editors, Pi's package list shared with Pi, +and Linux executables linked with RELRO. Everything else is [0.9.0](0.9.0.md). + +### `pi-bolt compile` + +Compiles the extensions installed on this machine into the installed Pi-Bolt, on Linux and macOS, with no checkout or tools: a +new release asset per platform, `pi-bolt-kit-.tar.gz` (about 7 MB), holds what the release build read, and is checked +against the signed `SHA256SUMS` like the runtime. `pi-bolt update` compiles the same extensions into each new release. A +compiled-in extension loads in under a millisecond instead of 10-90 ms; one changed since loads from its files, with a warning. + +### Updates + +- `pi-bolt update --rollback` puts back the build the last update replaced, without downloading anything; again goes forward. +- `pi-bolt update --channel beta` also takes pre-releases (`--channel stable` goes back). Signatures are checked as before. + +### `grep` and `find` built in + +Pi-Bolt's `grep` and `find` tools search in-process, with the same parameters and output and nothing to install or start: no +ripgrep or fd. They follow ripgrep's and fd's rules as Pi runs them (`.gitignore`, `.ignore`, global excludes, hidden and binary +files, `--glob`). With `opensec-pi-fff` or `@ff-labs/pi-fff` loaded, its search tools are used instead. + +### `pi-bolt doctor` + +- **A report to paste into an issue.** It covers: + - the build: version, Pi version, platform, JIT on or off, the runtime it was built with, and whether its compiled code is used; + - how and where Pi-Bolt is installed, and every `pi-bolt` on `PATH`; + - whether the release's signature verifies, and the latest version; + - extensions: compiled in, run at run time, or failing to load, and which use `bun:ffi`; + - how Pi's `packages` and Pi-Bolt's `piBolt.packages` differ; + - the Herdr and editor (ACP) setup, and the problems it finds, such as another `pi-bolt` earlier on `PATH`. + + It prints no keys or tokens, and shows the home folder as `~`. `--offline` leaves out the signature and version checks. +- **Extensions that need the JIT say so.** On the default build, "bun:ffi requires the JIT" now names the extension and gives the + command that installs the JIT build, for the installer or npm: + `pi-foo needs Pi-Bolt's JIT build (bun:ffi); switch with: curl -fsSL https://pi-bolt.opensec.in/install.sh | PIBOLT_VARIANT=arm64-jit sh`. + The extension is named when it fails to load, when one of its handlers fails, and in a notice it shows itself. + +### Pi as an MCP server + +`pi-bolt --mcp` serves Pi to other agents and MCP clients (Claude Code, Codex, Cursor, ...) over stdio: + +- `prompt` runs a prompt in a Pi session (with `cwd`, `sessionId`, `model`, `thinking`) and returns Pi's answer and the session's + id; a later call continues the session, in the same process or a later one. `list_sessions` lists them. +- Progress while Pi works: each tool, the first line of each message, a running command's latest line, and a heartbeat. +- Cancelling stops the turn. Up to eight sessions stay open; prompts to one session run in order, different sessions at once. +- Extension dialogs reach the client as elicitation forms when it offers them. + +[docs/MCP.md](https://github.com/opensec-git/Pi-Bolt/blob/pi-bolt/docs/MCP.md) has the setup for each client. + +### Editors (ACP) + +- **Modes:** Default and Read-only (the model gets only `read`, `grep`, `find` and `ls`), as modes and as a config option. +- **Sessions:** delete and fork; compactions shown live and in a loaded session's history. +- **Commands** that need no terminal: `/model`, `/thinking`, `/session`, `/export`, `/reload`, besides `/compact` and `/name`. + +### Packages shared with Pi + +Pi-Bolt uses Pi's `packages` list: `pi-bolt install` and `pi-bolt remove` change it, as `pi install` does, and Pi and Pi-Bolt load +the same extensions. `--pi-bolt` changes `piBolt.packages` instead, which adds to Pi's list, replaces an entry of it, or leaves +one out (`-`) for Pi-Bolt only. A copied list from an earlier Pi-Bolt is turned into that once, so what Pi-Bolt loaded +stays the same. (Thanks to @boonlai, #30.) + +### Linux + +- **RELRO:** the Linux executables are linked with `-z relro -z now`, so the loader's tables are read-only once it is done. They + stay at a fixed address, which the prebuilt heap needs. +- **Arch Linux:** the community package `pi-bolt-avx2-bin-release-git` in the AUR, in the README. + +### npm on Windows + +The Windows builds are published to npm as `@kushalkhemka/pi-bolt-win32-x64` and `-jit`, where `npm install -g pi-bolt` on Windows +now gets them; it used to fall back to GitHub. + +### Engine + +All four runtimes are new, built by the `runtime` workflow on GitHub's runners: the Linux link flags above, and the runtime's +stamp next to it, which `pi-bolt doctor` reports. + +### Install + +```bash +curl -fsSL https://pi-bolt.opensec.in/install.sh | sh +``` + +```powershell +irm https://pi-bolt.opensec.in/install.ps1 | iex +``` + +```bat +powershell -c "irm https://pi-bolt.opensec.in/install.ps1 | iex" +``` + +or `pi-bolt update`, or `npm install -g --allow-scripts=pi-bolt pi-bolt`, or `bun add -g --trust pi-bolt`. + +| Asset | For | +|---|---| +| `pi-bolt-linux-x64.tar.xz` / `.tar.gz` | **Recommended on Linux x86-64.** CPUs with AVX2 (Intel Haswell 2013+, AMD Zen+). | +| `pi-bolt-linux-x64-baseline.tar.xz` / `.tar.gz` | Any x86-64 CPU with SSE4.2. | +| `pi-bolt-linux-x64-jit.tar.xz` / `.tar.gz` | JIT also on, for plugins loaded at run time. | +| `pi-bolt-linux-arm64.tar.xz` / `.tar.gz` | **Linux on ARM64**, any 64-bit ARM CPU; Android in Termux, with the installer. | +| `pi-bolt-linux-arm64-jit.tar.xz` / `.tar.gz` | Linux on ARM64, JIT also on. | +| `pi-bolt-darwin-arm64.tar.xz` / `.tar.gz` | **macOS.** Macs with Apple silicon, macOS 13 or later. | +| `pi-bolt-darwin-arm64-jit.tar.xz` / `.tar.gz` | macOS, JIT also on. | +| `pi-bolt-win32-x64.zip` | **Windows.** Windows 10 (1809) or 11 on x64; compiled code on AVX2 CPUs, bytecode on others. | +| `pi-bolt-win32-x64-jit.zip` | Windows, JIT also on. | +| `pi-bolt-runtime-linux-x64.tar.gz`, `pi-bolt-runtime-linux-arm64.tar.gz`, `pi-bolt-runtime-darwin-arm64.tar.gz`, `pi-bolt-runtime-win32-x64.zip` | The Pi-Bolt Bun runtime, for compiling plugins in. | +| `pi-bolt-kit-linux-x64.tar.gz`, `pi-bolt-kit-linux-arm64.tar.gz`, `pi-bolt-kit-darwin-arm64.tar.gz` | What `pi-bolt compile` builds with, besides the runtime. | + +Linux x86-64 or ARM64 with glibc 2.17 or later; Android 7 or later on 64-bit ARM, in Termux; macOS 13 or later on Apple silicon; +Windows 10 1809 or later, or Windows 11, on x64. diff --git a/npm/bin/pi-bolt b/npm/bin/pi-bolt index a2d432b39..d4ec0df35 100755 --- a/npm/bin/pi-bolt +++ b/npm/bin/pi-bolt @@ -4,7 +4,7 @@ # replaces this script with the native executable (exec), so the launcher costs no startup time. set -eu -VERSION=0.9.0 +VERSION=0.9.1 HOME_DIR="${PIBOLT_HOME:-$HOME/.pi-bolt}" if [ "$(uname -s)" = Darwin ]; then PLATFORM=darwin; else PLATFORM=linux; fi diff --git a/npm/package.json b/npm/package.json index d6ad2c248..219150eda 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "pi-bolt", - "version": "0.9.0", + "version": "0.9.1", "description": "Pi, the coding agent, compiled ahead of time to native code: one executable for Linux x86-64 and ARM64 (Android too, in Termux), macOS on Apple silicon and Windows x64, starts 2-3x sooner than Pi on Bun and uses about a third of its CPU over a session", "keywords": [ "pi",