Repository navigation
v0.14.1
Security fixes from the 2026-10-21 review, and the most-misquoted lines given their
originals. No dependency had a known vulnerability (cargo audit, pip-audit on all
three requirements files). The review found three issues and the follow-up a fourth, on
Windows; each was reproduced before it was fixed. Three are in the desktop app and the
FoJin retrieval fence, below; the fourth is in CI.
The retrieval fence's random number was probed on real models before release, at about
$0.30. The injected text sat behind a forged end marker made of -, the shape the line
filter keeps. Ten runs per arm:
- No model obeyed it in either arm (claude-sonnet-5-5, claude-haiku-5-5; old fence and
prompt vs new). - With the new prompt, Haiku told the user the retrieval data looked tampered with in 7 of
10 runs, against 0 of 10 with the old one, and one run cited the number mismatch.
Sonnet flagged it every time in both arms. - The probe is too small to show the number blocks an attack the old fence let through. It
shows the rule is read and does not hurt answers.
Security — CI: the publish token and the hook wrapper (#341)
npm-publish.ymlgrantedid-token: writeto the whole workflow, so thegatejob,
which installs floor-pinned requirements and runs the full suite, could also mint the
OIDC token npm's trusted publishing exchanges. Only thepublishjob holds it now, and
scripts/tests/test_workflow_permissions.pyfails any workflow that grants it at the top.hooks/run-hook.cmdran withenabledelayedexpansion, which strips every!from an
expanded path. A plugin under a Windows user named likebang!xfailed its SessionStart
hook with exit 127 (measured on Windows 11). The wrapper no longer needs delayed
expansion, and a test runs it from a path holding! & ^ % ( )and spaces.
Performance — update --all is ten times faster (#346)
resolveSkill() rebuilt the whole catalog on every lookup, so all 21 SKILL.md files and
the personas' meta.json were re-read and re-parsed each time, and cmdInstall looks
skills up inside a loop over names × personas. A CPU profile put 70% of update --all's
2.8s in those reads. The catalog is now built once per process: 0.27s. The two
named-pipe tests, which give update --all 3 seconds, had been failing on a loaded
machine for this reason.
Content (#340)
- master-tsongkhapa: the two 《广论》 lines the fd3d832 run misquoted most (卷1 四殊胜,
卷17 「唯无我慧乃能永断三有根本」) are verbatim 原典 blocks with CBETA B10n0067 page
and line. - master-yinguang: three 《文钞》 letters checked word for word against the pinned 殆知阁
corpus. - compare-masters, master-debate, master-curriculum:
cross_critiquepositions are this
repository's synthesis, not a master's remark about another.
Dependencies
- anthropic 1.8.0 → 1.17.0 and openai 3.19.2 → 3.35.1 for the paid eval (#301). Both
verification scripts pass, and the keyless smoke output is unchanged apart from the
version line. - actions/setup-node 7.1.0 (#306), download-artifact 8.0.2 (#307), upload-artifact 7.0.2
(#308). The new SHAs were checked against the upstream tags. - eframe 0.37 (#310) is not in this release. CI is green, but nobody has looked at the
window yet.
Security — the Windows desktop exe no longer runs a python.exe dropped beside it (2026-10-24)
The desktop app spawns python, node and npm.cmd by bare name. On Windows, Rust's
Command searches the directory of the running executable before PATH, and the released
exe is a single file people run from Downloads. Reproduced with the v0.14.0 release on
Windows: with a python.exe next to the exe (a copy of the desktop exe itself),
--baseline reported invalid arguments: ["…\scripts\test-fidelity.py", "--master", …]
as the dry-run's stderr, although a real Python was on PATH. Without the decoy the same
run printed ok demo. A decoy in the working directory or the repo root did not run.
- On Windows a bare interpreter name is now looked up on PATH by the app and spawned by
absolute path. Only absolute PATH entries are searched, and only.exeis appended,
as std does: PATHEXT would let apython.batoutrankpython.exe. - Not found on PATH is an error naming the program and the override to set
(NODE,MASTER_SKILL_PYTHON,MASTER_SKILL_NPM). Falling back to the bare name
would put the executable's directory back in the search. - Unix is unchanged.
execvpnever searched the executable's directory.
Security — the desktop app no longer runs a planted script under a safe-looking root (2026-10-21)
The desktop app finds its repo root by walking up from the working directory, then runs
python3 <root>/scripts/test-fidelity.py, node <root>/bin/cli.mjs and npm test from it.
The trust check only looked at the root's own mode bits. A reviewer reproduced the gap: root
0755, scripts/ 0777, a planted scripts/test-fidelity.py, and --baseline ran it, announced
as "discovered from the working directory".
- On Unix the root,
scripts/,bin/,scripts/test-fidelity.py,bin/cli.mjsand
package.jsonmust now all be owned by the effective uid and not group- or
world-writable. A symlinkedscripts/orbin/is judged by its target. - A refused root is named on the startup line together with the path that failed and why,
for example…/scripts is group- or world-writable (mode 0777). - Windows is unchanged: no check, as before. ACLs are not inspected, and the gap is
documented indesktop/src/cli.rs.MASTER_SKILL_REPO_ROOTpins the root on any platform.
Security — a near-copy of the FoJin data fence's end marker broke out of it (2026-10-21)
rag_query.py and master_builder.py wrap FoJin text in boundary markers and strip
forged markers from the body, but only exact copies. ===== FOJIN 检索数据结束 =====
(one extra space) went through, so the agent saw two end markers and whatever followed
the fake one read as text outside the data. FoJin's data is partly enriched from
editable sources (Wikidata), so the body is attacker-reachable.
- Both fences now drop any body line that reads as a marker once it is folded the way a
reader sees it: NFKD, invisibles and combining marks removed, case ignored. That covers
irregular spaces, tabs, U+3000, NBSP, a different number of=, full-width=/<>
/ letters, zero-width characters and 檢索數據. A line that only mentions FoJin's retrieval
data in prose has no=run or angle brackets and is kept. The shared helper is
fojin_bridge.drop_forged_boundary_lines. rag_query.py's real start and end lines carry a per-call random number, and
prompts/rag_instructions.mdsays only the end line with the start line's number closes
the data. A body line cannot know the number. The build-time<<<FOJIN_DATA>>>fence in
master_builder.pykeeps its literal marker, which the analysis prompts document.tests/test_boundary_forgery.py: 21 forged variants foremit, 10 for_fence, and
prose that must survive. 27 of them failed before the change.