@nonbot/cli 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @nonbot/cli changelog
2
2
 
3
+ ## 0.10.1 (2026-09-09) — SECURITY
4
+
5
+ - **Hardens the tmux theme validator; upgrade if you run `nonbot daemon`.**
6
+ `isSafeTmuxConf` guards a server-supplied cosmetic theme before the daemon
7
+ writes it to disk and hands it to `tmux source-file`, which EXECUTES it. It
8
+ allowlisted the tmux *directive* (`set` / `setw` / `set-option` /
9
+ `set-window-option`) and denylisted three option names. tmux stores HOOKS as
10
+ options, and `man tmux` is explicit that `set-hook` and `set-option` are
11
+ equivalent for them — so a theme line naming a hook used an allowlisted
12
+ directive, named an option the denylist did not cover, and contained none of
13
+ the patterns the validator screens for (`#(`, `$(`, a backtick, a backslash, a
14
+ `;`). It passed every check, and tmux ran the hook's command on the next
15
+ matching pane event.
16
+
17
+ A denylist of option names cannot be complete here: it would have to enumerate
18
+ every hook tmux has now and every one it adds later. The option NAME is now
19
+ ALLOWLISTED instead — presentational suffixes (`-style`, `-format`, `-bg`,
20
+ `-fg`, `-attr`, `-colour`, `-color`, `-length`) plus a short explicit set for
21
+ the cosmetic options that have no such suffix — which refuses every hook by
22
+ construction. `lock-command`, `copy-command` and `editor` joined the forbidden
23
+ set alongside `default-command`, `default-shell` and `command-alias`.
24
+
25
+ Themes produced by the normal generator are unaffected. +20 tests
26
+ (`tests/tmux-theme-allowlist.test.ts`) covering real generated output and 13
27
+ rejected payloads.
28
+
3
29
  ## 0.10.0 (2026-09-04)
4
30
 
5
31
  - At-exit transcript for fast-exit runs (memory C32, 2026-09-04): the daemon keeps a rolling
@@ -147,7 +147,22 @@ const TMUX_SAFE_DIRECTIVES = new Set([
147
147
  ]);
148
148
  const TMUX_FORBIDDEN_OPTIONS = new Set([
149
149
  'default-command', 'default-shell', 'command-alias',
150
+ 'lock-command', 'copy-command', 'editor',
150
151
  ]);
152
+ const TMUX_SAFE_OPTION_SUFFIXES = [
153
+ '-style', '-format', '-bg', '-fg', '-attr', '-colour', '-color', '-length',
154
+ ];
155
+ const TMUX_SAFE_OPTION_NAMES = new Set([
156
+ 'status', 'status-position', 'status-justify', 'status-interval',
157
+ 'window-status-separator', 'pane-border-status', 'pane-border-indicators',
158
+ ]);
159
+ function isSafeTmuxOptionName(name) {
160
+ if (TMUX_FORBIDDEN_OPTIONS.has(name))
161
+ return false;
162
+ if (TMUX_SAFE_OPTION_NAMES.has(name))
163
+ return true;
164
+ return TMUX_SAFE_OPTION_SUFFIXES.some((suffix) => name.endsWith(suffix));
165
+ }
151
166
  export function isSafeTmuxConf(conf) {
152
167
  for (const rawLine of conf.split(/\r?\n/)) {
153
168
  const line = rawLine.trim();
@@ -170,6 +185,9 @@ export function isSafeTmuxConf(conf) {
170
185
  if (TMUX_FORBIDDEN_OPTIONS.has(tok))
171
186
  return false;
172
187
  }
188
+ const optionName = tokens.slice(1).find((t) => !t.startsWith('-'));
189
+ if (!optionName || !isSafeTmuxOptionName(optionName))
190
+ return false;
173
191
  }
174
192
  }
175
193
  return true;
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.10.0';
1
+ export const VERSION = '0.10.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonbot/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "description": "The local host for non.bot ▶ Run — opens a terminal on your machine and starts the work in your linked repo.",
6
6
  "license": "UNLICENSED",