v1.6.1
Follow-up to the 1.6.0 security release. The neutralisation that shipped
there rests on char::is_control, which covers C0, DEL and C1. It does not
cover the twelve characters carrying the Bidi_Control property: those are
Cf, not Cc, and they reorder how a terminal renders the text around them
without ever being a control byte. The pre-trust bootstrap summary inherited
the gap, and that is the one output whose whole job is to let someone
authorise a shell command out of a repo they have not vetted. The TUI
worktrees table was never on the path the CLI sinks protect at all, and what
had shielded it so far was incidental to ratatui’s rendering. Both are closed
here, along with an alias expansion that becomes argv before clap can be
reached.
The rest of the fixes are test hardening around the same class of mistake: a rule stated in a doc comment rather than checked, and two races in the harness that were never reachable from the product.
Two additions travel with it. The published documentation now redeploys itself
when main moves, and herdr-plugin-gwm gets an integration page in English
and French.
-
The published documentation now follows
main. A merge intomainthat touchesdocs/,changelogs/orCargo.tomlposts arepository_dispatchtokbrdn1/kbrdn-docs, which re-runs the conversion, commits whatever drifted and redeploys the site. This repository owns the Markdown sources and the trigger, nothing else: doing the conversion here would pull a Bun monorepo into a CI that cannot verify its build.mainrather than the tag, becausemainis only reached through adevtomainpull request, so what lands there is what was delivered. A tag would be worse than a shortcut: thev*.*.*glob also matchesv0.8.0-rc.4, the traprelease.ymlalready guards by hand, so every release candidate’s documentation would go live as if it were stable.Cargo.tomlis watched because the site reads the[package]version for the release badge in its header, and a bump can travel without touchingchangelogs/. (#423) -
An integration page for
herdr-plugin-gwm, in English and French. herdr is a terminal multiplexer with workspaces, panes and a plugin API; the plugin wires gwm into it, so create, switch, remove, review a pull request,execandcleanall reach across worktrees from inside the multiplexer, with gwm’s own TUI available in a pane.The page leads with the rule the plugin is built around: it never creates a worktree on the herdr side. Creation and removal always go through gwm, and herdr only adopts what gwm produced. That is what keeps a single source of truth, and the plugin enforces it rather than documenting it, with one helper as the only path to herdr and a grep-assert failing the build if any script reaches around it. (#511)
-
The rule that a test which can observe a rewritten environment variable must hold its binary’s lock is now checked by construction rather than stated in a doc comment. That comment is what #503 was about: it named a boundary narrower than the real one, and five tests were written against the sentence. Its replacement then stated a second wrong boundary, that a layered load reads
$HOME. It does not:Config::load_layeredandresolved_rowstake the global config path as a parameter, andConfig::load_for_repo, which does resolve it, is called by no test in that binary. A new test walks the four test binaries that rewrite a variable and fails on any test calling an ambient reader of it without the lock, ignoring mentions in comments and identifiers that merely end with the name. Both mistakes came from a search that could not tell those apart. (#507) -
A branch name no longer reaches the TUI table with its bidi controls intact. The sinks the CLI goes through are not on the TUI’s path, and what protected the TUI so far was incidental: measured on ratatui 0.30, every render path drops the zero-width control bytes, but
ListandTablekeep theBidi_Controlcharacters. The worktrees table renders throughTable, and git’s ref rules refuse the ASCII controls and~^:?*[but not the Unicode format characters, so a fetched ref could carry one and its row could read in an order the ref is not stored in.The neutralisation goes in the width-clipping funnel every constrained cell already passes through, rather than at each cell, so a column added later inherits it. It runs before the width count, so what is measured is what is drawn: a
Bidi_Controlcharacter can measure zero columns where its?measures one. The path column, which is not width-constrained, says so itself.tui::wt_tree::sanitize_namenow delegates to the shared predicate instead of keeping a copy that had drifted: a filename out ofgit status -zreorders a sidebar row exactly as a ref name reorders a table row. (#506) -
A config value no longer reaches the terminal with its Unicode bidi controls intact. The neutralisation added in 1.6.0 replaces control characters, and
char::is_controlcovers C0, DEL and C1: it does not cover the twelve characters carrying theBidi_Controlproperty, which areCf, notCc. They reorder how a terminal renders the text around them without ever being a control byte, so a value carrying one can display something other than what it is.The site that matters is the pre-trust bootstrap summary, whose whole job is to let someone decide whether to authorise a shell command out of a repo they have not vetted. A summary that can be made to misrepresent the command it asks about is worse than no summary.
gwm config get,types,trust listand the rendered diagnostics carry the same exposure at lower stakes, and all of them inherit the fix: it lands in the two sinks every echo site already goes through, the way they inherited the control-character rule. An[aliases]expansion is refused rather than neutralised, for the reason it already was in 1.6.0: it becomes argv before clap parses it, and clap prints its own error without passing through gwm’s output path, sonope<ALM>abccame back out of it with the character intact.The set is
Bidi_Controlexactly rather than the overrides and isolates alone, because the three implicit marks reorder too:U+061Cis bidi classALandU+200FisR, so either one is a strong right-to-left character inside left-to-right text and the weak and neutral rules of UAX #9 then reorder the digits and punctuation around it. An argument or a URL can be made to render in an order the bytes do not have. This is a gap in the 1.6.0 mitigation, not a regression: 1.5.0 and earlier neutralised nothing at all. (#502) -
A race in the test harness, not reachable from the product.
exec_in_dir_runs_a_relative_script_from_the_worktreewrites an executable and immediately runs it, andexecverefuses a file that is open for writing by any process: a child forked by another test thread carries a copy of that write handle until it execs, so the spawn intermittently returnedETXTBSYon Linux. It is retried on that errno alone, with the reason written at the retry, and every other spawn error still fails on the first attempt. (#500) -
The same shape in
config_tests, where the guard around$HOMEdocumented a boundary narrower than the real one, and five tests skipped it on the strength of that doc.expand_placeholdersresolves the home directory before it looks at a single token, so every call is a concurrent reader whatever the template says, while one test rewrites the variable withset_var. The guard is now taken by every test in that binary that can observe$HOME, and its doc states the hazard rather than a proxy for it. (#503)