<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Sanitisation on Opensource internals</title><link>https://articles.9j.nz/tags/sanitisation/</link><description>Posts filed under Sanitisation on Opensource internals.</description><generator>Hugo</generator><language>en-US</language><managingEditor>Jay Hubbert</managingEditor><lastBuildDate>Fri, 02 Oct 2026 08:30:00 +0000</lastBuildDate><atom:link href="https://articles.9j.nz/tags/sanitisation/index.xml" rel="self" type="application/rss+xml"/><item><title>tmux Sanitises Window Names Against Injection</title><link>https://articles.9j.nz/posts/how-tmux-sanitises-window-names-against-injection/</link><pubDate>Fri, 02 Oct 2026 08:30:00 +0000</pubDate><dc:creator>Jay Hubbert</dc:creator><guid isPermaLink="true">https://articles.9j.nz/posts/how-tmux-sanitises-window-names-against-injection/</guid><category>tmux</category><category>security</category><category>internals</category><category>sanitisation</category><description>Window names, session names, pane titles and paste buffer names all flow through clean_name(), which escapes them vis(3)-style. A small function with an interesting history of security fixes.</description><content:encoded>&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;clean_name()&lt;/code&gt; is tmux&amp;rsquo;s one defence against name-based injection, and it works by &lt;em&gt;encoding&lt;/em&gt; rather than &lt;em&gt;filtering&lt;/em&gt;: it stores names in a form where the dangerous characters are unrepresentable, rather than keeping a blocklist of bad names.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Window names in tmux are not just strings. They end up in status lines, in&#10;control-mode notifications, in &lt;code&gt;#{window_name}&lt;/code&gt; format expansions, and in&#10;hook commands that are expanded and then &lt;em&gt;parsed as commands&lt;/em&gt;. A window name&#10;containing &lt;code&gt;#{...}&lt;/code&gt; or a quote is a potential injection point.&lt;/p&gt;&#10;&lt;p&gt;The defence is a single function called &lt;code&gt;clean_name()&lt;/code&gt;. Its history is a&#10;good illustration of how injection defence actually evolves: patch, get&#10;bypassed, patch again, get bypassed again.&lt;/p&gt;&#10;&lt;h2 id="the-function"&gt;The function&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;tmux.c&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;c&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;clean_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;untrusted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="kt"&gt;char&lt;/span&gt;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;utf8_isvalid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="n"&gt;copy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;xstrdup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;untrusted&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;#&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;(&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nf"&gt;utf8_stravis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VIS_OCTAL&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;VIS_CSTYLE&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;VIS_TAB&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;VIS_NL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Three things happen:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;UTF-8 validation.&lt;/strong&gt; An invalid sequence is rejected outright, and the&#10;caller gets &lt;code&gt;NULL&lt;/code&gt;. This isn&amp;rsquo;t cosmetic: &lt;code&gt;utf8_stravis()&lt;/code&gt; walks the&#10;string with the UTF-8 decoder, and a malformed string would desynchronise&#10;the walk.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;#(&lt;/code&gt; → &lt;code&gt;_(&lt;/code&gt; for untrusted names.&lt;/strong&gt; The &lt;code&gt;untrusted&lt;/code&gt; flag marks names&#10;that came from &lt;em&gt;inside&lt;/em&gt; a pane — a program that set the terminal title via&#10;OSC 0 or OSC 2. A title is the one place where a hostile process can put&#10;arbitrary bytes into a name, so &lt;code&gt;#(&lt;/code&gt; is defanged there.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&lt;code&gt;vis(3)&lt;/code&gt;-style escaping.&lt;/strong&gt; &lt;code&gt;utf8_stravis()&lt;/code&gt; escapes everything that&#10;isn&amp;rsquo;t plainly printable.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="why-vis3-and-not-a-hand-rolled-check"&gt;Why &lt;code&gt;vis(3)&lt;/code&gt; and not a hand-rolled check&lt;/h2&gt;&#10;&lt;p&gt;tmux uses the BSD &lt;code&gt;vis(3)&lt;/code&gt; family — bundled in &lt;code&gt;compat/vis.c&lt;/code&gt; and&#10;&lt;code&gt;compat/unvis.c&lt;/code&gt; — because &amp;ldquo;escape the dangerous characters&amp;rdquo; is a problem&#10;with a long tail, and &lt;code&gt;vis&lt;/code&gt; has already enumerated the tail.&lt;/p&gt;&#10;&lt;p&gt;The important part is what happens to the characters that are &lt;em&gt;not&lt;/em&gt;&#10;printable. &lt;code&gt;vis()&lt;/code&gt;&amp;rsquo;s definition of visibility (&lt;code&gt;compat/vis.c&lt;/code&gt;) is:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;c&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define&#9;isvisible(c,flag)&#9;&#9;&#9;&#9;&#9;&#9;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&#9;(((c) == &amp;#39;\\&amp;#39; || (flag &amp;amp; VIS_ALL) == 0) &amp;amp;&amp;amp;&#9;&#9;&#9;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&#9;(((u_int)(c) &amp;lt;= UCHAR_MAX &amp;amp;&amp;amp; isascii((u_char)(c)) &amp;amp;&amp;amp;&#9;&#9;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&#9;(((c) != &amp;#39;*&amp;#39; &amp;amp;&amp;amp; (c) != &amp;#39;?&amp;#39; &amp;amp;&amp;amp; (c) != &amp;#39;[&amp;#39; &amp;amp;&amp;amp; (c) != &amp;#39;#&amp;#39;) ||&#9;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&#9;&#9;(flag &amp;amp; VIS_GLOB) == 0) &amp;amp;&amp;amp; isgraph((u_char)(c))) ||&#9;\&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt; and &lt;code&gt;[&lt;/code&gt; exclusions only apply when &lt;code&gt;VIS_GLOB&lt;/code&gt; is set.&#10;tmux doesn&amp;rsquo;t set it, so those characters are ordinary visible characters&#10;and are stored as-is. &lt;code&gt;#&lt;/code&gt; in a window name survives as &lt;code&gt;#&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;What does get escaped is everything non-printable, because &lt;code&gt;VIS_CSTYLE&lt;/code&gt; and&#10;&lt;code&gt;VIS_OCTAL&lt;/code&gt; together make &lt;code&gt;vis()&lt;/code&gt; fall through to its encoding branches:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;c&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;VIS_CSTYLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\\&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\r&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\\&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;so a newline in a name becomes the two characters &lt;code&gt;\&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt;, a tab&#10;becomes &lt;code&gt;\t&lt;/code&gt;, a carriage return becomes &lt;code&gt;\r&lt;/code&gt;, and any other non-printable&#10;byte becomes an octal escape such as &lt;code&gt;\001&lt;/code&gt;. That matters for the&#10;control-mode protocol, where a raw newline in a &lt;code&gt;%%window-renamed&lt;/code&gt;&#10;notification would forge a protocol line.&lt;/p&gt;&#10;&lt;p&gt;The consequence for format injection is subtler than it looks. A window&#10;name &lt;em&gt;can&lt;/em&gt; contain &lt;code&gt;#{&lt;/code&gt;, and it can contain &lt;code&gt;#(&lt;/code&gt;. Neither is re-expanded&#10;when the name is substituted into a status line, because a format&#10;expansion&amp;rsquo;s result isn&amp;rsquo;t itself expanded — only the &lt;code&gt;E&lt;/code&gt; and &lt;code&gt;T&lt;/code&gt; modifiers&#10;re-expand. So the &lt;code&gt;#{...}&lt;/code&gt; in a name is inert text. The &lt;code&gt;#(&lt;/code&gt; in a name is&#10;&lt;em&gt;not&lt;/em&gt; inert, because a format &lt;em&gt;can&lt;/em&gt; contain a shell job, and that&amp;rsquo;s exactly&#10;what the explicit rewrite in &lt;code&gt;clean_name()&lt;/code&gt; is for.&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;c&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;VIS_CSTYLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\\&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\r&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;\\&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;so a newline in a name becomes the two characters &lt;code&gt;\&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt;. That is&#10;important for the control-mode protocol, where a raw newline in a&#10;&lt;code&gt;%%window-renamed&lt;/code&gt; notification would forge a protocol line.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;VIS_NL&lt;/code&gt; is in the flag list too, and it&amp;rsquo;s worth being precise about what&#10;it does: in BSD &lt;code&gt;vis&lt;/code&gt;, &lt;code&gt;VIS_NL&lt;/code&gt; means &amp;ldquo;also encode newline characters&amp;rdquo; — it&#10;is an &lt;em&gt;instruction to encode&lt;/em&gt;, not an exemption. Combined with &lt;code&gt;VIS_CSTYLE&lt;/code&gt;&#10;the newline comes out as &lt;code&gt;\n&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="where-it-is-applied"&gt;Where it is applied&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;clean_name()&lt;/code&gt; is called from a small, deliberate set of places:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Call site&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Context&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;window_set_name()&lt;/code&gt; (&lt;code&gt;window.c&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;window renames, &lt;code&gt;untrusted&lt;/code&gt; flag passed through&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;screen.c:242&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pane title from OSC&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;screen.c:256&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pane path from OSC 7&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;input.c:3284&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OSC title parsing, &lt;code&gt;untrusted = 1&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cmd-rename-session.c:63&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;session rename&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cmd-new-window.c:92&lt;/code&gt;, &lt;code&gt;cmd-new-session.c&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-n&lt;/code&gt; names&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;names.c:169&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;the shared &amp;ldquo;find next unused name&amp;rdquo; helper&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;paste.c:232&lt;/code&gt;, &lt;code&gt;paste.c:300&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;paste buffer names&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cmd-break-pane.c:173&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;broken-pane names&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The &lt;code&gt;untrusted&lt;/code&gt; distinction shows up only in the OSC title path. A name set&#10;by a &lt;em&gt;command&lt;/em&gt; (&lt;code&gt;rename-window foo&lt;/code&gt;) keeps its &lt;code&gt;#&lt;/code&gt; characters, because a&#10;user typing &lt;code&gt;#&lt;/code&gt; into a window name is not an attack. A name set by a&#10;&lt;em&gt;program inside a pane&lt;/em&gt; has &lt;code&gt;#(&lt;/code&gt; rewritten to &lt;code&gt;_(&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Note that the &lt;em&gt;stored&lt;/em&gt; name is the sanitised one — there&amp;rsquo;s no separate&#10;&amp;ldquo;display&amp;rdquo; form that gets un-escaped. A name that contained a newline is&#10;stored with a literal backslash-n in it and displayed that way. That keeps&#10;the invariant simple: &lt;strong&gt;whatever is in &lt;code&gt;w-&amp;gt;name&lt;/code&gt; is safe to interpolate into&#10;a status line, a control-mode notification or a format.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="the-history"&gt;The history&lt;/h2&gt;&#10;&lt;p&gt;The git log tells the story of how this went wrong, repeatedly.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;code&gt;d339ab51&lt;/code&gt;&lt;/strong&gt; — &amp;ldquo;Sanitize pane titles and window and session names more&#10;consistently and strictly, prevents C0 characters and other nonvisible&#10;characters causing problems. Reported (with a different fix) by Chris Monardo&#10;in GitHub issue 4999.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Before this, some names were validated and some were not. C0 characters in&#10;a window name could produce all kinds of weirdness in the status line and&#10;in control-mode output.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;code&gt;64e83caf&lt;/code&gt; / &lt;code&gt;eb653314&lt;/code&gt;&lt;/strong&gt; — &amp;ldquo;Be more strict about what names and titles we&#10;allow and reject them immediately when possible, but allow # again for those&#10;directly set by commands (but not escape sequences). From Barrett Ruth in&#10;GitHub issue 5175.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;This is the interesting one. The strict version &lt;em&gt;rejected&lt;/em&gt; names containing&#10;&lt;code&gt;#&lt;/code&gt;. That was too strict: users legitimately want &lt;code&gt;#&lt;/code&gt; in window names&#10;(&lt;code&gt;vim #1&lt;/code&gt;, &lt;code&gt;ssh box#2&lt;/code&gt;), and the fix was to allow &lt;code&gt;#&lt;/code&gt; where it&amp;rsquo;s harmless&#10;while keeping it defanged where it isn&amp;rsquo;t — the &lt;code&gt;untrusted&lt;/code&gt; flag.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;code&gt;746dd91e&lt;/code&gt;&lt;/strong&gt; — &amp;ldquo;Sanitize paste buffer names in paste_set and paste_rename,&#10;GitHub issue 5032 from Barrett Ruth.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Paste buffer names were a separate hole. They&amp;rsquo;re format-expanded in places,&#10;and they weren&amp;rsquo;t being cleaned. Fixed by routing them through &lt;code&gt;clean_name()&lt;/code&gt;&#10;too.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;code&gt;518fcf7e&lt;/code&gt;&lt;/strong&gt; — &amp;ldquo;Do not sanitize title when popping it from stack, also&#10;add a limit to number of pushed titles.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Terminals push and pop titles with OSC 9/10 and OSC 133. Popping a title&#10;restores a &lt;em&gt;previously sanitised&lt;/em&gt; title, so sanitising again would&#10;double-escape it. The fix was to skip sanitisation on pop, and to cap the&#10;title stack so a program can&amp;rsquo;t grow it without bound.&lt;/p&gt;&#10;&lt;p&gt;That last one is a nice illustration of the general tension: sanitisation&#10;isn&amp;rsquo;t idempotent, and a system that both stores and restores sanitised&#10;values has to know which side of the boundary each value came from.&lt;/p&gt;&#10;&lt;h2 id="what-is-not-defended"&gt;What is &lt;em&gt;not&lt;/em&gt; defended&lt;/h2&gt;&#10;&lt;p&gt;A few things to be clear about:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Session and window names aren&amp;rsquo;t restricted in length or character set&lt;/strong&gt;&#10;beyond &amp;ldquo;valid UTF-8&amp;rdquo;. They can contain spaces, quotes, semicolons. The&#10;defence is escaping, not rejection.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The command parser&amp;rsquo;s &lt;code&gt;${VAR}&lt;/code&gt; splicing is a separate issue.&lt;/strong&gt; A name is&#10;escaped; an environment variable isn&amp;rsquo;t. See&#10;&lt;a href="https://articles.9j.nz/posts/how-tmux-format-expansion-runs-shell-commands/"&gt;How tmux Format Expansion Runs Shell Commands&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Control-mode output from commands isn&amp;rsquo;t escaped.&lt;/strong&gt; Names are; command&#10;output isn&amp;rsquo;t. See&#10;&lt;a href="https://articles.9j.nz/posts/how-tmux-control-mode-is-an-unauthenticated-command-channel/"&gt;tmux Control Mode Is an Unauthenticated Command Channel&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;A name isn&amp;rsquo;t escaped for the &lt;em&gt;shell&lt;/em&gt;.&lt;/strong&gt; &lt;code&gt;run-shell &amp;quot;echo #{window_name}&amp;quot;&lt;/code&gt;&#10;will expand the name and then hand the result to &lt;code&gt;/bin/sh -c&lt;/code&gt;. The name&#10;is escaped for tmux&amp;rsquo;s format and control-mode syntaxes, not for &lt;code&gt;sh&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;That last one is the most common way people get burned.&lt;/strong&gt; If a window name&#10;comes from an untrusted source and you interpolate it into a shell command,&#10;you need shell quoting. &lt;code&gt;cmd_template_replace()&lt;/code&gt; provides &lt;code&gt;%%&lt;/code&gt; and &lt;code&gt;%1&lt;/code&gt; for&#10;exactly this, and they have different escaping rules:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;c&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Replace the first %% or any %idx in template by s. %% is intended for use in&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * single quotes, so &amp;#39; is escaped. %%% and %idx% are for double quotes, so a&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * list of special characters is escaped. %idx is left unescaped.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;%1&lt;/code&gt; is deliberately &lt;em&gt;not&lt;/em&gt; escaped, on the assumption you&amp;rsquo;ll quote it&#10;yourself. That&amp;rsquo;s the trap: &lt;code&gt;%1&lt;/code&gt; inside double quotes is fine, &lt;code&gt;%1&lt;/code&gt; inside&#10;single quotes isn&amp;rsquo;t, and &lt;code&gt;%%&lt;/code&gt; inside single quotes escapes the quote&#10;character but isn&amp;rsquo;t safe in double quotes.&lt;/p&gt;&#10;&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;clean_name()&lt;/code&gt; is a good example of defence that works by &lt;em&gt;encoding&lt;/em&gt; rather&#10;than &lt;em&gt;filtering&lt;/em&gt;. tmux doesn&amp;rsquo;t maintain a blocklist of dangerous window&#10;names. It stores names in a form where the dangerous characters are&#10;unrepresentable.&lt;/p&gt;&#10;&lt;p&gt;A blocklist would have to know about &lt;code&gt;#{&lt;/code&gt;, &lt;code&gt;#(&lt;/code&gt;, newlines, quotes, &lt;code&gt;;&lt;/code&gt;,&#10;backticks, &lt;code&gt;$(&lt;/code&gt;, and every future syntax tmux adds. An encoder only has to&#10;know about the characters that aren&amp;rsquo;t printable — plus the one piece of&#10;syntax that genuinely has to be spelled out, &lt;code&gt;#(&lt;/code&gt;, which is why that one is&#10;handled by hand in &lt;code&gt;clean_name()&lt;/code&gt; rather than by &lt;code&gt;vis&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The cost is that the stored and displayed forms are the same, which is&#10;simple, but that sanitisation isn&amp;rsquo;t idempotent. Anything that stores a&#10;sanitised value and later restores it — the terminal title stack — has to&#10;know which side of the boundary each value came from. That bug is exactly&#10;what commit &lt;code&gt;518fcf7e&lt;/code&gt; fixed.&lt;/p&gt;&#10;</content:encoded></item></channel></rss>