<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Emanuele Micheletti</title>
      <link>https://micheletti.io</link>
      <description>Emanuele Micheletti</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://micheletti.io/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Wed, 22 Jul 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Proxelar 0.5.0: Sessions, Rules, and More Ways to Capture Traffic</title>
          <pubDate>Wed, 22 Jul 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/proxelar-050/</link>
          <guid>https://micheletti.io/proxelar-050/</guid>
          <description xml:base="https://micheletti.io/proxelar-050/">&lt;p&gt;&lt;em&gt;Disclosure: this post was written with the assistance of an LLM. The release, the code, and the technical decisions behind it are my own work, and I have reviewed and edited everything below.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.5.0&quot;&gt;Proxelar 0.5.0&lt;&#x2F;a&gt; is out.&lt;&#x2F;p&gt;
&lt;p&gt;This release got a bit out of hand. I started with one annoyance, captures disappearing when the proxy stops, and ended up adding sessions, HAR import&#x2F;export, a real filter language, content-aware body views, declarative rules, a headless API, addon packages, and four new capture modes.&lt;&#x2F;p&gt;
&lt;p&gt;A flat feature list wouldn&#x27;t help much, so this post follows a workflow instead: get traffic into Proxelar, find the exchange you care about, understand the body, change or replay it, and keep the result for later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;captures-finally-survive-a-restart&quot;&gt;Captures Finally Survive a Restart&lt;&#x2F;h2&gt;
&lt;p&gt;The most obvious missing piece was persistence. Until now, closing Proxelar meant losing the session.&lt;&#x2F;p&gt;
&lt;p&gt;You can now save the complete capture when Proxelar shuts down cleanly:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --save-session checkout-debug.proxelar.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Press &lt;strong&gt;Ctrl+C&lt;&#x2F;strong&gt; when you&#x27;re done. The file contains completed HTTP flows, WebSocket connections and frames, observed raw TCP chunks, DNS exchanges, UDP datagrams, stable flow IDs, and body-truncation metadata.&lt;&#x2F;p&gt;
&lt;p&gt;Load it on the next run:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --load-session checkout-debug.proxelar.json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --save-session checkout-debug-continued.proxelar.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The old flows appear immediately, and new traffic is appended to the same in-memory session. This is what I wanted when a bug takes three attempts to reproduce and you don&#x27;t want three unrelated screenshots pretending to be one timeline.&lt;&#x2F;p&gt;
&lt;p&gt;The native format is versioned JSON, and I kept it readable on purpose. It&#x27;s large, but you can inspect it, diff it, or write a small conversion tool without depending on an opaque database.&lt;&#x2F;p&gt;
&lt;p&gt;One thing to watch: &lt;strong&gt;native sessions are not redacted&lt;&#x2F;strong&gt;. They preserve the capture exactly, so they can contain cookies, authorization headers, tokens, and request bodies. Treat them like credentials and don&#x27;t commit them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;har-curl-and-raw-http&quot;&gt;HAR, curl, and Raw HTTP&lt;&#x2F;h2&gt;
&lt;p&gt;The native format is the best representation of a Proxelar session, but it&#x27;s not always the format you need. 0.5.0 can import HAR and export three interoperable forms:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --import-har browser-capture.har&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --export-har cleaned.har&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --export-curl replay.sh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --export-raw raw-flows&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;HAR carries HTTP requests and responses. The curl export writes one command per request. Raw export writes request&#x2F;response file pairs while preserving repeated headers instead of flattening them into a map.&lt;&#x2F;p&gt;
&lt;p&gt;Common secrets are redacted from these exports by default:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Authorization&lt;&#x2F;code&gt; and &lt;code&gt;Proxy-Authorization&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Cookie&lt;&#x2F;code&gt; and &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;common token and secret query parameters&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can opt out with &lt;code&gt;--export-secrets&lt;&#x2F;code&gt;, and the flag is loud about it on purpose. Exported captures have a way of ending up in issue trackers, chat messages, and temporary repositories, so redaction is the safer default.&lt;&#x2F;p&gt;
&lt;p&gt;HAR can&#x27;t represent everything in a native session. Raw TCP chunks, DNS&#x2F;UDP data, and some WebSocket metadata stay native-only. In practice I use HAR when another tool needs the HTTP traffic, curl when I want a reproducible request, and the native file when I might need the full capture later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;filters-became-a-language&quot;&gt;Filters Became a Language&lt;&#x2F;h2&gt;
&lt;p&gt;The old &lt;code&gt;column:value&lt;&#x2F;code&gt; filter was fine for &lt;code&gt;status:404&lt;&#x2F;code&gt;. It got awkward the moment I wanted something like &quot;POST requests that failed, except health checks&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Filters now support boolean operators, parentheses, negation, and body&#x2F;header matching:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;method:POST &amp;amp; status:500&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;host:api.example.test &amp;amp; !path:&#x2F;health&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(status:401 | status:403) &amp;amp; header:authorization&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;type:json &amp;amp; response_body:error&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;proto:wss | proto:https&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Adjacent terms are an implicit AND, so this works too:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;method:POST host:api.example.test status:4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The available fields are &lt;code&gt;host:&lt;&#x2F;code&gt;, &lt;code&gt;method:&lt;&#x2F;code&gt;, &lt;code&gt;status:&lt;&#x2F;code&gt;, &lt;code&gt;type:&lt;&#x2F;code&gt;, &lt;code&gt;body:&lt;&#x2F;code&gt;, &lt;code&gt;header:&lt;&#x2F;code&gt;, &lt;code&gt;request_body:&lt;&#x2F;code&gt;, and &lt;code&gt;response_body:&lt;&#x2F;code&gt;. Matching is substring-based rather than regular-expression-based, which keeps the common cases readable without much quoting.&lt;&#x2F;p&gt;
&lt;p&gt;The same parser backs the TUI, web GUI, session API, and cross-protocol match endpoint, so a filter doesn&#x27;t mean one thing in the terminal and something slightly different in the browser.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-050&#x2F;boolean-filter.png&quot; alt=&quot;The web GUI showing a boolean content-type filter across captured JSON and JavaScript responses&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bodies-are-more-than-a-byte-count&quot;&gt;Bodies Are More Than a Byte Count&lt;&#x2F;h2&gt;
&lt;p&gt;Clicking a JSON response and seeing one long escaped string wasn&#x27;t useful. The new content layer decodes transport and content encodings, then picks a view based on the media type.&lt;&#x2F;p&gt;
&lt;p&gt;JSON, XML, HTML, forms, multipart bodies, CSS, and JavaScript are formatted for inspection. Declared character sets are decoded. &lt;code&gt;gzip&lt;&#x2F;code&gt;, Brotli, zstd, and deflate bodies are decompressed before display. Safe raster images can render directly in the web interface.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-050&#x2F;content-view.png&quot; alt=&quot;A captured application&#x2F;json response rendered as formatted structured content&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Binary data stays binary. Invalid UTF-8 request bodies open as hexadecimal bytes instead of being mangled through a replacement-character string. Protobuf messages can be viewed and edited as wire fields without a schema, and JSON-shaped MessagePack values get a structured editor.&lt;&#x2F;p&gt;
&lt;p&gt;There are limits. Protobuf without a schema gives you field numbers, not meaningful names. Multipart is split into readable parts, but it isn&#x27;t a full structured multipart editor. A body capped by &lt;code&gt;--body-capture-limit&lt;&#x2F;code&gt; is marked as truncated and can&#x27;t be reconstructed from the captured prefix.&lt;&#x2F;p&gt;
&lt;p&gt;When an intercepted or replayed body changes, Proxelar drops stale transfer&#x2F;content encodings and recalculates &lt;code&gt;Content-Length&lt;&#x2F;code&gt;. That sounds minor, but an editor that leaves &lt;code&gt;Content-Encoding: gzip&lt;&#x2F;code&gt; on a plain JSON body creates bugs that look nothing like the edit you just made.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rules-for-the-things-that-don-t-need-lua&quot;&gt;Rules for the Things That Don&#x27;t Need Lua&lt;&#x2F;h2&gt;
&lt;p&gt;Lua is still the flexible option, but I kept writing tiny scripts for jobs that are really configuration: map this asset directory, point this API prefix at localhost, return a fixed health response.&lt;&#x2F;p&gt;
&lt;p&gt;Two common mappings can now live on the command line:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --map-local&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;https:&#x2F;&#x2F;app.test&#x2F;assets&#x2F;=.&#x2F;fixtures&#x2F;assets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --map-remote&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;https:&#x2F;&#x2F;api.test&#x2F;v1&#x2F;=http:&#x2F;&#x2F;127.0.0.1:3000&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For anything more involved, use a JSON rules file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;rules&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;set_request_header&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;url_prefix&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;https:&#x2F;&#x2F;api.test&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;x-debug-client&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;mock&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;url_prefix&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;https:&#x2F;&#x2F;api.test&#x2F;health&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;method&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: [{ &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; }],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;      &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:true}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --rules rules.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Rules run in file order. Header changes can accumulate; the first matching rule that creates a response wins. The available actions are &lt;code&gt;map_local&lt;&#x2F;code&gt;, &lt;code&gt;map_remote&lt;&#x2F;code&gt;, &lt;code&gt;redirect&lt;&#x2F;code&gt;, &lt;code&gt;mock&lt;&#x2F;code&gt;, &lt;code&gt;set_request_header&lt;&#x2F;code&gt;, and &lt;code&gt;remove_request_header&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Map-local paths are constrained to the configured directory, and &lt;code&gt;..&#x2F;&lt;&#x2F;code&gt; traversal and symlink escapes are rejected. A development convenience shouldn&#x27;t turn into a file server for the rest of your machine.&lt;&#x2F;p&gt;
&lt;p&gt;Roughly: rules for static routing, Lua when the decision needs code, interactive intercept when it&#x27;s one request.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-headless-api&quot;&gt;A Headless API&lt;&#x2F;h2&gt;
&lt;p&gt;The web interface already needed a server for live events and commands, so 0.5.0 exposes the useful parts as a documented JSON API:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;export&lt;&#x2F;span&gt;&lt;span&gt; PROXELAR_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;local-development-token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i api --api-token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$PROXELAR_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Every request needs a bearer token:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  -H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Authorization: Bearer &lt;&#x2F;span&gt;&lt;span&gt;$PROXELAR_TOKEN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;  &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;http:&#x2F;&#x2F;127.0.0.1:8081&#x2F;api&#x2F;v1&#x2F;flows?filter=method:POST%20%26%20status:500&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The API can read the session, query flows, fetch decoded content views, replay requests, clear traffic, toggle intercept mode, and resolve pending intercepts with forward&#x2F;drop&#x2F;modify decisions. Binary modified bodies can be sent as byte arrays, and headers can be represented as an ordered list when duplicate order matters. Shell scripts and local test harnesses become straightforward, which is what I was after.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s still a local developer API. There are no user accounts, TLS termination, rate limits, or multi-tenant permissions. Keep it on loopback, and if it has to cross a network, put it behind an authenticated TLS tunnel and treat the token as a credential.&lt;&#x2F;p&gt;
&lt;p&gt;The browser GUI uses a separate login token. Proxelar opens a URL with the token in the fragment, exchanges it for an &lt;code&gt;HttpOnly&lt;&#x2F;code&gt;, &lt;code&gt;SameSite=Strict&lt;&#x2F;code&gt; cookie, and removes the fragment from browser history. The API token isn&#x27;t embedded in the downloadable JavaScript.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;more-ways-to-get-traffic-in&quot;&gt;More Ways to Get Traffic In&lt;&#x2F;h2&gt;
&lt;p&gt;Forward and reverse proxy modes are still the normal choices, but some clients can&#x27;t be configured with an HTTP proxy at all. 0.5.0 adds four narrower capture modes for those cases.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;socks5&quot;&gt;SOCKS5&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m socks5 -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 1080&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --socks5-hostname 127.0.0.1:1080 http:&#x2F;&#x2F;example.com&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The SOCKS5 listener accepts IPv4, IPv6, and domain CONNECT targets. HTTP is inspected, TLS goes through the normal local-CA flow, and unknown protocols are recorded as directional raw TCP chunks. There&#x27;s no SOCKS authentication yet, so bind it to loopback.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dns&quot;&gt;DNS&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m dns -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 5353&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --dns-upstream 1.1.1.1:53&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --dns-map api.example.test=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt;127.0.0.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;DNS mode records UDP queries and responses, forwards normal lookups to the configured resolver, and can synthesize A&#x2F;AAAA answers. I use it to point a real client at a local API without touching the client&#x27;s hostname configuration. It&#x27;s plain UDP DNS, not DNS-over-HTTPS.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fixed-target-udp&quot;&gt;Fixed-Target UDP&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m udp -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 9001&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --target upstream.example:9000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This mode forwards each incoming datagram to one known upstream and records both directions. It&#x27;s request&#x2F;response-oriented by design: one target, at most one response per request, and a five-second no-response result. Good for small known UDP protocols, not a general UDP router.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;wireguard&quot;&gt;WireGuard&lt;&#x2F;h3&gt;
&lt;p&gt;This is the mode I&#x27;m happiest with. If an app has no proxy setting but can use a VPN profile, nothing else in Proxelar could reach it before.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m wireguard -b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 0.0.0.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 51820&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --wireguard-endpoint 192.168.1.10:51820&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On first start, Proxelar writes an owner-only client configuration to &lt;code&gt;~&#x2F;.proxelar&#x2F;proxelar-wg.conf&lt;&#x2F;code&gt;. The empty TUI and authenticated web GUI show the same profile as a QR code, and terminal mode prints it at startup. Scan it from the WireGuard app or import the file directly. The QR disappears from the interactive interfaces after the first captured event.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-050&#x2F;wireguard-tui-qr.png&quot; alt=&quot;The empty Proxelar TUI showing the WireGuard client profile as a high-contrast QR code&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;proxelar-wg&lt;&#x2F;code&gt; name has to be short because Android caps WireGuard interface names at 15 characters. The QR contains the client private key, so only show it on a trusted screen.&lt;&#x2F;p&gt;
&lt;p&gt;TCP is reconstructed in userspace and then follows the normal HTTP, TLS, WebSocket, or raw-stream paths. Port 53 uses the DNS configuration and overrides described above. For now it generates one client identity per CA directory, UDP forwarding is still aimed at request&#x2F;response traffic, and QUIC&#x2F;HTTP&#x2F;3 interception isn&#x27;t supported.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chaining-through-another-proxy&quot;&gt;Chaining Through Another Proxy&lt;&#x2F;h2&gt;
&lt;p&gt;Outbound connections can go through an HTTP CONNECT or SOCKS5 proxy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --upstream-proxy http:&#x2F;&#x2F;proxy.example:8080&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --upstream-proxy socks5:&#x2F;&#x2F;127.0.0.1:9050&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --upstream-proxy http:&#x2F;&#x2F;proxy.example:8080&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --upstream-proxy-auth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;user:password&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Chaining applies to ordinary forwarding, reverse mode, and replay. Credentials passed on the command line may be visible to local process inspection, so use a dedicated low-privilege account rather than your important password.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lua-addons-instead-of-loose-folders&quot;&gt;Lua Addons Instead of Loose Folders&lt;&#x2F;h2&gt;
&lt;p&gt;Loose &lt;code&gt;--script&lt;&#x2F;code&gt; files are still the fastest way to iterate. They now hot-reload when the entrypoint changes, keeping the last working version if the new script has an error. WebSocket frames can also pass through an optional Lua hook for modification or dropping.&lt;&#x2F;p&gt;
&lt;p&gt;For something I want to keep or share, there&#x27;s now an addon package format:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; addon verify .&#x2F;header-tagger&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; addon install .&#x2F;header-tagger&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; addon list&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --addon header-tagger&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;An addon has an &lt;code&gt;init.lua&lt;&#x2F;code&gt; entrypoint and a versioned &lt;code&gt;proxelar-addon.json&lt;&#x2F;code&gt; manifest. The manifest declares its version, hooks, requirements, and a SHA-256 digest for every file. Installation rejects traversal, symlinks, undeclared files, and digest mismatches before copying the package into the local catalog.&lt;&#x2F;p&gt;
&lt;p&gt;No online marketplace, no registry. It&#x27;s an inspectable package boundary for Lua code you already have on disk, which is as far as I want to take it for now.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;launching-a-browser-without-changing-system-settings&quot;&gt;Launching a Browser Without Changing System Settings&lt;&#x2F;h2&gt;
&lt;p&gt;For browser-only debugging, this is the shortest path:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i gui --launch-browser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Proxelar finds a Chromium-family browser and starts an isolated profile configured to use the proxy. Your normal profile, extensions, cookies, and proxy settings stay untouched, which saves you from remembering what to undo afterwards.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-few-things-that-changed-underneath&quot;&gt;A Few Things That Changed Underneath&lt;&#x2F;h2&gt;
&lt;p&gt;Some smaller changes that matter even without their own button:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;generated leaf certificates now use distinct ECDSA private keys instead of reusing the CA key&lt;&#x2F;li&gt;
&lt;li&gt;replay and edited bodies reconcile stale encoding and length headers&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;proxyapi&lt;&#x2F;code&gt; keeps &lt;code&gt;#![forbid(unsafe_code)]&lt;&#x2F;code&gt;, and addons remain portable pure Lua&lt;&#x2F;li&gt;
&lt;li&gt;release artifacts include SHA-256 checksums, SPDX SBOMs, and provenance attestations&lt;&#x2F;li&gt;
&lt;li&gt;the repository now documents the session compatibility policy, capture topology, architecture, and threat model&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And the limits worth knowing up front: HTTP&#x2F;2 clients are accepted, but inspected requests are currently normalized and forwarded upstream as HTTP&#x2F;1.1. HTTP&#x2F;3 isn&#x27;t intercepted. Certificate-pinned applications will reject the generated certificates, and Android applications have to explicitly trust user-installed CAs. I&#x27;d rather write those down than hide them behind a &quot;supports HTTPS&quot; bullet.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Install or update Proxelar:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;brew&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; upgrade proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; install proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For a quick browser session with persistence:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i gui --launch-browser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --save-session debug.proxelar.json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --export-har debug.har&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For a quick SOCKS test:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i gui -m socks5 -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 1080&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;curl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --socks5-hostname 127.0.0.1:1080 http:&#x2F;&#x2F;example.com&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For a local API with a static mock:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m reverse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --target http:&#x2F;&#x2F;127.0.0.1:3000&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --rules rules.json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBCB8B;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;  --save-session local-api.proxelar.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The full documentation is at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;proxelar.micheletti.io&quot;&gt;proxelar.micheletti.io&lt;&#x2F;a&gt;, and the complete changelog is on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.5.0&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;d start with sessions and the filter language. They&#x27;re the least dramatic features in the release, and they&#x27;re the ones that changed my day-to-day use the most. Capture something once, narrow it down without scrolling through 500 rows, and keep the useful part for tomorrow.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Cove 0.2.0: Agent Mode Without Settings</title>
          <pubDate>Sat, 02 May 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/cove-020/</link>
          <guid>https://micheletti.io/cove-020/</guid>
          <description xml:base="https://micheletti.io/cove-020/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&#x2F;releases&#x2F;tag&#x2F;v0.2.0&quot;&gt;Cove 0.2.0&lt;&#x2F;a&gt; is out.&lt;&#x2F;p&gt;
&lt;p&gt;The main thing in this release is Agent Mode: Cove can now ask a local coding agent to write or edit the query you are working on. No API key screen, no provider setup, no model picker, no billing integration. If you already have &lt;code&gt;claude&lt;&#x2F;code&gt; or &lt;code&gt;codex&lt;&#x2F;code&gt; installed on your machine, Cove can use it.&lt;&#x2F;p&gt;
&lt;p&gt;That was the whole design constraint for this release. I did not want to build another settings page just to make a text box talk to an LLM. Developers already have these tools installed, logged in, configured, and paid for. Cove should just reuse them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;agent-mode&quot;&gt;Agent Mode&lt;&#x2F;h2&gt;
&lt;p&gt;Press &lt;strong&gt;Cmd+K&lt;&#x2F;strong&gt;, or hover the current query block and click &lt;strong&gt;agent mode&lt;&#x2F;strong&gt;. A small prompt opens inside the editor. Ask for what you want:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;show the slowest queries grouped by user for the last 24 hours&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;change this query so it only includes failed payments&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-020&#x2F;agent-mode-composer.png&quot; alt=&quot;Agent Mode open in the Cove query editor next to the selected query block&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-020&#x2F;agent-mode-composer-zoom.png&quot; alt=&quot;Agent Mode open inside the Cove query editor, ready to edit the selected query&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Cove sends the selected query block, the active backend, the database metadata it knows about, and your instruction to the local agent CLI. The agent returns only query text, and Cove inserts it back into the editor.&lt;&#x2F;p&gt;
&lt;p&gt;If there is already a query under the cursor, Agent Mode treats your request as an edit. If the cursor is on an empty line, it inserts a new query there. This keeps it out of the chat-app shape. You are still in the query editor, and the output is still just a query.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-local-clis&quot;&gt;Why Local CLIs&lt;&#x2F;h2&gt;
&lt;p&gt;I want things simple.&lt;&#x2F;p&gt;
&lt;p&gt;Most technical users who want this feature already have Claude Code or Codex CLI installed. Those tools already handle auth, account state, subscriptions, model routing, rate limits, config files, and whatever else their vendors need this month. Cove does not need to duplicate any of that.&lt;&#x2F;p&gt;
&lt;p&gt;So 0.2.0 does the boring thing: it looks for &lt;code&gt;claude&lt;&#x2F;code&gt; or &lt;code&gt;codex&lt;&#x2F;code&gt; on &lt;code&gt;PATH&lt;&#x2F;code&gt; and runs the command locally.&lt;&#x2F;p&gt;
&lt;p&gt;For Claude Code, Cove uses print mode and disables tool use. For Codex CLI, Cove runs &lt;code&gt;codex exec&lt;&#x2F;code&gt; in an ephemeral, read-only workspace. The prompt goes in through stdin, the query comes back through stdout or the CLI output file, and Cove strips code fences if the model includes them anyway.&lt;&#x2F;p&gt;
&lt;p&gt;That is it. There is no Cove account. There is no Cove API key. There is no server in the middle.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-cove-sends&quot;&gt;What Cove Sends&lt;&#x2F;h2&gt;
&lt;p&gt;The prompt is intentionally plain. The agent gets:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;the connection name and environment&lt;&#x2F;li&gt;
&lt;li&gt;the backend type, like PostgreSQL, Redis, MongoDB, DuckDB, etc.&lt;&#x2F;li&gt;
&lt;li&gt;the active database&lt;&#x2F;li&gt;
&lt;li&gt;the selected tree path, if you have one&lt;&#x2F;li&gt;
&lt;li&gt;the loaded sidebar tree&lt;&#x2F;li&gt;
&lt;li&gt;completion schema: schemas, tables, collections, indexes, fields, functions, and types where available&lt;&#x2F;li&gt;
&lt;li&gt;the server version, when Cove can fetch it&lt;&#x2F;li&gt;
&lt;li&gt;the current query block&lt;&#x2F;li&gt;
&lt;li&gt;your instruction&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It does not send database passwords.&lt;&#x2F;p&gt;
&lt;p&gt;The most important rule in the prompt is also the simplest one: return only the query or command text. No Markdown, no explanation, no &quot;here is the SQL&quot;. If the model still returns a fenced code block, Cove unwraps it.&lt;&#x2F;p&gt;
&lt;p&gt;I want this to feel like autocomplete with more context, not like a second application living inside the database client.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-context-matters&quot;&gt;The Context Matters&lt;&#x2F;h2&gt;
&lt;p&gt;Asking an LLM to &quot;write a query&quot; without schema context is usually a coin toss. It will invent table names with full confidence, because that is what it does.&lt;&#x2F;p&gt;
&lt;p&gt;Cove already has a lot of database context loaded for autocomplete and browsing, so Agent Mode reuses that. If you have expanded &lt;code&gt;public&#x2F;users&lt;&#x2F;code&gt; and &lt;code&gt;public&#x2F;orders&lt;&#x2F;code&gt; in the sidebar, that tree is part of the prompt. If the backend supports completion schema, the agent also sees the real table and column names.&lt;&#x2F;p&gt;
&lt;p&gt;The prompt explicitly tells the agent not to invent database objects. This does not make it magic, but it moves the feature from &quot;toy demo&quot; to &quot;actually useful for boring work&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;For example, asking for:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;find users who placed more than 3 orders this month&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;is only useful if the agent knows whether your database has &lt;code&gt;users&lt;&#x2F;code&gt;, &lt;code&gt;accounts&lt;&#x2F;code&gt;, &lt;code&gt;orders&lt;&#x2F;code&gt;, &lt;code&gt;purchases&lt;&#x2F;code&gt;, &lt;code&gt;created_at&lt;&#x2F;code&gt;, &lt;code&gt;inserted_at&lt;&#x2F;code&gt;, or whatever names you actually use. Cove now gives it that context.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;editing-existing-queries&quot;&gt;Editing Existing Queries&lt;&#x2F;h2&gt;
&lt;p&gt;The part I use more than generation is editing.&lt;&#x2F;p&gt;
&lt;p&gt;Select a query, or just put the cursor inside it, then ask:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make this return one row per customer instead of one row per order&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;convert this Postgres query to ClickHouse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;or:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;add a time bucket by hour and keep the same filters&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The current query block is included in the prompt, and Cove replaces that block with the result. The runnable range box stays around the edited query, so you can immediately hit Cmd+Return and run it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-020&#x2F;agent-generated-query.png&quot; alt=&quot;Cove after Agent Mode generated a replacement query in the editor&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is the interaction I wanted: keep the database client focused on the database, but let a local agent remove the annoying parts of writing SQL by hand.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;still-review-before-running&quot;&gt;Still Review Before Running&lt;&#x2F;h2&gt;
&lt;p&gt;Agent Mode does not execute anything automatically.&lt;&#x2F;p&gt;
&lt;p&gt;It writes text into the editor. You read it, change it if needed, then run it yourself. That is especially important in a database client, where a confident wrong query can be much worse than a compiler error.&lt;&#x2F;p&gt;
&lt;p&gt;The same idea applies to table edits. Cove already staged inline edits and showed generated SQL before saving. In 0.2.0 that review flow is tighter for query result edits too. For PostgreSQL result sets that can be traced back to a single table and include the primary key columns, Cove can infer the editable table, stage the changes, and show the generated &lt;code&gt;UPDATE&lt;&#x2F;code&gt; statements before anything hits the database.&lt;&#x2F;p&gt;
&lt;p&gt;No invisible writes. No &quot;trust me&quot; behavior.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;better-error-text&quot;&gt;Better Error Text&lt;&#x2F;h2&gt;
&lt;p&gt;Small fix, but it matters: Cove now keeps useful database error details visible instead of flattening everything into generic localized error strings.&lt;&#x2F;p&gt;
&lt;p&gt;Driver errors, especially from Postgres&#x2F;NIO, often carry the useful part in their custom string representation. The human-readable server message is what you need when a query fails. Losing that and showing a vague wrapper error is just hostile.&lt;&#x2F;p&gt;
&lt;p&gt;0.2.0 preserves more of that detail in the editor and surrounding UI.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;not-a-full-agent-platform&quot;&gt;Not a Full Agent Platform&lt;&#x2F;h2&gt;
&lt;p&gt;This release is deliberately small.&lt;&#x2F;p&gt;
&lt;p&gt;Right now Agent Mode supports:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code via the &lt;code&gt;claude&lt;&#x2F;code&gt; CLI&lt;&#x2F;li&gt;
&lt;li&gt;Codex CLI via the &lt;code&gt;codex&lt;&#x2F;code&gt; CLI&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I am going to add more agents and API-backed providers in future releases. There are valid reasons to want direct API support: reproducible model selection, team config, remote environments, non-interactive installs, and agents that do not ship as local CLIs.&lt;&#x2F;p&gt;
&lt;p&gt;But I did not want the first version to start there. The zero-settings version is the one I actually wanted to use today.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;The next obvious steps:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;more local agents&lt;&#x2F;li&gt;
&lt;li&gt;direct API provider support&lt;&#x2F;li&gt;
&lt;li&gt;better per-backend prompt context&lt;&#x2F;li&gt;
&lt;li&gt;a way to preview the prompt Cove sends&lt;&#x2F;li&gt;
&lt;li&gt;import&#x2F;export&lt;&#x2F;li&gt;
&lt;li&gt;data filtering and search&lt;&#x2F;li&gt;
&lt;li&gt;query history&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Feedback and contributions are welcome on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Proxelar 0.4.2: WebSocket Inspection and Smarter Filtering</title>
          <pubDate>Sat, 11 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/proxelar-042/</link>
          <guid>https://micheletti.io/proxelar-042/</guid>
          <description xml:base="https://micheletti.io/proxelar-042/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.4.2&quot;&gt;Proxelar 0.4.2&lt;&#x2F;a&gt; is out.&lt;&#x2F;p&gt;
&lt;p&gt;Two things in this release. WebSocket connections are now fully inspectable — frames, directions, payloads, all of it. And the TUI filter bar learned &lt;code&gt;column:value&lt;&#x2F;code&gt; syntax so you can stop searching through everything just to find your 404s.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;watching-websocket-traffic&quot;&gt;Watching WebSocket Traffic&lt;&#x2F;h2&gt;
&lt;p&gt;Until now, Proxelar captured the WebSocket upgrade handshake — a normal HTTP request — but had no visibility into the frames flowing over the open connection. Starting with 0.4.2, Proxelar proxies WebSocket connections the same way it does HTTPS — transparently, without the client knowing. The upgrade handshake appears as a normal row. A live connection shows &lt;code&gt;WS⇄&lt;&#x2F;code&gt; in the status column; once it closes, it shows &lt;code&gt;WS✓&lt;&#x2F;code&gt;. The size column counts frames instead of bytes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-042&#x2F;tui-ws-rows.png&quot; alt=&quot;TUI with WebSocket rows alongside regular HTTP traffic — WS⇄ for live connections, WS✓ for closed ones&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The detail panel for a WebSocket row has a &lt;strong&gt;Frames&lt;&#x2F;strong&gt; tab. Each entry shows direction (&lt;code&gt;↑&lt;&#x2F;code&gt; client→server, &lt;code&gt;↓&lt;&#x2F;code&gt; server→client), opcode, size in bytes, and a payload preview — text as characters, binary as hex. The list tails the connection live; scrolling up pauses follow mode.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-042&#x2F;tui-ws-frames.png&quot; alt=&quot;Frames tab — captured frames with direction, opcode, size, and payload preview&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The web GUI works the same way. WebSocket rows appear in the table; clicking one replaces the Request&#x2F;Response tabs with a Frames tab, and new frames append to the bottom live as they arrive. A badge at the top of the panel shows whether the connection is still open.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-042&#x2F;web-ws-frames.png&quot; alt=&quot;Web GUI Frames tab — live frame list with per-frame direction, opcode, and payload&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A concrete scenario where this saves time: your app has a real-time notification system that occasionally drops messages. You suspect the server is sending frames faster than the client is processing them, or maybe there&#x27;s a malformed payload somewhere. Point the app at Proxelar, reproduce the bug, and look at the Frames tab. You can see exactly which frame arrived when, in which direction, and what it contained — without touching the application code.&lt;&#x2F;p&gt;
&lt;p&gt;One implementation detail worth knowing: proxying WebSocket frames correctly requires the proxy to speak both sides of the protocol at once. Toward the client it acts as a WebSocket server (client frames are masked, server frames are unmasked). Toward the upstream it acts as a WebSocket client (the opposite). The frame relay runs in a &lt;code&gt;select!&lt;&#x2F;code&gt; loop, emitting a &lt;code&gt;WebSocketFrame&lt;&#x2F;code&gt; event for each frame before forwarding it. &lt;code&gt;wss:&#x2F;&#x2F;&lt;&#x2F;code&gt; connections go through the same HTTPS MITM as regular HTTPS, so you don&#x27;t need to do anything extra once the CA certificate is installed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;filtering-by-column&quot;&gt;Filtering by Column&lt;&#x2F;h2&gt;
&lt;p&gt;Once a traffic capture session gets long — a few hundred requests — the filter bar starts to feel blunt. Searching for &lt;code&gt;200&lt;&#x2F;code&gt; matches URLs, methods, and hosts that happen to contain that string, not just status codes. Searching for &lt;code&gt;github&lt;&#x2F;code&gt; might pull in requests you didn&#x27;t want because the path mentions it somewhere.&lt;&#x2F;p&gt;
&lt;p&gt;The TUI filter now accepts &lt;code&gt;column:value&lt;&#x2F;code&gt; syntax. Press &lt;kbd&gt;&#x2F;&lt;&#x2F;kbd&gt;, type &lt;code&gt;status:4&lt;&#x2F;code&gt;, press &lt;kbd&gt;Enter&lt;&#x2F;kbd&gt;. You get only 4xx responses, regardless of what else the rows contain.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;status:404      only 404s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;status:4        all 4xx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;method:POST     POST requests only&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;host:github     requests to github hosts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;path:&#x2F;api&#x2F;v2    requests to that path prefix&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;size:1.5        responses around 1.5 KB&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-042&#x2F;tui-column-filter.png&quot; alt=&quot;TUI with status:4 filter active — only 4xx responses visible&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Plain text search still works exactly as before. &lt;code&gt;github&lt;&#x2F;code&gt; still matches URLs and methods containing that string. The column syntax only activates when the prefix before &lt;code&gt;:&lt;&#x2F;code&gt; is a recognised column name — everything else is a plain search, so nothing you were already doing breaks.&lt;&#x2F;p&gt;
&lt;p&gt;WebSocket rows participate in column filtering too — &lt;code&gt;status:ws&lt;&#x2F;code&gt; matches all of them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;brew&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; upgrade proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; install proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For WebSocket traffic over &lt;code&gt;wss:&#x2F;&#x2F;&lt;&#x2F;code&gt;, the CA certificate is the only prerequisite — the same one you installed for HTTPS. No extra configuration.&lt;&#x2F;p&gt;
&lt;p&gt;The full changelog is on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.4.2&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;WebSocket inspection was the last item on the roadmap I set in &lt;a href=&quot;&#x2F;proxelar-030&quot;&gt;0.3.0&lt;&#x2F;a&gt;. The list is shorter now. What remains:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Save &#x2F; load &#x2F; export&lt;&#x2F;strong&gt; — sessions disappear on shutdown; they shouldn&#x27;t&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Content views&lt;&#x2F;strong&gt; — JSON, XML, and HTML deserve syntax highlighting and formatting, not raw text&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;2 interception&lt;&#x2F;strong&gt; — currently falls through to raw TCP tunneling&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Proxelar 0.4.0: Intercept &amp; Modify Traffic</title>
          <pubDate>Sat, 04 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/proxelar-040/</link>
          <guid>https://micheletti.io/proxelar-040/</guid>
          <description xml:base="https://micheletti.io/proxelar-040/">&lt;p&gt;I&#x27;m happy to release &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.4.0&quot;&gt;Proxelar 0.4.0&lt;&#x2F;a&gt;, which delivers the feature I teased at the end of the &lt;a href=&quot;&#x2F;proxelar-030&quot;&gt;0.3.0 post&lt;&#x2F;a&gt;: &lt;strong&gt;intercept mode&lt;&#x2F;strong&gt;. You can now pause any HTTP or HTTPS request mid-flight, inspect it, edit whatever you want — method, URI, headers, body — and then choose to forward it or drop it entirely.&lt;&#x2F;p&gt;
&lt;p&gt;Until now, Proxelar could capture and display traffic, and with scripting it could modify traffic programmatically. But there was no way to reach in and change a specific request by hand, on the fly, without writing a script first. Intercept mode fills that gap. You see the request frozen in front of you, exactly as the client sent it, and you decide: forward it as-is, modify it first, or drop it entirely.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-intercept-mode-works&quot;&gt;How Intercept Mode Works&lt;&#x2F;h2&gt;
&lt;p&gt;When intercept is &lt;strong&gt;off&lt;&#x2F;strong&gt;, traffic flows through normally — captured, displayed, forwarded. When intercept is &lt;strong&gt;on&lt;&#x2F;strong&gt;, every request is held. Nothing reaches the upstream server until you act on it. The proxy is paused, waiting for your decision.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tui&quot;&gt;TUI&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;toggling-intercept&quot;&gt;Toggling Intercept&lt;&#x2F;h3&gt;
&lt;p&gt;Press &lt;strong&gt;&lt;code&gt;i&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; to enable intercept. The status bar shows a red &lt;strong&gt;&lt;code&gt;INTERCEPT&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; badge so you always know whether you&#x27;re in capture-only mode or actively intercepting.&lt;&#x2F;p&gt;
&lt;p&gt;Press &lt;strong&gt;&lt;code&gt;i&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; again to turn it off. Any requests that were pending at that moment are forwarded immediately — clients never hang waiting for a response.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-040&#x2F;tui-intercept-on.png&quot; alt=&quot;TUI with intercept active — red INTERCEPT badge in the status bar, pending rows in the request list&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;acting-on-a-request&quot;&gt;Acting on a Request&lt;&#x2F;h3&gt;
&lt;p&gt;When a request arrives while intercept is on, it appears in the request list as a &lt;code&gt;⏸&lt;&#x2F;code&gt; row instead of the usual status code. Navigate to it with &lt;code&gt;j&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;k&lt;&#x2F;code&gt; and choose what to do:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Key&lt;&#x2F;th&gt;&lt;th&gt;Action&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;f&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Forward the request to the upstream server&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;d&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Drop — returns a 504 Gateway Timeout to the client&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;e&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Open the inline editor&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;the-inline-editor&quot;&gt;The Inline Editor&lt;&#x2F;h3&gt;
&lt;p&gt;Press &lt;strong&gt;&lt;code&gt;e&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; and the full raw HTTP request appears in an editor panel. Every part of the request is editable — the request line, headers, blank separator, and body, exactly as they would be written on the wire:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;POST &#x2F;api&#x2F;login HTTP&#x2F;1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;host: example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;content-type: application&#x2F;json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;authorization: Bearer old-token&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&amp;quot;user&amp;quot;:&amp;quot;alice&amp;quot;,&amp;quot;pass&amp;quot;:&amp;quot;secret&amp;quot;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Change anything. Replace the URL, swap the method, add or delete a header, rewrite the body. The editor supports full cursor navigation:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Key&lt;&#x2F;th&gt;&lt;th&gt;Action&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Arrow keys &#x2F; Home &#x2F; End&lt;&#x2F;td&gt;&lt;td&gt;Move the cursor&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Enter&lt;&#x2F;td&gt;&lt;td&gt;Insert a new line&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Backspace &#x2F; Delete&lt;&#x2F;td&gt;&lt;td&gt;Delete characters&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Esc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Finish editing — request stays held, ready to forward&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;f&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Forward with your edits applied&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;d&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Drop&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Esc&lt;&#x2F;code&gt; (again, when not typing)&lt;&#x2F;td&gt;&lt;td&gt;Discard your edits&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-040&#x2F;tui-editor.png&quot; alt=&quot;TUI inline editor — full raw HTTP request, editable in place&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The design is a &lt;strong&gt;two-step flow&lt;&#x2F;strong&gt;: edit freely, then &lt;code&gt;Esc&lt;&#x2F;code&gt; to stage your changes, then &lt;code&gt;f&lt;&#x2F;code&gt; to forward. This lets you review your edits before committing. If you change your mind and want to discard everything, press &lt;code&gt;Esc&lt;&#x2F;code&gt; again from the idle-in-editor state.&lt;&#x2F;p&gt;
&lt;p&gt;If the request line you wrote is malformed — wrong format, missing HTTP version, unrecognized method — the editor stays open and the border turns red. Fix the line and try forwarding again. The proxy never sends a request it can&#x27;t parse.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-040&#x2F;tui-editor-error.png&quot; alt=&quot;TUI editor parse error — red border and error message when the request line is malformed&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Binary bodies&lt;&#x2F;strong&gt; — if the original request body is not valid UTF-8, the editor shows a ⚠ warning. The content is displayed lossily; edits may corrupt binary data, so forward binary-bodied requests without edits or drop them. A future patch will preserve the original bytes when only the method, URI, or headers are changed.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Here is a complete session: a login request caught mid-flight, the token replaced, forwarded to the server:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Original:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;POST &#x2F;api&#x2F;login HTTP&#x2F;1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;host: api.example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;content-type: application&#x2F;json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&amp;quot;user&amp;quot;:&amp;quot;alice&amp;quot;,&amp;quot;pass&amp;quot;:&amp;quot;wrong-password&amp;quot;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Edited (in the TUI inline editor):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;POST &#x2F;api&#x2F;login HTTP&#x2F;1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;host: api.example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;content-type: application&#x2F;json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&amp;quot;user&amp;quot;:&amp;quot;alice&amp;quot;,&amp;quot;pass&amp;quot;:&amp;quot;correct-password&amp;quot;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# After pressing f:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;→ server receives the corrected request&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;→ client receives the real response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;→ TUI shows the completed exchange as a normal row&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No script, no configuration, no restart. You caught it, changed it, forwarded it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;web-gui&quot;&gt;Web GUI&lt;&#x2F;h2&gt;
&lt;p&gt;The web GUI gets the same capabilities with a point-and-click interface.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;toggling-intercept-1&quot;&gt;Toggling Intercept&lt;&#x2F;h3&gt;
&lt;p&gt;Click the &lt;strong&gt;&lt;code&gt;⏸ Intercept: OFF&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; button in the toolbar to enable intercept. It turns red and starts displaying a count of pending requests — &lt;code&gt;⏸ Intercept: ON (2)&lt;&#x2F;code&gt; — so you can tell at a glance how many clients are waiting.&lt;&#x2F;p&gt;
&lt;p&gt;Click it again to turn intercept off. All pending requests are forwarded immediately.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-040&#x2F;web-intercept-on.png&quot; alt=&quot;Web GUI intercept button active — red, showing pending request count&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;acting-on-a-request-1&quot;&gt;Acting on a Request&lt;&#x2F;h3&gt;
&lt;p&gt;Pending requests appear in the table with an amber left border to distinguish them from completed exchanges. Click a row to open the editor panel.&lt;&#x2F;p&gt;
&lt;p&gt;The panel shows four editable fields:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Method&lt;&#x2F;strong&gt; — text field, e.g. &lt;code&gt;GET&lt;&#x2F;code&gt;, &lt;code&gt;POST&lt;&#x2F;code&gt;, &lt;code&gt;DELETE&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;URI&lt;&#x2F;strong&gt; — the full request URI&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Headers&lt;&#x2F;strong&gt; — key&#x2F;value pairs, one per line&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Body&lt;&#x2F;strong&gt; — free-form text area&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Edit whatever you need, then:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Forward&lt;&#x2F;strong&gt; to send the request (with any edits applied)&lt;&#x2F;li&gt;
&lt;li&gt;Click &lt;strong&gt;Drop (504)&lt;&#x2F;strong&gt; to block the request and return a 504 to the client&lt;&#x2F;li&gt;
&lt;li&gt;Press &lt;strong&gt;Ctrl+Enter&lt;&#x2F;strong&gt; as a keyboard shortcut for Forward&lt;&#x2F;li&gt;
&lt;li&gt;Press &lt;strong&gt;Esc&lt;&#x2F;strong&gt; or click &lt;strong&gt;×&lt;&#x2F;strong&gt; to close the panel without acting — the request stays pending in the table&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;proxelar-040&#x2F;web-editor.png&quot; alt=&quot;Web GUI editor panel — method, URI, headers, body fields with Forward and Drop buttons&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a typical flow in the web GUI: you&#x27;re testing an API that reads a user ID from the URL. You intercept a &lt;code&gt;GET &#x2F;api&#x2F;users&#x2F;1&lt;&#x2F;code&gt; request, change the ID to &lt;code&gt;99&lt;&#x2F;code&gt;, and forward it to see what the server returns for a user you don&#x27;t have credentials for. Two clicks, no tooling.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;practical-scenarios&quot;&gt;Practical Scenarios&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;testing-authorization-logic&quot;&gt;Testing Authorization Logic&lt;&#x2F;h3&gt;
&lt;p&gt;You want to verify that your API correctly rejects requests with an expired token. With intercept on, catch a normal request, replace the &lt;code&gt;Authorization&lt;&#x2F;code&gt; header with an expired JWT, and forward:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GET &#x2F;api&#x2F;profile HTTP&#x2F;1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;host: api.example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;authorization: Bearer eyJ... (expired token)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The server returns 401. You&#x27;ve confirmed the token validation is working — without touching any client code or adding test infrastructure.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;overriding-request-parameters&quot;&gt;Overriding Request Parameters&lt;&#x2F;h3&gt;
&lt;p&gt;Your frontend sends a request with a hardcoded value you can&#x27;t easily change without recompiling. Intercept it, replace the value in the body, forward it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Original body:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&amp;quot;plan&amp;quot;: &amp;quot;free&amp;quot;, &amp;quot;user_id&amp;quot;: 42}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Edited:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&amp;quot;plan&amp;quot;: &amp;quot;enterprise&amp;quot;, &amp;quot;user_id&amp;quot;: 42}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You just tested the premium code path from your existing client session.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;catching-and-blocking-specific-requests&quot;&gt;Catching and Blocking Specific Requests&lt;&#x2F;h3&gt;
&lt;p&gt;Sometimes you want to know what happens when a request doesn&#x27;t go through. Drop a specific request mid-session — the client gets a 504, and you observe how it handles the failure without shutting anything down.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;replaying-a-request-with-modifications&quot;&gt;Replaying a Request with Modifications&lt;&#x2F;h3&gt;
&lt;p&gt;Intercept is complementary to Lua scripting. A script is the right tool when you want a rule that applies to every request matching a pattern. Intercept is the right tool when you want to make a one-off decision for a specific request, inspect the result, and move on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;under-the-hood&quot;&gt;Under the Hood&lt;&#x2F;h2&gt;
&lt;p&gt;Intercept mode is built around two new types exported from &lt;code&gt;proxyapi&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;pub struct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt; InterceptConfig&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    pub&lt;&#x2F;span&gt;&lt;span&gt; enabled&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;pub enum&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt; InterceptDecision&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    Forward&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;ProxiedRequest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8FBCBB;&quot;&gt;    Drop&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When intercept is enabled, the &lt;code&gt;CapturingHandler&lt;&#x2F;code&gt; emits a &lt;code&gt;RequestIntercepted&lt;&#x2F;code&gt; event with the request and a oneshot channel instead of forwarding immediately. The interface layer — TUI or web GUI — receives the event, holds the request in a pending list, and waits for the user&#x27;s decision. When the user acts, an &lt;code&gt;InterceptDecision&lt;&#x2F;code&gt; is sent back through the channel and the proxy forwards or drops accordingly.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;RequestIntercepted&lt;&#x2F;code&gt; event carries the same stable ID that the eventual &lt;code&gt;RequestComplete&lt;&#x2F;code&gt; event will use, so the interface can correlate the pending row with the completed exchange after the request goes through.&lt;&#x2F;p&gt;
&lt;p&gt;Turning intercept off drains the pending queue by sending &lt;code&gt;InterceptDecision::Forward&lt;&#x2F;code&gt; for each waiting request. This is why clients don&#x27;t hang: the proxy never leaves a oneshot receiver without a sender.&lt;&#x2F;p&gt;
&lt;p&gt;One edge case worth knowing: if a client times out while its request is pending, it drops the TCP connection. The row stays in the UI — the proxy has no way to know the client gave up. If you then forward, the upstream response has nowhere to go and the write fails silently. The proxy handles this gracefully, but the pending row will linger until you act on it or toggle intercept off.&lt;&#x2F;p&gt;
&lt;p&gt;The parse-error path in the TUI editor validates the edited request before forwarding. If the request line doesn&#x27;t parse — wrong number of parts, unrecognized method, bad HTTP version — the editor returns an error and the border turns red. The forward is not attempted until the input is valid.&lt;&#x2F;p&gt;
&lt;p&gt;Two bugs were fixed in this release along the way:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;XSS in the web GUI header editor&lt;&#x2F;strong&gt; — the header editor was previously building the DOM using &lt;code&gt;innerHTML&lt;&#x2F;code&gt;, which would execute arbitrary HTML in header values. It now uses proper DOM construction methods.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Multi-value header loss&lt;&#x2F;strong&gt; — both the TUI and web editors were using &lt;code&gt;insert&lt;&#x2F;code&gt; when writing edited headers back, which silently dropped all but the last value for headers like &lt;code&gt;Set-Cookie&lt;&#x2F;code&gt;. They now use &lt;code&gt;append&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Update Proxelar:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; install proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run the proxy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;          # TUI (default)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i gui&lt;&#x2F;span&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;   # Web GUI at localhost:8081&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In the TUI, press &lt;code&gt;i&lt;&#x2F;code&gt; to toggle intercept. In the web GUI, click the intercept button in the toolbar. Browse normally — or point &lt;code&gt;curl&lt;&#x2F;code&gt; at the proxy with &lt;code&gt;--proxy http:&#x2F;&#x2F;127.0.0.1:8080&lt;&#x2F;code&gt; — and watch requests arrive as &lt;code&gt;⏸&lt;&#x2F;code&gt; rows.&lt;&#x2F;p&gt;
&lt;p&gt;The full documentation for intercept mode is at &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;proxelar.micheletti.io&#x2F;intercept.html&quot;&gt;proxelar.micheletti.io&#x2F;intercept.html&lt;&#x2F;a&gt;. The complete changelog is on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.4.0&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;Intercept mode closes the gap between passively watching traffic and actively controlling it. The remaining items from the &lt;a href=&quot;&#x2F;proxelar-030&quot;&gt;0.3.0 roadmap&lt;&#x2F;a&gt; are still the priority:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traffic replay&lt;&#x2F;strong&gt; — resend any captured request from the UI, or serve recorded responses as a mock server. The data is already there; this is about building the UI to use it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Save &#x2F; load &#x2F; export&lt;&#x2F;strong&gt; — persist captured sessions to disk. Export to HAR, curl commands, or raw HTTP. No more losing everything on shutdown.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;2 interception&lt;&#x2F;strong&gt; — full MITM for HTTP&#x2F;2 connections instead of falling through to raw TCP tunneling.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WebSocket inspection&lt;&#x2F;strong&gt; — decode and display individual WebSocket frames with message-level granularity.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Content views&lt;&#x2F;strong&gt; — auto-detect and prettify JSON, XML, HTML, Protobuf, and form data with syntax highlighting across all interfaces.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Proxelar 0.3.0: Lua Scripting for HTTP Traffic</title>
          <pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/proxelar-030/</link>
          <guid>https://micheletti.io/proxelar-030/</guid>
          <description xml:base="https://micheletti.io/proxelar-030/">&lt;p&gt;I&#x27;m happy to release &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.3.0&quot;&gt;Proxelar 0.3.0&lt;&#x2F;a&gt;, which adds the feature I&#x27;ve wanted since the beginning: &lt;strong&gt;Lua scripting&lt;&#x2F;strong&gt;. You can now write simple scripts that intercept, modify, block, or mock HTTP traffic as it flows through the proxy.&lt;&#x2F;p&gt;
&lt;p&gt;Until now, Proxelar could capture and display traffic, but it couldn&#x27;t change it. That made it a viewer, not a tool. With scripting, Proxelar becomes programmable — you can inject headers, block ad domains, mock API endpoints, rewrite responses, strip cookies, and anything else you can express in a few lines of Lua. This is the single biggest feature since the &lt;a href=&quot;&#x2F;proxelar-020&quot;&gt;0.2.0 rewrite&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;&#x2F;h2&gt;
&lt;p&gt;Create a Lua script that defines &lt;code&gt;on_request&lt;&#x2F;code&gt; and&#x2F;or &lt;code&gt;on_response&lt;&#x2F;code&gt; — both are optional. Pass it to Proxelar with &lt;code&gt;--script&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --script my_script.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it. The proxy loads the script at startup, and every request and response flows through your hooks before being forwarded. Here&#x27;s the full API:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Called before forwarding the request to the upstream server.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Return the request table to forward it (modified or not).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Return a response table to short-circuit (the request never reaches upstream).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Return nil to pass through unchanged.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- request.method   &amp;quot;GET&amp;quot;, &amp;quot;POST&amp;quot;, ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- request.url      &amp;quot;https:&#x2F;&#x2F;example.com&#x2F;path?q=1&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- request.headers  { [&amp;quot;host&amp;quot;] = &amp;quot;example.com&amp;quot;, ... }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- request.body     string (may contain binary data)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Called before returning the response to the client.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Return the response table (modified or not), or nil to pass through.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- response.status   200&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- response.headers  { [&amp;quot;content-type&amp;quot;] = &amp;quot;text&#x2F;html&amp;quot;, ... }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- response.body     string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The design is deliberately minimal: two hooks, plain Lua tables, no framework to learn. If you&#x27;ve ever written a line of Lua (or even if you haven&#x27;t — the syntax takes about five minutes to pick up), you can start scripting your proxy immediately. And if five minutes still sounds like too much effort, just describe what you want to an LLM and paste the output.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-lua&quot;&gt;Why Lua&lt;&#x2F;h2&gt;
&lt;p&gt;Scripting is the feature that turns a proxy from a debugging tool into a development platform. For a Rust project, I wanted a scripting language that could be &lt;strong&gt;embedded with zero system dependencies&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Lua checks every box. It&#x27;s the standard scripting language for networking tools — nginx, HAProxy, nmap, Redis, and WireGuard all use it. The runtime is tiny, fast (script calls take microseconds), and the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;khvzak&#x2F;mlua&quot;&gt;mlua&lt;&#x2F;a&gt; crate provides safe Rust bindings with vendored compilation. When you &lt;code&gt;cargo install proxelar&lt;&#x2F;code&gt;, Lua 5.4 is compiled from source alongside everything else. No Python installation, no PATH issues, no version conflicts.&lt;&#x2F;p&gt;
&lt;p&gt;Scripting is behind a &lt;code&gt;scripting&lt;&#x2F;code&gt; feature flag (enabled by default), so if you need a minimal build without Lua, &lt;code&gt;--no-default-features&lt;&#x2F;code&gt; gives you exactly the same proxy as before.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;real-examples&quot;&gt;Real Examples&lt;&#x2F;h2&gt;
&lt;p&gt;Let me walk through a few practical scenarios. Each one is a complete, working script.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;blocking-domains&quot;&gt;Blocking Domains&lt;&#x2F;h3&gt;
&lt;p&gt;The simplest use case: prevent requests from reaching certain hosts.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;local&lt;&#x2F;span&gt;&lt;span&gt; blocked&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;ads%.example%.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;tracker%.analytics%.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; _, pattern&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; ipairs&lt;&#x2F;span&gt;&lt;span&gt;(blocked) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, pattern) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 403&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; { [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;text&#x2F;plain&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Blocked by Proxelar: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt; request.url,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;ads.example.com&#x2F;banner.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;Blocked&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; by Proxelar: http:&#x2F;&#x2F;ads.example.com&#x2F;banner.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;example.com&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;doctype&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; htm&lt;&#x2F;span&gt;&lt;span&gt;l&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;  # passes through normally&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When &lt;code&gt;on_request&lt;&#x2F;code&gt; returns a table with a &lt;code&gt;status&lt;&#x2F;code&gt; field, Proxelar treats it as a response and sends it back directly — the request never leaves the proxy. Return &lt;code&gt;nil&lt;&#x2F;code&gt; (or nothing) and the request passes through untouched.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;mocking-api-endpoints&quot;&gt;Mocking API Endpoints&lt;&#x2F;h3&gt;
&lt;p&gt;During frontend development, you often need a backend endpoint that doesn&#x27;t exist yet. Instead of setting up a mock server, point your app at the proxy and let the script handle it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; request.method&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;GET&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&#x2F;api&#x2F;user&#x2F;me&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; { [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&amp;quot;id&amp;quot;: 1, &amp;quot;name&amp;quot;: &amp;quot;Test User&amp;quot;, &amp;quot;email&amp;quot;: &amp;quot;test@example.com&amp;quot;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; request.method&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;POST&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&#x2F;api&#x2F;login&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 200&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; { [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&amp;quot;token&amp;quot;: &amp;quot;mock-jwt-token-12345&amp;quot;, &amp;quot;expires_in&amp;quot;: 3600}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;api.myapp.com&#x2F;api&#x2F;user&#x2F;me&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;id&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; 1,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Test User&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;email&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;test@example.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -x http:&#x2F;&#x2F;127.0.0.1:8080 -X POST http:&#x2F;&#x2F;api.myapp.com&#x2F;api&#x2F;login&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;token&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;mock-jwt-token-12345&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;expires_in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;api.myapp.com&#x2F;api&#x2F;products&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# passes through to the real server&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Unmocked endpoints pass through normally, so you can mix real and fake responses in the same session.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;injecting-headers&quot;&gt;Injecting Headers&lt;&#x2F;h3&gt;
&lt;p&gt;Need to test how your app behaves with specific headers? Inject them on every request:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Authorization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Bearer dev-token-12345&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;X-Request-ID&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; tostring&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;os.time&lt;&#x2F;span&gt;&lt;span&gt;())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; request&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;httpbin.org&#x2F;headers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;  &amp;quot;headers&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    &amp;quot;Authorization&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Bearer dev-token-12345&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    &amp;quot;X-Request-ID&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;1743206400&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    &amp;quot;Host&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;httpbin.org&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is particularly useful for testing authenticated APIs without modifying client code or storing credentials in app configuration.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;adding-cors-headers&quot;&gt;Adding CORS Headers&lt;&#x2F;h3&gt;
&lt;p&gt;Every frontend developer has hit CORS issues during local development. Instead of configuring the backend, let the proxy fix it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Access-Control-Allow-Origin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Access-Control-Allow-Methods&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;GET, POST, PUT, DELETE, OPTIONS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Access-Control-Allow-Headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Type, Authorization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -v -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;api.example.com&#x2F;data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; HTTP&#x2F;1.1 200 OK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Content-Type: application&#x2F;json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Access-Control-Allow-Origin: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Access-Control-Allow-Headers: Content-Type, Authorization&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The CORS headers are injected into every response, regardless of what the upstream server returns. Point your browser at the proxy and the cross-origin errors disappear.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;rewriting-html-responses&quot;&gt;Rewriting HTML Responses&lt;&#x2F;h3&gt;
&lt;p&gt;You can modify response bodies too. This script injects a visual indicator into every HTML page so you always know you&#x27;re browsing through the proxy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; ct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;or&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if not&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(ct, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;text&#x2F;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then return end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; banner&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&amp;lt;div style=&amp;quot;position:fixed;top:0;left:0;right:0;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;background:#ff6b35;color:white;text-align:center;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;padding:4px;z-index:99999;font-size:12px;&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Proxied by Proxelar&amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.gsub&lt;&#x2F;span&gt;&lt;span&gt;(response.body, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&amp;lt;body&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&amp;lt;body&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt; banner, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Every HTML page now shows an orange bar at the top. Non-HTML responses (images, JSON, CSS) pass through untouched because the function returns &lt;code&gt;nil&lt;&#x2F;code&gt; early.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stripping-tracking-cookies&quot;&gt;Stripping Tracking Cookies&lt;&#x2F;h3&gt;
&lt;p&gt;Remove known tracking cookies from your outgoing requests while keeping functional ones intact:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;local&lt;&#x2F;span&gt;&lt;span&gt; tracking&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; { &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;_ga&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;_gid&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;fbp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;fr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;datr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; cookie&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;cookie&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if not&lt;&#x2F;span&gt;&lt;span&gt; cookie&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; then return end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; kept&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; pair&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.gmatch&lt;&#x2F;span&gt;&lt;span&gt;(cookie, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;([^;]+)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        pair&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.match&lt;&#x2F;span&gt;&lt;span&gt;(pair, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;^%s*(.-)%s*$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        local&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.match&lt;&#x2F;span&gt;&lt;span&gt;(pair, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;^([^=]+)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        local&lt;&#x2F;span&gt;&lt;span&gt; dominated&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; = false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; _, tc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; ipairs&lt;&#x2F;span&gt;&lt;span&gt;(tracking) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; tc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; then&lt;&#x2F;span&gt;&lt;span&gt; dominated&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; = true&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;break end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        if not&lt;&#x2F;span&gt;&lt;span&gt; dominated&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; then&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; table.insert&lt;&#x2F;span&gt;&lt;span&gt;(kept, pair) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if #&lt;&#x2F;span&gt;&lt;span&gt;kept&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;cookie&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; table.concat&lt;&#x2F;span&gt;&lt;span&gt;(kept, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;cookie&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;= nil&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; request&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# Original cookie header:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# Cookie: session=abc123; _ga=GA1.2.123; lang=en; _gid=GA1.2.456&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# After script:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# Cookie: session=abc123; lang=en&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;logging-traffic&quot;&gt;Logging Traffic&lt;&#x2F;h3&gt;
&lt;p&gt;For quick debugging, print a summary of every request and response to stdout:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;string.format&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;[REQ] %s %s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, request.method, request.url))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; ct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;or&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;unknown&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; = #&lt;&#x2F;span&gt;&lt;span&gt;response.body&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;string.format&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;[RES] %s %s -&amp;gt; %d (%s, %d bytes)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        request.method, request.url, response.status, ct, size))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; proxelar -i terminal --script log_traffic.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# (in another terminal: curl -x http:&#x2F;&#x2F;127.0.0.1:8080 http:&#x2F;&#x2F;example.com)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;REQ&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt; GET http:&#x2F;&#x2F;example.com&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;RES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt; GET http:&#x2F;&#x2F;example.com&#x2F; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; 200 (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;text&#x2F;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span&gt; charset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;UTF-8,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; 1256&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; bytes&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notice that both hooks return &lt;code&gt;nil&lt;&#x2F;code&gt; (implicitly), so traffic passes through unchanged. The script is purely observational.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reverse-proxy-scripting&quot;&gt;Reverse Proxy + Scripting&lt;&#x2F;h2&gt;
&lt;p&gt;The examples above use forward proxy mode, where you configure your client to route through Proxelar. But scripting really shines in &lt;strong&gt;reverse proxy mode&lt;&#x2F;strong&gt;, where Proxelar sits in front of your service and you control the traffic between your clients and your backend. This is the setup you&#x27;d use at work — put the proxy in front of your local API, staging environment, or microservice, and let scripts handle the rest.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;simulating-authentication-in-development&quot;&gt;Simulating Authentication in Development&lt;&#x2F;h3&gt;
&lt;p&gt;Your backend requires a JWT, but during local development you don&#x27;t want to go through the login flow every time. Put Proxelar in front of your API and let the script handle auth:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m reverse --target http:&#x2F;&#x2F;localhost:3000 --script auth_dev.lua -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 4000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- auth_dev.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Clients hit localhost:4000, Proxelar forwards to localhost:3000 with auth injected&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;local&lt;&#x2F;span&gt;&lt;span&gt; DEV_USER&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&amp;quot;sub&amp;quot;: &amp;quot;user-42&amp;quot;, &amp;quot;role&amp;quot;: &amp;quot;admin&amp;quot;, &amp;quot;name&amp;quot;: &amp;quot;Dev User&amp;quot;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- Skip if the client already sent a token&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;authorization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then return end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;authorization&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Bearer dev-token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- Inject the decoded user context that your middleware expects&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    request.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;x-user-context&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; DEV_USER&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; request&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# No token needed — the proxy injects it&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;admin&#x2F;users&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: 1, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Alice&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}, {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: 2, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Bob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# If you pass your own token, the script leaves it alone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -H&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Authorization: Bearer real-token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;me&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;id&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; 7,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;You&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Your frontend, Postman, or any HTTP client can hit &lt;code&gt;localhost:4000&lt;&#x2F;code&gt; without worrying about tokens. The backend sees a properly authenticated request every time.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;adding-security-headers-to-a-local-service&quot;&gt;Adding Security Headers to a Local Service&lt;&#x2F;h3&gt;
&lt;p&gt;Your staging environment fails security audits because the backend doesn&#x27;t set the right headers yet. Instead of waiting for a backend fix, add them at the proxy layer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m reverse --target http:&#x2F;&#x2F;localhost:3000 --script security_headers.lua -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 4000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- security_headers.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Strict-Transport-Security&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;max-age=31536000; includeSubDomains&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;X-Content-Type-Options&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;nosniff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;X-Frame-Options&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;DENY&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Security-Policy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;default-src &amp;#39;self&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Referrer-Policy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;strict-origin-when-cross-origin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl -v http:&#x2F;&#x2F;localhost:4000&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; HTTP&#x2F;1.1 200 OK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Strict-Transport-Security: max-age&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;31536000&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; includeSubDomains&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; X-Content-Type-Options: nosniff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; X-Frame-Options: DENY&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Content-Security-Policy: default-src &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; Referrer-Policy: strict-origin-when-cross-origin&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Point the security scanner at &lt;code&gt;localhost:4000&lt;&#x2F;code&gt; and the headers are there. When the backend team ships the real implementation, remove the proxy and nothing changes.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;faking-errors-to-test-resilience&quot;&gt;Faking Errors to Test Resilience&lt;&#x2F;h3&gt;
&lt;p&gt;You want to verify that your frontend handles server errors gracefully — timeouts, 500s, rate limits. Instead of breaking your actual backend, make the proxy return errors for specific endpoints:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m reverse --target http:&#x2F;&#x2F;localhost:3000 --script chaos.lua -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 4000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- chaos.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Simulate failures on specific endpoints to test client error handling&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- Simulate a 500 on the payments endpoint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&#x2F;api&#x2F;payments&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 500&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; { [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&amp;quot;error&amp;quot;: &amp;quot;Internal Server Error&amp;quot;, &amp;quot;message&amp;quot;: &amp;quot;database connection timeout&amp;quot;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- Simulate rate limiting on search&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&#x2F;api&#x2F;search&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 429&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Content-Type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Retry-After&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;30&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&amp;quot;error&amp;quot;: &amp;quot;Too Many Requests&amp;quot;, &amp;quot;retry_after&amp;quot;: 30}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;payments&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;error&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Internal Server Error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;database connection timeout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;search?q=test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;error&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;Too Many Requests&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;retry_after&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;users&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# passes through to the real backend normally&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Edit the script, restart the proxy, and you have a different failure scenario. No mocking libraries, no environment variables, no code changes in your application.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;api-response-patching&quot;&gt;API Response Patching&lt;&#x2F;h3&gt;
&lt;p&gt;Your backend returns a response that&#x27;s &lt;em&gt;almost&lt;&#x2F;em&gt; right, but you need to tweak a field to unblock frontend work. Instead of modifying the backend or hardcoding values in the frontend, patch it at the proxy:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m reverse --target http:&#x2F;&#x2F;localhost:3000 --script patch_api.lua -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 4000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;lua&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- patch_api.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;-- Patch specific fields in API responses without touching the backend&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; on_response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;request&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    local&lt;&#x2F;span&gt;&lt;span&gt; ct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; response.headers[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;content-type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;] &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;or&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if not&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(ct, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;application&#x2F;json&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then return end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- The backend doesn&amp;#39;t return feature flags yet, but the frontend expects them&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&#x2F;api&#x2F;config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.sub&lt;&#x2F;span&gt;&lt;span&gt;(response.body, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            response.body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.gsub&lt;&#x2F;span&gt;&lt;span&gt;(response.body, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;                &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&amp;quot;feature_flags&amp;quot;:{&amp;quot;new_dashboard&amp;quot;:true,&amp;quot;dark_mode&amp;quot;:true}}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;    -- Override the environment label so the frontend shows &amp;quot;staging&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.find&lt;&#x2F;span&gt;&lt;span&gt;(request.url, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&#x2F;api&#x2F;health&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        response.body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt; string.gsub&lt;&#x2F;span&gt;&lt;span&gt;(response.body, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&amp;quot;env&amp;quot;:&amp;quot;development&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;&amp;quot;env&amp;quot;:&amp;quot;staging&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;span&gt; response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;version&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;1.2.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;feature_flags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;: {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;new_dashboard&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;dark_mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; curl http:&#x2F;&#x2F;localhost:4000&#x2F;api&#x2F;health&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;&amp;quot;status&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;staging&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;uptime&lt;&#x2F;span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt; 3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The backend returns the real data, and the proxy patches only what you need. When the backend catches up, delete the script.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-s-built&quot;&gt;How It&#x27;s Built&lt;&#x2F;h2&gt;
&lt;p&gt;The scripting engine lives in &lt;code&gt;proxyapi&#x2F;src&#x2F;scripting.rs&lt;&#x2F;code&gt;, behind a &lt;code&gt;scripting&lt;&#x2F;code&gt; feature flag. A single Lua VM is created at startup, loaded with the user&#x27;s script, and shared across all connections via &lt;code&gt;Arc&amp;lt;ScriptEngine&amp;gt;&lt;&#x2F;code&gt;. The VM is protected by a &lt;code&gt;std::sync::Mutex&lt;&#x2F;code&gt; — not a tokio mutex, since Lua calls are synchronous and complete in microseconds.&lt;&#x2F;p&gt;
&lt;p&gt;The hooks are injected directly into the existing &lt;code&gt;CapturingHandler&lt;&#x2F;code&gt;, which already handles body collection and event emission. The request hook runs in &lt;code&gt;handle_request()&lt;&#x2F;code&gt; after the body is collected but before forwarding. The response hook runs in &lt;code&gt;collect_and_emit()&lt;&#x2F;code&gt; before the event is emitted to the UI. This means &lt;strong&gt;zero changes to the forward or reverse proxy modules&lt;&#x2F;strong&gt; — scripting is entirely transparent to the rest of the proxy.&lt;&#x2F;p&gt;
&lt;p&gt;Script errors are caught, logged, and the request passes through unchanged. A buggy script can never crash the proxy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Update Proxelar:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; install proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run with a script:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --script examples&#x2F;scripts&#x2F;block_domain.lua&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The repository includes &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;tree&#x2F;main&#x2F;examples&#x2F;scripts&quot;&gt;13 example scripts&lt;&#x2F;a&gt; covering header injection, domain blocking, API mocking, CORS fixes, traffic logging, HTML rewriting, cookie stripping, and more. Each one is a standalone file you can use directly or adapt.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;Scripting was the most important missing piece, but there&#x27;s still a long road ahead. Here&#x27;s what I&#x27;m working toward, roughly in priority order:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interactive intercept mode&lt;&#x2F;strong&gt; — pause matching requests in the TUI, edit headers and body on the fly, then forward. Real man-in-the-middle editing, not just observation.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Traffic replay&lt;&#x2F;strong&gt; — resend captured requests from the UI, or serve previously recorded responses as a mock server. Essential for testing and debugging.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Save &#x2F; load &#x2F; export&lt;&#x2F;strong&gt; — persist captured sessions to disk. Export to HAR, curl commands, or raw HTTP. No more losing everything on shutdown.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;HTTP&#x2F;2 interception&lt;&#x2F;strong&gt; — full MITM for HTTP&#x2F;2 connections instead of falling through to raw TCP tunneling.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WebSocket inspection&lt;&#x2F;strong&gt; — decode and display individual WebSocket frames with message-level granularity.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Content views&lt;&#x2F;strong&gt; — auto-detect and prettify JSON, XML, HTML, Protobuf, form data with syntax highlighting across all interfaces.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The full changelog is available on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.3.0&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Cove 0.1.0: Six New Backends, Tabs, and Environments</title>
          <pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/cove-010/</link>
          <guid>https://micheletti.io/cove-010/</guid>
          <description xml:base="https://micheletti.io/cove-010/">&lt;p&gt;I&#x27;m releasing &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&#x2F;releases&#x2F;tag&#x2F;v0.1.0&quot;&gt;Cove 0.1.0&lt;&#x2F;a&gt;, bringing the total number of supported databases from three to nine. MySQL, MariaDB, SQLite, MongoDB, Cassandra, and Elasticsearch join the original PostgreSQL, ScyllaDB, and Redis backends. This release also adds multiple tabs, connection environments, and encrypted credential storage.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-010&#x2F;hero.png&quot; alt=&quot;Cove 0.1.0 with the New Connection dialog showing all nine supported backends&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;DatabaseBackend&lt;&#x2F;code&gt; protocol from &lt;a href=&quot;&#x2F;cove&quot;&gt;0.0.0&lt;&#x2F;a&gt; made this straightforward — six new backends, roughly 5,000 new lines of Swift, zero changes to the UI layer.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;from-three-to-nine&quot;&gt;From Three to Nine&lt;&#x2F;h2&gt;
&lt;p&gt;The six new backends cover the databases I kept reaching for after the initial release:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;MySQL&lt;&#x2F;strong&gt; and &lt;strong&gt;MariaDB&lt;&#x2F;strong&gt; connect via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vapor&#x2F;mysql-nio&quot;&gt;mysql-nio&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vapor&#x2F;mysql-kit&quot;&gt;mysql-kit&lt;&#x2F;a&gt;. Both support multi-database browsing — the hierarchy shows all databases on the server, each with its own tables, views, indexes, and triggers. MariaDB shares the wire protocol but gets its own backend with MariaDB-specific keywords and behavior. TLS fallback is handled automatically.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;SQLite&lt;&#x2F;strong&gt; is the only backend that doesn&#x27;t need a server. Point it at a &lt;code&gt;.db&lt;&#x2F;code&gt; file and Cove opens it directly, no configuration beyond the file path. It uses the system SQLite library through a lightweight Swift wrapper — no external dependencies. The hierarchy maps tables, views, indexes, and triggers from the &lt;code&gt;sqlite_master&lt;&#x2F;code&gt; table.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;MongoDB&lt;&#x2F;strong&gt; speaks the MongoDB wire protocol via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mongodb&#x2F;mongo-swift-driver&quot;&gt;MongoSwift&lt;&#x2F;a&gt;. Instead of SQL, the query editor accepts shell-style commands (&lt;code&gt;find&lt;&#x2F;code&gt;, &lt;code&gt;insertOne&lt;&#x2F;code&gt;, &lt;code&gt;aggregate&lt;&#x2F;code&gt;). Since MongoDB is schemaless, Cove samples documents from each collection and builds a merged column set to display in the data table. The hierarchy lists databases, collections, and inferred fields.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Cassandra&lt;&#x2F;strong&gt; reuses the same &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;apple&#x2F;swift-cassandra-client&quot;&gt;swift-cassandra-client&lt;&#x2F;a&gt; driver as ScyllaDB but with its own keyword set and hierarchy tuned for Cassandra&#x27;s replication model. If you already had ScyllaDB working, Cassandra is essentially free — the CQL protocol is the same, but keeping them separate means the autocomplete and highlighter are accurate for each.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Elasticsearch&lt;&#x2F;strong&gt; operates differently from the other backends — no SQL, no tables, just indexes and REST-style queries. The query editor accepts JSON bodies that get sent as Elasticsearch search requests. The hierarchy discovers indexes and maps their field mappings into the familiar tree structure. It connects over HTTP via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;swift-server&#x2F;async-http-client&quot;&gt;async-http-client&lt;&#x2F;a&gt;, so it works with any Elasticsearch-compatible API including OpenSearch.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;DatabaseBackend&lt;&#x2F;code&gt; protocol didn&#x27;t need a single change to accommodate any of these. Each backend implements the same ~20 methods — hierarchy browsing, data fetching, query execution, inline editing, SQL&#x2F;command generation, and autocomplete keywords — and the UI treats them all identically.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;multiple-tabs&quot;&gt;Multiple Tabs&lt;&#x2F;h2&gt;
&lt;p&gt;Cove now supports multiple independent tabs, each with its own connection, query editor, and data view. Cmd+T opens a new tab, Cmd+W closes the current one. Each tab maintains its own session state — the selected tree node, the query text, pagination position — so switching between tabs picks up exactly where you left off.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-010&#x2F;multiple-tabs.png&quot; alt=&quot;Multiple tabs open in the tab bar&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Sessions are persisted per tab across app relaunches. Close Cove with three tabs open, relaunch it, and all three restore their previous state including the active connection and last query.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connection-environments&quot;&gt;Connection Environments&lt;&#x2F;h2&gt;
&lt;p&gt;Every connection is now tagged with an environment: Production, Staging, Development, or Local. The toolbar displays a colored indicator matching the active environment, so there&#x27;s a constant visual reminder of where you&#x27;re pointing. You can filter the connection rail by environment to keep things uncluttered.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-010&#x2F;multiple-environments.png&quot; alt=&quot;The environment picker in the toolbar with color-coded Production, Staging, Development, and Local options&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When switching between a local database and a production replica throughout the day, the colored indicator catches mistakes before they happen.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;encrypted-credential-storage&quot;&gt;Encrypted Credential Storage&lt;&#x2F;h2&gt;
&lt;p&gt;Passwords are no longer stored alongside connection configurations. Database passwords, SSH passwords, and SSH key passphrases are encrypted with AES-256-GCM via CryptoKit and stored in a separate &lt;code&gt;.secrets&lt;&#x2F;code&gt; file. The symmetric key is generated once and saved with restricted permissions (&lt;code&gt;0600&lt;&#x2F;code&gt;). The JSON config files in &lt;code&gt;~&#x2F;Library&#x2F;Application Support&#x2F;Cove&#x2F;&lt;&#x2F;code&gt; contain only non-sensitive fields.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bug-fixes&quot;&gt;Bug Fixes&lt;&#x2F;h2&gt;
&lt;p&gt;Notable fixes in this release:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TLS for PostgreSQL&lt;&#x2F;strong&gt; — cloud-hosted databases (Supabase, Neon, RDS) now connect properly. The TLS configuration was missing in 0.0.0, which meant only unencrypted local connections worked.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Completion engine cursor boundary&lt;&#x2F;strong&gt; — an off-by-one error caused the autocomplete to suggest completions one character too late in certain positions.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;SSH tunnel assignment&lt;&#x2F;strong&gt; — fixed a race condition in the tunnel setup that could leave the connection in a half-initialized state.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Download the DMG or ZIP from the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&#x2F;releases&#x2F;tag&#x2F;v0.1.0&quot;&gt;GitHub release&lt;&#x2F;a&gt;, or build from source:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; clone https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; cove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;xcodebuild&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -scheme Cove -derivedDataPath .build build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; .build&#x2F;Build&#x2F;Products&#x2F;Debug&#x2F;Cove.app&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Requires macOS 15 (Sequoia) or later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;Planned for upcoming releases:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;More database backends&lt;&#x2F;li&gt;
&lt;li&gt;Import&#x2F;export (CSV, JSON, SQL dump)&lt;&#x2F;li&gt;
&lt;li&gt;Query history panel&lt;&#x2F;li&gt;
&lt;li&gt;Data filtering and search&lt;&#x2F;li&gt;
&lt;li&gt;Query explain&#x2F;analyze visualization&lt;&#x2F;li&gt;
&lt;li&gt;SSL&#x2F;TLS certificate configuration for all backends&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Feedback and contributions are welcome on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Cove: A Native macOS Database Client</title>
          <pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/cove/</link>
          <guid>https://micheletti.io/cove/</guid>
          <description xml:base="https://micheletti.io/cove/">&lt;p&gt;I&#x27;m releasing &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&quot;&gt;Cove 0.0.0&lt;&#x2F;a&gt;, a native macOS database client built entirely in Swift and SwiftUI. It supports PostgreSQL, ScyllaDB, and Redis out of the box, with a pluggable architecture that makes adding new backends straightforward. No Electron, no web views — just a fast, lightweight, native app.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-hero.gif&quot; alt=&quot;Cove demo&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Most database clients are either Electron apps that eat memory for breakfast, or legacy native apps that haven&#x27;t been updated in years. DBeaver ships a new release every two weeks, which I find oddly comforting given that the experience has remained identical since 2013. I wanted something that feels like a proper macOS citizen — fast startup, native controls, Keychain integration — while supporting the databases I actually use. Let&#x27;s walk through what&#x27;s in this release.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-databasebackend-protocol&quot;&gt;The DatabaseBackend Protocol&lt;&#x2F;h2&gt;
&lt;p&gt;I built Cove with contributions in mind from the start. Not just human contributors — LLMs too. The architecture had to be obvious enough that someone opening the project for the first time could add a new database backend without reading the entire codebase, and structured enough that an LLM could do the same given the right context.&lt;&#x2F;p&gt;
&lt;p&gt;The result is a single Swift protocol — &lt;code&gt;DatabaseBackend&lt;&#x2F;code&gt; — that every database driver implements. The protocol defines around 20 methods covering hierarchy browsing, data fetching, query execution, inline editing, SQL generation, and autocomplete. The UI code never checks which backend is active. Browsing a Redis instance feels the same as browsing a Postgres database: same tree navigation, same editing workflow, same query interface.&lt;&#x2F;p&gt;
&lt;p&gt;Each backend is split into five focused files following a consistent pattern:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Cove&#x2F;DB&#x2F;Postgres&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ├── PostgresBackend.swift       # connection, keywords, pooling&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ├── PostgresHierarchy.swift     # schema → table → column tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ├── PostgresDataOps.swift       # fetchTableData, executeQuery&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ├── PostgresSQLGen.swift        # UPDATE&#x2F;INSERT&#x2F;DELETE generation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  └── PostgresDecoders.swift      # QueryResult parsing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;ScyllaDB and Redis follow the same structure. Adding a new backend — say, MySQL or MongoDB — means creating a new folder, implementing the protocol, and adding a case to the &lt;code&gt;BackendType&lt;&#x2F;code&gt; enum. Zero UI changes required. A human can follow the pattern by looking at any existing backend. An LLM can do it with just the protocol definition and one example. That was the whole point.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;three-backends-for-now&quot;&gt;Three Backends (For Now)&lt;&#x2F;h2&gt;
&lt;p&gt;The first three backends are the databases I actually use every day at work. I built what I needed first.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;&#x2F;strong&gt; connects via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vapor&#x2F;postgres-nio&quot;&gt;postgres-nio&lt;&#x2F;a&gt; with full SQL support, TLS connections, and per-database connection pooling. The keyword set covers DDL, DML, joins, window functions, and Postgres-specific types like &lt;code&gt;JSONB&lt;&#x2F;code&gt; and &lt;code&gt;UUID&lt;&#x2F;code&gt;. Client instances are cached per database with &lt;code&gt;NSLock&lt;&#x2F;code&gt; for thread safety, so switching between databases in the same connection doesn&#x27;t re-establish the link.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;ScyllaDB&lt;&#x2F;strong&gt; uses &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;apple&#x2F;swift-cassandra-client&quot;&gt;swift-cassandra-client&lt;&#x2F;a&gt; and speaks the native Cassandra protocol. CQL keywords power the syntax highlighter and autocomplete engine. The hierarchy maps keyspaces to tables to columns, matching ScyllaDB&#x27;s data model.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Redis&lt;&#x2F;strong&gt; is the odd one out — it&#x27;s a key-value store, not a relational database. The backend organizes keys by data type (strings, hashes, lists, sets, sorted sets, streams) and presents them in the same tree structure. The query editor accepts Redis commands directly. Connection pooling is handled via NIO&#x27;s &lt;code&gt;EventLoopGroup&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The obvious ones are next. In the upcoming patch versions I plan to add MySQL, SQLite, MongoDB, MariaDB, and Cassandra support. The protocol-based architecture was designed exactly for this — each new backend is an afternoon of work — or a couple of hours with an LLM.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;inline-editing-with-sql-preview&quot;&gt;Inline Editing with SQL Preview&lt;&#x2F;h2&gt;
&lt;p&gt;Cove wraps AppKit&#x27;s &lt;code&gt;NSTableView&lt;&#x2F;code&gt; in SwiftUI for native table performance and familiar macOS behavior — column resizing, sorting by header click, keyboard navigation with arrow keys. But the interesting part is inline editing.&lt;&#x2F;p&gt;
&lt;p&gt;Click any cell to edit it. The cell turns orange to indicate a pending change. New rows show a green background, deleted rows turn red. None of these changes hit the database immediately.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-edit-line.png&quot; alt=&quot;Editing a row inline — the modified row is highlighted and the row inspector shows the current values&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Instead, pressing Cmd+S opens a SQL preview sheet showing the exact &lt;code&gt;UPDATE&lt;&#x2F;code&gt;, &lt;code&gt;INSERT&lt;&#x2F;code&gt;, or &lt;code&gt;DELETE&lt;&#x2F;code&gt; statements that will be executed. Only after confirmation do the changes commit. This gives full visibility into what&#x27;s about to happen, which matters especially when pointing at a production database.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-confirm-changes.png&quot; alt=&quot;The Review Changes dialog showing the generated UPDATE statement before execution&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Keyboard shortcuts cover the common operations: Cmd+C to copy a cell, Cmd+R to refresh, Tab&#x2F;Shift+Tab to navigate between cells, Escape to discard edits, Return to commit. Pagination is configurable at 50, 100, or 500 rows per page.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;query-editor-with-syntax-highlighting-and-autocomplete&quot;&gt;Query Editor with Syntax Highlighting and Autocomplete&lt;&#x2F;h2&gt;
&lt;p&gt;The query editor is built on &lt;code&gt;NSTextView&lt;&#x2F;code&gt; with a custom tokenizer that colorizes SQL in real time. The tokenizer is a hand-written lexer that processes UTF-16 codepoints and recognizes keywords, string literals, numbers, single-line comments (&lt;code&gt;--&lt;&#x2F;code&gt;), and block comments (&lt;code&gt;&#x2F;* ... *&#x2F;&lt;&#x2F;code&gt;). Each backend provides its own keyword set, so the highlighter adapts automatically — SQL keywords for Postgres, CQL keywords for ScyllaDB.&lt;&#x2F;p&gt;
&lt;p&gt;Autocomplete is context-aware. The &lt;code&gt;CompletionEngine&lt;&#x2F;code&gt; analyzes the cursor position and offers different suggestions depending on where you are in a query:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;After &lt;code&gt;FROM&lt;&#x2F;code&gt; or &lt;code&gt;JOIN&lt;&#x2F;code&gt; — table names&lt;&#x2F;li&gt;
&lt;li&gt;After &lt;code&gt;WHERE&lt;&#x2F;code&gt; or in expressions — column names and functions&lt;&#x2F;li&gt;
&lt;li&gt;After a dot — columns of the referenced table or alias&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The engine resolves aliases too. If you write &lt;code&gt;FROM users u JOIN orders o&lt;&#x2F;code&gt;, then typing &lt;code&gt;u.&lt;&#x2F;code&gt; completes with columns from the &lt;code&gt;users&lt;&#x2F;code&gt; table. It handles comma-separated FROM clauses, all JOIN types, and basic subqueries. Suggestions pop up after a 100ms debounce, capped at 50 items, and you can navigate them with arrow keys or Tab to insert.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-runnable-range-indicator&quot;&gt;The Runnable Range Indicator&lt;&#x2F;h2&gt;
&lt;p&gt;This is the feature I&#x27;ve wanted in every database client I&#x27;ve ever used, and none of them have it.&lt;&#x2F;p&gt;
&lt;p&gt;When you have multiple SQL statements in the editor, a thin blue bar appears on the left margin highlighting the statement the cursor is currently inside. That&#x27;s it. You always know exactly which query will run when you hit the execute button. No selecting text, no hoping you got the right semicolon boundary, no accidentally running a &lt;code&gt;DROP&lt;&#x2F;code&gt; when you meant to run the &lt;code&gt;SELECT&lt;&#x2F;code&gt; above it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-runnable-range-indicator.png&quot; alt=&quot;The blue bar on the left marks the first SELECT statement as the runnable range&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Move the cursor to a different statement, and the bar follows.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-runnable-range-indicator-selection.png&quot; alt=&quot;Cursor moved to the second statement — the blue bar updates accordingly&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It sounds trivial, but after years of writing multi-statement scripts in database clients and second-guessing which statement is about to fire, this small piece of visual feedback changes the entire experience. I&#x27;m genuinely surprised no one else has done this.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ssh-tunneling&quot;&gt;SSH Tunneling&lt;&#x2F;h2&gt;
&lt;p&gt;Connecting to databases behind firewalls is a first-class feature. Cove&#x27;s SSH tunnel implementation uses Swift NIO&#x27;s NIOSSH library with support for both password and private key authentication.&lt;&#x2F;p&gt;
&lt;p&gt;For private keys, the loader handles multiple formats: OpenSSH, PEM (PKCS#8 and SEC1), and modern curves including Ed25519, P-256, P-384, and P-521. Encrypted keys are supported too — Cove detects the encryption, prompts for the passphrase, and decrypts via the system&#x27;s &lt;code&gt;ssh-keygen&lt;&#x2F;code&gt;. Error messages distinguish between wrong passphrases and unsupported key formats.&lt;&#x2F;p&gt;
&lt;p&gt;The tunnel establishes a local TCP server on an auto-selected port, then bridges traffic bidirectionally between the local socket and the remote SSH channel using custom NIO &lt;code&gt;ChannelDuplexHandler&lt;&#x2F;code&gt;s. A 10-second connection timeout prevents indefinite hangs, and a test channel verifies authentication before reporting success.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;connection-management-and-environments&quot;&gt;Connection Management and Environments&lt;&#x2F;h2&gt;
&lt;p&gt;The connection rail — a vertical sidebar on the far left — shows all saved connections as color-coded pills. Each connection is tagged with an environment: Production, Staging, Development, or Local. The toolbar displays a colored dot matching the active environment, so there&#x27;s always a visual reminder of where you&#x27;re pointing. You can filter connections by environment via the toolbar picker.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;cove-new-connection-form.png&quot; alt=&quot;The New Connection form with backend selector, environment picker, and SSH tunnel toggle&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Connection details (name, backend, host, port, database, SSH configuration) are stored as JSON in &lt;code&gt;~&#x2F;Library&#x2F;Application Support&#x2F;Cove&#x2F;&lt;&#x2F;code&gt;. Passwords are excluded from the JSON entirely — they&#x27;re stored in the macOS Keychain under the &lt;code&gt;com.cove.app&lt;&#x2F;code&gt; service, bound to the user&#x27;s login keychain via &lt;code&gt;kSecUseDataProtectionKeychain&lt;&#x2F;code&gt;. Three credentials per connection are handled separately: database password, SSH password, and SSH key passphrase. There&#x27;s also an automatic migration path: if Cove detects an older config file with plaintext passwords, it moves them to the Keychain and re-saves the JSON without credentials.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;&#x2F;h2&gt;
&lt;p&gt;The codebase is organized into four layers:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DB&lt;&#x2F;strong&gt; — the &lt;code&gt;DatabaseBackend&lt;&#x2F;code&gt; protocol, three backend implementations (15 files), SSH tunneling, connection configuration, and the autocomplete engine&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;State&lt;&#x2F;strong&gt; — &lt;code&gt;@Observable&lt;&#x2F;code&gt; classes for app state, tree navigation, table display, query editor, and dialogs. Uses Swift 6&#x27;s &lt;code&gt;@Observable&lt;&#x2F;code&gt; macro for fine-grained reactivity without &lt;code&gt;@Published&lt;&#x2F;code&gt; boilerplate&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Store&lt;&#x2F;strong&gt; — JSON persistence with Keychain integration, session persistence per tab, saved queries per connection, and automatic migration from legacy formats&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Views&lt;&#x2F;strong&gt; — 13 SwiftUI views covering the full interface: connection rail, sidebar tree, data table, row inspector, query editor with autocomplete popup, SQL preview sheet, and connection dialog&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The whole thing is written in Swift 6 with structured concurrency, targeting macOS 15+. About 9,000 lines of Swift across 49 files, with a self-imposed ~300 line limit per file. Everything runs on &lt;code&gt;@MainActor&lt;&#x2F;code&gt; with async&#x2F;await — no blocking the UI thread.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Clone and build with Xcode:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; clone https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; cove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;xcodebuild&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -scheme Cove -derivedDataPath .build build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or just open &lt;code&gt;Cove.xcodeproj&lt;&#x2F;code&gt; in Xcode and hit Run. Requires macOS 15 (Sequoia) or later.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;&#x2F;h2&gt;
&lt;p&gt;This is an early release. The core browsing, editing, and query workflow is solid, but there&#x27;s more to come:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Import&#x2F;export (CSV, JSON, SQL)&lt;&#x2F;li&gt;
&lt;li&gt;Multiple query tabs&lt;&#x2F;li&gt;
&lt;li&gt;Data filtering and search&lt;&#x2F;li&gt;
&lt;li&gt;Query history panel&lt;&#x2F;li&gt;
&lt;li&gt;SSL&#x2F;TLS certificate configuration for all backends&lt;&#x2F;li&gt;
&lt;li&gt;Query explain&#x2F;analyze visualization&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Feedback and contributions are welcome on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;cove&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;P.S. — Some of the screenshots show UI elements from future releases. I took them during development and didn&#x27;t bother retaking them for the v0.0.0 announcement. Consider it a spoiler.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Proxelar 0.2.0: A Complete Rewrite</title>
          <pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://micheletti.io/proxelar-020/</link>
          <guid>https://micheletti.io/proxelar-020/</guid>
          <description xml:base="https://micheletti.io/proxelar-020/">&lt;p&gt;I&#x27;m happy to release &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.2.0&quot;&gt;Proxelar 0.2.0&lt;&#x2F;a&gt;, a major milestone that completely reimagines the project from the ground up. What started as a Tauri desktop application is now a lean, modular CLI tool with three interface modes, a new proxy engine built on hyper 1.x, and both forward and reverse proxy support.&lt;&#x2F;p&gt;
&lt;p&gt;This release touches virtually every part of the codebase — 96 commits spanning architecture, networking, TLS, CI&#x2F;CD, and testing. Having &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.anthropic.com&quot;&gt;Claude Opus 4.6&lt;&#x2F;a&gt; as a pair-programming partner made tackling a rewrite of this scale much more manageable. Let&#x27;s walk through what&#x27;s new.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;new-architecture-three-crates-three-interfaces&quot;&gt;New Architecture: Three Crates, Three Interfaces&lt;&#x2F;h2&gt;
&lt;p&gt;The single Tauri application has been replaced with a clean &lt;strong&gt;3-crate workspace&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;proxelar-cli&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — the CLI binary, providing the entry point and interface layer&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;proxyapi&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — the core proxy engine with the &lt;code&gt;HttpHandler&lt;&#x2F;code&gt; trait, connection handling, and TLS interception&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;proxyapi_models&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — shared request&#x2F;response models and serialization&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This separation makes the proxy engine reusable as a library, independent of any specific frontend. On the interface side, Proxelar now ships with three modes you can switch between at launch:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;                 # interactive TUI (default)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i terminal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;     # plain terminal output&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -i gui&lt;&#x2F;span&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;          # web GUI at localhost:8081&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;strong&gt;TUI&lt;&#x2F;strong&gt; is built with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ratatui&#x2F;ratatui&quot;&gt;ratatui&lt;&#x2F;a&gt; and supports navigation, request&#x2F;response inspection, filtering, and keyboard shortcuts. The &lt;strong&gt;web GUI&lt;&#x2F;strong&gt; runs on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tokio-rs&#x2F;axum&quot;&gt;axum&lt;&#x2F;a&gt; with WebSocket streaming. All three interfaces are fed by a unified event pipeline — a bounded &lt;code&gt;mpsc&lt;&#x2F;code&gt; channel of &lt;code&gt;ProxyEvent&lt;&#x2F;code&gt; values — so they all see the same traffic in real time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hyper-1-x-and-modern-tls&quot;&gt;Hyper 1.x and Modern TLS&lt;&#x2F;h2&gt;
&lt;p&gt;The core proxy engine has been migrated from &lt;strong&gt;hyper 0.14 to hyper 1.x&lt;&#x2F;strong&gt;, along with &lt;strong&gt;rustls 0.23&lt;&#x2F;strong&gt; and &lt;strong&gt;tokio-rustls 0.26&lt;&#x2F;strong&gt;. This meant rewriting connection handling, body types (&lt;code&gt;ProxyBody&lt;&#x2F;code&gt;), and the entire TLS interception layer.&lt;&#x2F;p&gt;
&lt;p&gt;The new stack brings better performance, improved HTTP compliance, and alignment with the current Rust async ecosystem. Certificate generation now uses &lt;strong&gt;per-host caching&lt;&#x2F;strong&gt;, so repeated connections to the same host reuse previously minted certificates instead of generating new ones on every request.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;forward-and-reverse-proxy&quot;&gt;Forward and Reverse Proxy&lt;&#x2F;h2&gt;
&lt;p&gt;Proxelar 0.2.0 introduces proper &lt;strong&gt;forward proxy&lt;&#x2F;strong&gt; mode with CONNECT tunneling and HTTPS man-in-the-middle interception, as well as a new &lt;strong&gt;reverse proxy&lt;&#x2F;strong&gt; mode:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# Forward proxy (default) — configure your system proxy to 127.0.0.1:8080&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #616E88;&quot;&gt;# Reverse proxy — forward all traffic to an upstream target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; -m reverse --target http:&#x2F;&#x2F;localhost:3000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Forward mode handles both HTTP and HTTPS via CONNECT, transparently intercepting encrypted traffic by minting per-host certificates signed by the local CA. Reverse mode rewrites incoming requests to the specified upstream target — useful for debugging APIs and local services.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;easy-ca-certificate-installation&quot;&gt;Easy CA Certificate Installation&lt;&#x2F;h2&gt;
&lt;p&gt;Getting the CA certificate installed is often the most annoying part of setting up a MITM proxy. Proxelar now includes a built-in &lt;strong&gt;certificate download server&lt;&#x2F;strong&gt; that intercepts requests to the &lt;code&gt;proxel.ar&lt;&#x2F;code&gt; hostname. Just configure your browser to use the proxy and visit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http:&#x2F;&#x2F;proxel.ar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You&#x27;ll get a page with direct PEM and DER downloads and installation instructions for your platform. No more hunting for certificate files in hidden directories (though &lt;code&gt;~&#x2F;.proxelar&#x2F;proxelar-ca.pem&lt;&#x2F;code&gt; is still there if you prefer).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-and-ci-cd&quot;&gt;Testing and CI&#x2F;CD&lt;&#x2F;h2&gt;
&lt;p&gt;This release adds &lt;strong&gt;integration tests&lt;&#x2F;strong&gt; covering the certificate authority, the cert download server, forward proxy, reverse proxy, and model serialization. Previously, the project had minimal test coverage — now the critical paths are exercised automatically.&lt;&#x2F;p&gt;
&lt;p&gt;The CI&#x2F;CD pipeline has been overhauled as well:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cargo caching&lt;&#x2F;strong&gt; for faster builds&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MSRV 1.88 verification&lt;&#x2F;strong&gt; to ensure compatibility&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;cargo-deny&lt;&#x2F;strong&gt; policy checks for license and vulnerability auditing&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cross-platform release workflow&lt;&#x2F;strong&gt; producing binaries for multiple targets&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Dependabot&lt;&#x2F;strong&gt; for automated dependency updates&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;breaking-changes&quot;&gt;Breaking Changes&lt;&#x2F;h2&gt;
&lt;p&gt;This is a major release, and the public API has changed significantly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The binary is now &lt;code&gt;proxelar&lt;&#x2F;code&gt; (CLI)&lt;&#x2F;strong&gt; — the Tauri desktop app has been removed entirely&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;proxyapi&lt;&#x2F;code&gt; has been redesigned&lt;&#x2F;strong&gt; around the &lt;code&gt;HttpHandler&lt;&#x2F;code&gt; trait, &lt;code&gt;ProxyBody&lt;&#x2F;code&gt;, and &lt;code&gt;ProxyEvent&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;proxyapi_models&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; now uses http 1.x and http-serde 2.x, which changes the serialization format&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;License changed to MIT-only&lt;&#x2F;strong&gt; — the previous dual license has been simplified&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you were using &lt;code&gt;proxyapi&lt;&#x2F;code&gt; as a library, you&#x27;ll need to update your code to the new API.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;&#x2F;h2&gt;
&lt;p&gt;Install Proxelar with Cargo:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;cargo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; install proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then run it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;proxelar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Configure your system or browser proxy to &lt;code&gt;127.0.0.1:8080&lt;&#x2F;code&gt;, visit &lt;code&gt;http:&#x2F;&#x2F;proxel.ar&lt;&#x2F;code&gt; to install the CA certificate, and you&#x27;re ready to inspect traffic.&lt;&#x2F;p&gt;
&lt;p&gt;For all CLI options, check the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&quot;&gt;README&lt;&#x2F;a&gt; or run &lt;code&gt;proxelar --help&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;acknowledgments&quot;&gt;Acknowledgments&lt;&#x2F;h2&gt;
&lt;p&gt;Proxelar is heavily inspired by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mitmproxy.org&quot;&gt;mitmproxy&lt;&#x2F;a&gt;, a project I had the opportunity to contribute to. Working on mitmproxy gave me a deep understanding of how MITM proxies work and shaped many of the design decisions behind Proxelar. The full changelog is available on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emanuele-em&#x2F;proxelar&#x2F;releases&#x2F;tag&#x2F;v0.2.0&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
