<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Home on Josh&#39;s Toolbox</title>
    <link>https://joshstoolbox.com/</link>
    <description>Recent content on Josh&#39;s Toolbox</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sat, 25 Apr 2026 18:54:16 +0000</lastBuildDate><atom:link href="https://joshstoolbox.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Wormhole: An AI Journey in Designing a Nix Module for a Network Ingress Tunnel</title>
      <link>https://joshstoolbox.com/blog/wormhole_an_ai_journey/</link>
      <pubDate>Sat, 25 Apr 2026 18:54:16 +0000</pubDate>
      
      <guid>https://joshstoolbox.com/blog/wormhole_an_ai_journey/</guid><description>&lt;style&gt; table.scoreboard { margin: 0 auto 0 auto; text-align: center; border: 2px solid
var(--terminal-blue); border-collapse: collapse; background-color: table-layout: fixed;
width: 50%; } table.scoreboard th { width: calc(50%/2); border: 2px solid
var(--terminal-blue); background-color: var(--terminal-blue); } table.scoreboard td {
border: 2px solid var(--terminal-blue); } td.scorer { -webkit-text-stroke: 1px
var(--terminal-green); color: var(--terminal-black); font-weight: bold; } td.loser {
-webkit-text-stroke: 1px var(--terminal-red); color: var(--terminal-black); font-weight:
bold; }&lt;/style&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update April 30, 2026:&lt;/strong&gt; I have now created a repository with all of the code for
each section that you can clone via:
&lt;code&gt;git clone https://joshstoolbox.com/repos/wormhole.git&lt;/code&gt;. Each section&amp;rsquo;s corresponding
commit reuses the section&amp;rsquo;s title. If I make any updates, you can find them there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve been spending some time recently working on my home server. It runs &lt;a href=&#34;https://nixos.org/&#34;&gt;NixOS&lt;/a&gt; and is currently only exposed on my internal network. It runs
&lt;a href=&#34;https://pi-hole.net/&#34;&gt;PiHole&lt;/a&gt;, has a &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server&#34;&gt;git server&lt;/a&gt;, and let&amp;rsquo;s us
watch videos through &lt;a href=&#34;https://jellyfin.org/&#34;&gt;Jellyfin&lt;/a&gt;. But, I&amp;rsquo;m quickly reaching the
limitations of utility without it being available when I&amp;rsquo;m not home. I want to run
&lt;a href=&#34;https://radicale.org/v3.html&#34;&gt;calendar software&lt;/a&gt; and &lt;a href=&#34;https://github.com/9001/copyparty&#34;&gt;some sort of file hosting&lt;/a&gt;, but those are largely more useful when out and
about.&lt;/p&gt;
&lt;p&gt;I could host these services and run them without a domain name, memorizing my Internet
Protocol (IP) address to access it, but that has its problems. For starters, IP
addresses are not fun to memorize. For another, while my IP address is semi-static,
whenever we lose service, which is all to common, I get a new IP address, and if I&amp;rsquo;m not
at home, I don&amp;rsquo;t know that it&amp;rsquo;s cycled, much less what it is. But probably, the worst
part is securing communication with my services: without a domain name, it&amp;rsquo;s much harder
to &lt;a href=&#34;https://en.wikipedia.org/wiki/Public_key_certificate&#34;&gt;set up certificates&lt;/a&gt; to
establish secure communication.&lt;/p&gt;
&lt;p&gt;So, I need to use a domain name to expose my services and that requires publishing a
&lt;a href=&#34;https://en.wikipedia.org/wiki/List_of_DNS_record_types#A&#34;&gt;Domain Name Server (DNS) A record&lt;/a&gt; with my IP address. And that
means making it public which isn&amp;rsquo;t desirable. Sure, the IPv4 address space is small
enough that it&amp;rsquo;s basically all getting hammered all the time, but once you associate an
IP address with it, you&amp;rsquo;re advertising to the world that you have interesting stuff to
attack. Creating DNS records isn&amp;rsquo;t usually enough, but once you create the
aforementioned certificates, &lt;a href=&#34;https://en.wikipedia.org/wiki/Certificate_Transparency&#34;&gt;certificate transparency records&lt;/a&gt; let everyone know your URL
exists.&lt;/p&gt;
&lt;p&gt;And yes, there are ways to work around this: generate a wildcard certificate, publish
records and expose your services only on subdomains. DNS servers don&amp;rsquo;t usually make it
possible to reveal a list of the records they serve. But I am paranoid. And with an IP
address that is generally static, once it is exposed, I can&amp;rsquo;t easily rotate it. So,
instead, what I want is to run a machine on a separate IP address that I can destroy and
rotate with ease. And that server is the ingress server to my network. (Well, one of the
networks in my house; I&amp;rsquo;m only letting this reach a subset of my devices.) Instead of
records and traffic using my IP address, requests are made to it and it routes traffic
between my server and requesters. To make things even more interesting, I want to
encrypt traffic between my server and this tunnel server using &lt;a href=&#34;https://www.wireguard.com&#34;&gt;WireGguard&lt;/a&gt;. While protocols like Hypertext Transport Protocol Secure
(HTTPS) encrypt your traffic, there are often parts, usually small, that are still
unencrypted. Transport Layer Security (TLS), the security layer on top of HTTP to create
HTTPS, for example, does not encrypt the &lt;a href=&#34;https://en.wikipedia.org/wiki/Server_Name_Indication&#34;&gt;Server Name Identification&lt;/a&gt;. In addition, many protocols don&amp;rsquo;t
hide what kind of traffic the underlying protocol is (e.g., Hypertext Transport Protocol
(HTTP)). For no-profit and little gain, I want to hide why these machines are talking to
each other, and that&amp;rsquo;s where configuring a WireGuard tunnel comes in.&lt;/p&gt;
&lt;p&gt;That is where our journey starts. At first, I just was talking to Claude (Sonnet 4.6) to
understand some &lt;a href=&#34;https://search.nixos.org/options?query=wireguard&#34;&gt;WireGuard nix options&lt;/a&gt;, but decided to explore deeper and
deeper till Claude was writing the whole thing. As we explore every iteration of the
journey, I&amp;rsquo;m gonna keep a scoreboard, to keep track of who I felt was most important to
each iteration: Claude or me. Is this scientific, no. But, whatever. And, I&amp;rsquo;ll give
Claude a well deserved point because it is the one writing all this code.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;1&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-1-initial-draft&#34;&gt;Iteration 1: Initial Draft&lt;/h2&gt;
&lt;p&gt;My initial plan had been to run an &lt;a href=&#34;https://nginx.org/en/&#34;&gt;nginx&lt;/a&gt; proxy on the instance
and naively route all traffic to my home server using &lt;a href=&#34;https://nginx.org/en/docs/stream/ngx_stream_core_module.html&#34;&gt;stream directives&lt;/a&gt;. These would receive all
all Transmission Control Protocol (TCP) and User Datagram Protocol (UDP) traffic then
send it on to my home server. Then once my server responded to the Nginx server, the
responses would be sent on back to the correct clients.&lt;/p&gt;
&lt;p&gt;This is actually a pretty common setup for web servers: it let&amp;rsquo;s you host multiple
different web pages on the same domain. It also let&amp;rsquo;s your various web services just
worry about generating the responses and letting Nginx handle things like TLS. Nginx can
terminate the TLS connection and speak just simple HTTP to your service.&lt;/p&gt;
&lt;p&gt;But, Claude had a different idea when I proposed this: what if we did all the routing in
the Linux kernel using &lt;a href=&#34;https://en.wikipedia.org/wiki/Nftables&#34;&gt;&lt;code&gt;nftables&lt;/code&gt;&lt;/a&gt;. (Well, it
initially recommended &lt;a href=&#34;https://en.wikipedia.org/wiki/Iptables&#34;&gt;&lt;code&gt;iptables&lt;/code&gt;&lt;/a&gt;, but
&lt;code&gt;nftables&lt;/code&gt; is the more modern replacement.) Keeping all the traffic in the kernel would
make it more performant. Nginx is a user mode application, so we end up crossing the
user and kernel mode boundaries, reducing performance. But, perhaps, worse, Nginx
actually terminates the connection and does direct negotiation with clients, which is
its own overhead. The kernel can just modify the packets in-place then send them on
their way.&lt;/p&gt;
&lt;p&gt;Point Claude.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;2&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-2-review&#34;&gt;Iteration 2: Review&lt;/h2&gt;
&lt;p&gt;I was pretty deep into my context window now. The same session that generated the code
was the session I had had long conversations about design trade-offs, the impact of the
limitations of underlying network technologies, and replacing iptables calls with
nftable ones. In order to avoid context rot permeating the design, I took the code from
iteration 1 over to a new session and asked it: &amp;ldquo;What do you think this Nix module does,
and what errors do you see in it?&amp;rdquo; (Is that comma important? ¯\_(ツ)_/¯)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When your eyes start to bleed, skip to &amp;ldquo;That was a lot.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before we get into the proposed corrections, a quick aside to talk about the limitations
of this design that Nginx doesn&amp;rsquo;t have to handle. Because Nginx terminates the traffic,
talking to our server is simply a separate network connection. But, when we rewrite
packets we need to take into account the network connection between the client and us
and us and the target server. Individual TCP and UDP packets are limited by the
configuration of the underlying point-to-point connection protocols on the Internet
(e.g., messages between two directly connected machines). For Ethernet, this usually
limits packets to 1500 bytes. This wouldn&amp;rsquo;t really be much of an issue if we were just
sending out packets un-modified to the target server. But, we&amp;rsquo;re sending these messages
over a VPN, and VPNs encapsulate messages which adds overhead. With WireGuard, the
overhead is 80 bytes for IPv4 and so the maximum transmission unit size (MTU) is
configured to be 1420 bytes. We need to tell clients this. For TCP, this is
straightforward. During connection negotiation, we can tell the client what the maximum
size the packets they can send are through the maximum segment size field (MSS). This is
basically the MTU minus the IP and TCP headers. It&amp;rsquo;s smaller for IPv6 because IPv6
addresses are longer, and we need to take that into account for both connections, the
client and the VPN. UDP, doesn&amp;rsquo;t have this negotiation, but if the packet is not allowed
to be fragmented (as indicated in its headers), when the kernel tries to send it to the
VPN, it will fail and send an Internet Control Message Protocol (ICMP) packet back to
the client telling them about the fragmentation needs. It&amp;rsquo;s then up to the caller to
figure out what to do. TCP would do something similar, but because TCP is a negotiated
connection, we&amp;rsquo;ll get the data. UDP is a fire and forget and so client software needs to
make their own choices; the network protocol won&amp;rsquo;t automatically ensure that we get that
information.&lt;/p&gt;
&lt;p&gt;That was a lot. But the point is, I identified this limitation in the initial session
and Claude did not. So point me:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;p&gt;Anyway, back to my new session. It identified issues in calculating the MSS, some
limitations on network interface naming in Linux, and a few hard-coded values. I let it
fix them and so Claude, too, gets another point:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-3-a-simple-rename&#34;&gt;Iteration 3: A Simple Rename&lt;/h2&gt;
&lt;p&gt;I had already decided that wormhole was going to be the name of this tool, and many of
the named configurations of subsystems used wormhole as a prefix to disambiguate them.
But, network interfaces are limited to 20 characters on Linux and that really reduced
the namespace for trailing identifiers if one were to configure multiple wormhole
services. Claude was the one who raised it, but I was the one who identified it as an
issue and instructed Claude to use &lt;code&gt;wh-&lt;/code&gt; as a prefix instead. So this one, is my point:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;2&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-4-an-api-change&#34;&gt;Iteration 4: An API Change&lt;/h2&gt;
&lt;p&gt;At this point, looking at the code was making my eyes bleed. I had originally designed
an API I liked, but it ended up requiring all sorts of string mangling to make work. So,
I asked Claude to take its own stab at it and really following Nix best practices. I
felt it did a pretty stellar job here in the rewrite and opened doors to improve type
checking. So, a well-deserved point to Claude.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-5-using-wireguard-magic&#34;&gt;Iteration 5: Using WireGuard Magic&lt;/h2&gt;
&lt;p&gt;One of the neat things about WireGuard is that both sides don&amp;rsquo;t actually need to know
that the other exists to form a connection. Instead, as long as one end is setup to
maintain a persistent connection, it will reach out and establish it. In my case, with a
potentially changing IP address for my home server, this is a huge boon. For configuring
the tunnel service, this means that we aren&amp;rsquo;t guaranteed to know at configuration time
if the connection between the tunnel and my home server will be IPv4 or IPv6. So, to get
my point for this iteration, I instructed Claude to use reasonable defaults and expose
configuration options to allow specifying a network type even if you didn&amp;rsquo;t know the
target IP address in question.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;3&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-6-comments&#34;&gt;Iteration 6: Comments&lt;/h2&gt;
&lt;p&gt;Given that I didn&amp;rsquo;t write this module, I decided that I needed Claude to write more
comments. So, for its hard work:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;5&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-7-a-hole-in-the-strategy&#34;&gt;Iteration 7: A Hole in the Strategy&lt;/h2&gt;
&lt;p&gt;Back in iteration 5, when I had requested that we expose IP version configuration, I
also had us error when the WireGuard IP was configured and it disagreed with the
configured version. But, I hadn&amp;rsquo;t considered the case of chained tunneling, and so here
I reduced the erroring space to take that into account. I wouldn&amp;rsquo;t give myself a point,
but Claude didn&amp;rsquo;t raise this either. Also, I&amp;rsquo;m the scorekeeper, so take that Claude:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;4&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-8-code-review-part-2-electric-boogaloo&#34;&gt;Iteration 8: Code Review Part 2: Electric Boogaloo&lt;/h2&gt;
&lt;p&gt;At this point, it was time to get a second set of virtual eyes on this thing, so I took
the same approach I took with the initial pass and asked a fresh Claude session for
insight. But, instead of letting that session correct it, I shared feedback with the
session that was making all these changes so as to not lose the context of the design
choices. For their tag team work, the Claudes earned another point:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;6&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-9-to-mtu-or-not-to-mtu&#34;&gt;Iteration 9: To MTU or Not to MTU&lt;/h2&gt;
&lt;p&gt;At this point, I was again thinking about packet sizes. I realized that if there was a
smaller than expected MTU between the tunnel server and my home server, the client would
never find out about it. An ICMP packet would be generated to tell the tunnel to reduce
packet sizes, but it had no means by which to send that back to the client. Or does it?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty sure that &lt;a href=&#34;https://man.archlinux.org/man/conntrack.8.en&#34;&gt;conntrack&lt;/a&gt;, the
kernel&amp;rsquo;s network connection tracking mechanism, will successfully send the ICMP packet
to the client. ICMP is used to report connection errors and diagnose connectivity
between machines. While ICMP is a separate protocol at the same level as TCP and UDP and
has a different structure, ICMP packets include as a part of their payload the IP header
and at least the first four bytes of the transport layer header which is where TCP/UDP
specify the source and destination ports. And that is enough to associate with the
connection. But, at this point, this didn&amp;rsquo;t click in my brain, and so I asked Claude to
provide more knobs to manually reduce packet sizes.&lt;/p&gt;
&lt;p&gt;As a result of my failure to truly investigate this, I award no points. And I slap
myself with a yellow card.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh
🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-10-hole-punching-or-connectionful-udp&#34;&gt;Iteration 10: Hole Punching or Connectionful UDP&lt;/h2&gt;
&lt;p&gt;While UDP traffic is connectionless, that doesn&amp;rsquo;t mean both ends don&amp;rsquo;t end up sending
traffic to each other. In fact, WireGuard works over UDP. When routers see TCP packets,
they can track the lifetime of the connection and ensure traffic to a given port from a
given IP continues to go to the right machine. This is not the case for UDP. But, they
do still do something similar: they simply keep that mapping alive for a brief period of
time, on Linux by default for 30 seconds. I mentioned earlier that only one of two
systems needs to know how to reach the other one, and it&amp;rsquo;s UDP hole punching that allows
this. When the system in the know reaches out to the other one, it is able to talk back
reusing the port that the smarter system reached out form.&lt;/p&gt;
&lt;p&gt;I asked Claude to provide a knob to adjust that window to compensate for services on
either side of the connection. My idea, my point:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;5&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-11-what-if-i-have-a-lot-of-friends&#34;&gt;Iteration 11: What If I Have a Lot of Friends?&lt;/h2&gt;
&lt;p&gt;There is one major limitation that you run into when designing a low overhead ingress
server. I briefly mentioned ports earlier, but let&amp;rsquo;s talk about them in a bit more
detail. A connection between two machines is defined by four pieces of information: the
IPs of the two machines and the ports each is using. Ports are just a number, a 16-bit
number to be precise. This let&amp;rsquo;s there be up to 65535 connections between one machine
and a service tied to a single port (like a web server) on another. By default, it&amp;rsquo;s
actually usually around 28000 as access to some of these ports is usually limited, but
we&amp;rsquo;ve put ourselves in an even more limited situation. In our case, no matter who
connects to us, the packet gets translated into a network where everyone shares an IP
address. So now, we can only handle 28000 connections per target port. One way to
partially overcome this limitation is to provision more IPs on the WireGuard network, so
I asked Claude to provide exactly that functionality. For my world-breaking geniusness,
I get one whole point:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;6&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-12-do-you-know-what-you-are-doing-claude&#34;&gt;Iteration 12: Do You Know What You Are Doing Claude?&lt;/h2&gt;
&lt;p&gt;While making the previous change, Claude made an unrelated change that actually would&amp;rsquo;ve
broken things. And with that maneuver, Claude hands me the lead.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;7&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-13-do-i-know-what-i-am-doing&#34;&gt;Iteration 13: Do I Know What I Am Doing?&lt;/h2&gt;
&lt;p&gt;I realized that like the UDP knobs, we&amp;rsquo;d want similar knobs to turn for TCP. When a UDP
connection is opened, &lt;code&gt;conntrack&lt;/code&gt; by default tracks that connection for 30 seconds. When
a TCP connection is opened, &lt;code&gt;conntrack&lt;/code&gt; tracks it till it closes, or if never sees that,
for 5 DAYS. Want to make the home server unreachable through the tunnel? Simply open a
bunch of TCP connections without closing them. There are some applications that might
need this, but having a knob to reduce this can make attacks harder.&lt;/p&gt;
&lt;p&gt;But, these knobs we&amp;rsquo;re turning are system-wide knobs. These are kernel parameters and
doing these things silently behind the scenes is going to get real confusing and
surprise systems owners when you turn tunnels off and on. So, instead, I directed Claude
to document these knobs and stop exposing my own weird interface for them. As a result,
I&amp;rsquo;m afraid I will have to remove a point from myself for such a silly oversight. What a
short-lived lead:&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;loser&#34;&gt;6&lt;/td&gt;&lt;td class=&#34;scorer&#34;&gt;7&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-14-are-we-there-yet&#34;&gt;Iteration 14: Are We There Yet?&lt;/h2&gt;
&lt;p&gt;I promise we&amp;rsquo;re almost out of iterations. This iteration was a simple request from me to
further expand the module&amp;rsquo;s documentation on kernel parameter configuration.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td
class=&#34;scorer&#34;&gt;8&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-15-connecting-to-the-wireguard-network&#34;&gt;Iteration 15: Connecting to the WireGuard Network&lt;/h2&gt;
&lt;p&gt;While only one end of a WireGuard connection needs to know the IP address of the other,
they both need to be configured with the other&amp;rsquo;s public WireGuard keys. My eventual plan
was to expose this through a simple web server (a whole other can of worms that I hope
to talk about in the future), and the configuration for that server would need to know
where to find the public key our module creates is, so I instructed Claude to expose
that in the Nix module&amp;rsquo;s configuration.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s nice to move my points in a more positive direction again.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;7&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;iteration-16--17-so-youre-out-of-connections&#34;&gt;Iteration 16 &amp;amp; 17: So You&amp;rsquo;re Out of Connections&lt;/h2&gt;
&lt;p&gt;In iteration 12 we provided the means to reduce the chance of connection exhaustion, but
that didn&amp;rsquo;t make it impossible. So what happens when connections are exhausted? The
kernel quietly releases them to the great big bit bucket in the sky. So, I asked Claude
to ask some documentation to help system maintainers detect if this is happening using
the &lt;code&gt;conntrack&lt;/code&gt; utility.&lt;/p&gt;
&lt;p&gt;After that, I realized we had another knob to turn to help with connection exhaustion.
Earlier I mentioned that we usually have fewer ports to work with than our available. So
after the previous documentation update, I instructed Claude to write some docs on that
knob.&lt;/p&gt;
&lt;table class=&#34;scoreboard&#34;&gt;&lt;tr&gt;&lt;th&gt;Josh 🟨&lt;/th&gt;&lt;th&gt;Claude&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td
class=&#34;scorer&#34;&gt;8&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;h2 id=&#34;where-does-that-leave-us&#34;&gt;Where Does That Leave Us?&lt;/h2&gt;
&lt;p&gt;And that brings us to the &lt;a href=&#34;https://joshstoolbox.com/tools/wormhole/&#34;&gt;final version&lt;/a&gt;. I have not reviewed it, or
even tested it. I figure that should take me a few hours, but at the time of writing
this blog post, I haven&amp;rsquo;t invested it. DO NOT USE IT. (Besides, you don&amp;rsquo;t even know my
super special and awesome way I plan to expose WireGuard credentials.)&lt;/p&gt;
&lt;p&gt;But, this post isn&amp;rsquo;t about the tool we created, at least not solely. It&amp;rsquo;s about my most
recent experience with Claude code.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I should note, Sonnet 4.6 may have not been the best model for this, so if you believe
that to be the case, you may disagree with some of this feedback.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This isn&amp;rsquo;t the first project I&amp;rsquo;ve done with Claude handling the bulk of the work. Late
last year, I wrote a simple Python server that translated events from a third party into
Prometheus metrics that I could query with &lt;a href=&#34;https://github.com/grafana/grafana&#34;&gt;Grafana&lt;/a&gt;. I don&amp;rsquo;t exactly recall which model I was using, but
I do remember using their best coding model at the time. I was a bit disappointed with
its architectural chops: the initial design was untestable and specifically designed for
the task at hand; adjusting it to a better architecture required a bit of teeth pulling.&lt;/p&gt;
&lt;p&gt;Here, I still saw echoes of that, but I must say it was much better at designing the
module&amp;rsquo;s interface. I was very surprised at the cleanliness of the interface and the
implementation. That said, I would&amp;rsquo;ve loved to see some more pushback on my design
choices, like when I proposed exposing system-wide knobs as a part of the module.&lt;/p&gt;
&lt;p&gt;And it&amp;rsquo;s still very prone to errors. While I know there&amp;rsquo;s a lot of work to make these
models more and more accurate, we can&amp;rsquo;t forget that they are statistical approximations
of conversation, not knowledge, and the very conversation your having can affect the
accuracy of the models. In iteration 9, it was the one who proposed that ICMP packets
could not properly be routed, and it took some sleep on my part to realize that would be
a rather notable failing of the nftables feature (masquerade) that we were using.&lt;/p&gt;
&lt;p&gt;I truly didn&amp;rsquo;t expect our scores to be tied when I tallied it up writing this post, but
in truth the values and their equivalency mean nothing. The non-zeroness of either
really tells the story. I was able to move much more rapidly with Claude and it raised
ideas I hadn&amp;rsquo;t considered. But, as mentioned, it is still prone to errors; it still has
blindspots. And it needed my expertise to avoid those both.&lt;/p&gt;
&lt;p&gt;Recently, when using AI, I&amp;rsquo;ve felt like I can really feel my skills atrophy. But in this
instance, I was surprised to feel like I was learning. I think the key here is that I
was an active participant where I think historically I&amp;rsquo;ve been using Claude to do things
I could do, I just didn&amp;rsquo;t feel like doing.&lt;/p&gt;
&lt;p&gt;And I can&amp;rsquo;t say that I&amp;rsquo;ll stop that use case. AI is addicting. It&amp;rsquo;s a quick fix and like
many quick fixes, there are long term trade-offs. But, I think that in an actively
engaged partnership, with enough expertise in the driver, AI can be an effective tool
for learning and growing.&lt;/p&gt;
&lt;p&gt;As for the efficacy of this project overall,I still have to test this. I still don&amp;rsquo;t
trust the output of AI tools enough to throw caution into the wind. And, maybe, I&amp;rsquo;ll
discover I&amp;rsquo;m too optimistic, or maybe too pessimistic.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>netcat: Making a very simple web server</title>
      <link>https://joshstoolbox.com/blog/netcat_intro/</link>
      <pubDate>Sat, 04 Apr 2026 04:41:56 +0000</pubDate>
      
      <guid>https://joshstoolbox.com/blog/netcat_intro/</guid><description>&lt;p&gt;If you want to share data across computers, it can be surprisingly cumbersome. If both
system are in the same ecosystem, like Apple&amp;rsquo;s, Google&amp;rsquo;s, or Dropbox&amp;rsquo;s, it can be more
straightforward. If you&amp;rsquo;re on the same network, you can use python&amp;rsquo;s &lt;code&gt;http.server&lt;/code&gt;
module to share a whole folder of files.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; python -m http.server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And there are tools like &lt;a href=&#34;https://github.com/9001/copyparty&#34;&gt;copy-party&lt;/a&gt; (which I&amp;rsquo;ve
been meaning to mess with). And of course &lt;a href=&#34;https://pastebin.com/&#34;&gt;pastebin&lt;/a&gt;. But all of
these require some amount of setup and/or turning over your data to a third party.
Python&amp;rsquo;s may be the simplest, but it does require turning your data into a file (and
installing python).&lt;/p&gt;
&lt;p&gt;But there is a tool that&amp;rsquo;s designed to just serve and receive arbitrary data across UDP
or TCP connections and may already be on your system: &lt;code&gt;netcat&lt;/code&gt;. Let&amp;rsquo;s take a look at a
simple transfer of information across based off the example from &lt;code&gt;man 1 nc&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; nc -N -d localhost &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Hello world!
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;1&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;+  Done                    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;That trailing line is bash announcing that the background process has completed,
successfully in this case given by the &lt;code&gt;Done&lt;/code&gt; string. If it had failed, it would have
instead said &lt;code&gt;Exit n&lt;/code&gt; where &lt;code&gt;n&lt;/code&gt; is the exit code the process finished with. I won&amp;rsquo;t be
showing it in future code snippets.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s breakdown what just happened.&lt;/p&gt;
&lt;p&gt;Our first invocation sets up the listening server. We pass the string &lt;code&gt;Hello world!&lt;/code&gt; to
it via a pip (&lt;code&gt;|&lt;/code&gt;) to &lt;code&gt;netcat&lt;/code&gt; (&lt;code&gt;nc&lt;/code&gt;) which has been configured to listen (&lt;code&gt;-l&lt;/code&gt;) on port
&lt;code&gt;8080&lt;/code&gt;. The &lt;code&gt;&amp;amp;&lt;/code&gt; tells &lt;code&gt;bash&lt;/code&gt; to run it as a background job so we can do other things
with the current shell.&lt;/p&gt;
&lt;p&gt;The second invocation makes a request for our TCP message. &lt;code&gt;-N&lt;/code&gt; tells &lt;code&gt;netcat&lt;/code&gt; to
shutdown once it sees the end of a file and the &lt;code&gt;-d&lt;/code&gt; tells it that there will be nothing
piped to it, so that implicitly happens as soon as it receives data from the server. The
&lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;8080&lt;/code&gt; tell it how to reach the server we just setup.&lt;/p&gt;
&lt;p&gt;And boom, the message is written to STDOUT of the listening process.&lt;/p&gt;
&lt;p&gt;But, this still requires &lt;code&gt;netcat&lt;/code&gt; on both ends. When I was setting this up, I wanted to
get arbitrary data to any of my devices on my local networks, and one of those is my
phone. I don&amp;rsquo;t have a terminal on my phone and my fiancee runs iOS, so even if I did, if
want to share things with her, this solutions wouldn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;But, what all of our devices have is a way to understand &lt;code&gt;HTTP&lt;/code&gt; message, our browsers.
And &lt;code&gt;HTTP&lt;/code&gt; is just specifically formatted text sent over TCP. On Linux, we also have
&lt;code&gt;curl&lt;/code&gt;, which will show &lt;code&gt;HTTP&lt;/code&gt; responses directly in our terminal. So, let&amp;rsquo;s switch our
receiver to the proper invocation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; curl localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GET / HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host: localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User-Agent: curl/8.5.0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept: */*
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl: &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; Received HTTP/0.9 when not allowed
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Like I mention above, HTTP requires a specific format, and &lt;code&gt;netcat&lt;/code&gt; has actually shown
us a bit of what that looks like, at least on the requesting end. It indicates the
request type &lt;code&gt;GET&lt;/code&gt;, that path being requested &lt;code&gt;/&lt;/code&gt;, and the HTTP version the request is
using &lt;code&gt;HTTP/1.1&lt;/code&gt;. The what follows are some header fields that can tell the server more
about the request to help it handle things. An intelligent server will use that
information to respond appropriately, but we can target the largely lowest common
denominator and ignore all of that.&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s serve a properly formatted HTTP message with &lt;code&gt;netcat&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; &lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nHello world!\n&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; curl localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GET / HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host: localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User-Agent: curl/8.5.0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept: */*
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Hello world!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Huzzah! We got our message.&lt;/p&gt;
&lt;p&gt;Now, this looks a little weird. We&amp;rsquo;re &lt;a href=&#34;../echo_modifies_text&#34;&gt;using &lt;code&gt;printf&lt;/code&gt;&lt;/a&gt; because
HTTP expects lines in the frontmatter to be separated by a carriage return (&lt;code&gt;\r&lt;/code&gt;)
followed by a new line (&lt;code&gt;\n&lt;/code&gt;). Here are the lines we&amp;rsquo;ve hard-coded:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Here we establish the HTTP version of our response (&lt;code&gt;HTTP/1.1&lt;/code&gt;), the return code (
&lt;code&gt;200&lt;/code&gt;) and a descriptive string of that code (&lt;code&gt;OK&lt;/code&gt;). The last part is optional, but
may be expected by your receiving software.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Type: text/plain&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;In addition to our HTTP version, the plaintext content type can reasonably be
expected to be supported by any HTTP supporting software.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Length: 13&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This tells the receiver how much data it should expect after it receives the end of
the header. In our case, that is 13 characters (&lt;code&gt;Hello world!&lt;/code&gt; and our newline).
We&amp;rsquo;ve calculate it by hand, but shortly we&amp;rsquo;ll resolve that.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\r\n\r\n&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The end of the header is indicated by two sets of a carriage return and new line.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Hello world!\n&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;And here is our payload, all 13 characters.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s resolve some of the issues here. First, so far we&amp;rsquo;ve been hard-coding our string
and the length. So let&amp;rsquo;s handle that by putting this all in it&amp;rsquo;s own &lt;code&gt;bash&lt;/code&gt; script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;cat&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;contentLength&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;%s&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$content&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; wc -c&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;contentLength&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we call this &lt;code&gt;simple-share.sh&lt;/code&gt; and mark it executable ( &lt;code&gt;chmod u+x ./simple-share.sh&lt;/code&gt;
), we can call it like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Hello world!&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; ./simple-share.sh &amp;gt;/dev/null &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; curl localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Hello world!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;&amp;gt;/dev/null&lt;/code&gt; means we won&amp;rsquo;t be seeing the request information in our output
anymore.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Huzzah? There&amp;rsquo;s actually a caveat here, the trailing new-line is gone. Bash has actually
dropped those when we assigned data to variables. Worse, if we ever included a null-byte
(&lt;code&gt;\0&lt;/code&gt;) as would 100% be possible and expected with arbitrary data, it&amp;rsquo;ll drop those too.
So, we need to get a bit smarter and use an intermediate file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;contentPath&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;mktemp&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cleanup&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  rm --force &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$contentPath&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;trap&lt;/span&gt; cleanup EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat &amp;gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$contentPath&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;contentLength&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;wc -c &amp;lt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$contentPath&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;contentLength&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  cat &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$contentPath&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whew. Almost makes you rethink how complicated the other solutions are.&lt;/p&gt;
&lt;p&gt;But, run everything again, and we&amp;rsquo;ve gotten our new-line! And arbitrary files won&amp;rsquo;t get
messed up. Now, to get this to another machine, there&amp;rsquo;s a few more things you&amp;rsquo;ll need to
do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open your firewall on port &lt;code&gt;8080&lt;/code&gt;. If you don&amp;rsquo;t have a firewall, you don&amp;rsquo;t need to do
this, but you really should have a firewall. If you have &lt;code&gt;ufw&lt;/code&gt;, it&amp;rsquo;s as simple as:
&lt;code&gt;sudo ufw allow 8080/tcp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Get your IP address: &lt;code&gt;ip addr&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;ll be everything before the slash &lt;code&gt;/&lt;/code&gt; in the first field after &lt;code&gt;inet&lt;/code&gt; or &lt;code&gt;inet6&lt;/code&gt;
that is not your loopback interface (i.e., NOT &lt;code&gt;127.0.0.1&lt;/code&gt; or &lt;code&gt;::1&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, you can use that combined information to go to it in the browser on your target
device by filling in the address bar appropriately: &lt;code&gt;http://IP_ADDRESS:8080&lt;/code&gt;. This all
assumes you&amp;rsquo;re on the local network and transferring data across the Internet is WELL
outside the scope of this blog post.&lt;/p&gt;
&lt;p&gt;There are still some other deficiencies in this solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The port is hard-coded. This is left as an exercise to the reader and to future me who
will eventually start uploading tool into this so called toolbox.&lt;/li&gt;
&lt;li&gt;This is &lt;strong&gt;NOT SECURE&lt;/strong&gt;. This uses HTTP and anybody observing your network traffic can
see what is being sent and anybody controlling your network can change what you&amp;rsquo;re
sending. Using HTTPS requires an SSL server like the one provided by &lt;code&gt;openssl&lt;/code&gt; and is
probably best left as a future blog post.&lt;/li&gt;
&lt;li&gt;Raw files are gonna work weird. Firefox will prompt you to save it, it&amp;rsquo;ll even note
that we lied in our Content-type, but it won&amp;rsquo;t know what the file type is. A more
intelligent design would better hint at the file contents. Integrating something like
&lt;code&gt;file&lt;/code&gt; in the script could help with that.&lt;/li&gt;
&lt;li&gt;This doesn&amp;rsquo;t solve the phone to computer situation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those deficiencies aside, this solution can pretty easily be mapped to other
applications. Just sprinkle a little bit of extra HTTP knowledge, and you can do things
like make requests automatically go to the site of your choosing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; &lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;HTTP/1.1 307 Temporary Redirect\r\nLocation: %s\r\n\r\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://joshstoolbox.com&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; nc -N -l &lt;span class=&#34;m&#34;&gt;8080&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; curl -L localhost:8080
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which brings us to an important point. There&amp;rsquo;s a &lt;code&gt;-N&lt;/code&gt; in our &lt;code&gt;netcat&lt;/code&gt; server call now.
This is because we didn&amp;rsquo;t set the &lt;code&gt;Content-Length&lt;/code&gt; header. As a result, the receiving
HTTP client doesn&amp;rsquo;t know that the message is complete and it doesn&amp;rsquo;t know it should
terminate the TCP connection. Only the server knows that there is no more data to send
and must be the one to terminate the TCP session. If you added &lt;code&gt;Content-Length: 0&lt;/code&gt; to
the headers, you wouldn&amp;rsquo;t need the &lt;code&gt;-N&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;In fact, in our very first example, if we sent too much data, it would end up truncated
because the listener wouldn&amp;rsquo;t actually know when the data would stop being received and
would close the connection prematurely. If we switched which call has the &lt;code&gt;-N&lt;/code&gt;,
everything would be transferred. And though HTTP clients don&amp;rsquo;t have this problem when
headers are properly set, it&amp;rsquo;s still probably worth it to set it in our
&lt;code&gt;simple-share.sh&lt;/code&gt; script as well.&lt;/p&gt;
&lt;p&gt;Now you have the power to make really simple servers with &lt;code&gt;netcat&lt;/code&gt; and share arbitrary
data from your Linux machine to other devices on your local network. One final note:
after a single request, these server can&amp;rsquo;t share the information again. &lt;code&gt;netcat&lt;/code&gt; sends
data from STDIN and it has already consumed it all.&lt;/p&gt;
&lt;p&gt;I hope you&amp;rsquo;ve learned something, and I hope to share a more refined version of this
script and its siblings soon!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>echo Modifies Your Strings</title>
      <link>https://joshstoolbox.com/blog/echo_modifies_text/</link>
      <pubDate>Sun, 15 Mar 2026 18:38:09 +0000</pubDate>
      
      <guid>https://joshstoolbox.com/blog/echo_modifies_text/</guid><description>&lt;p&gt;The &lt;code&gt;echo&lt;/code&gt; command is a commonly used command for spitting out arbitrary text in your
shell.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; echo &amp;#34;Hello world!&amp;#34;
Hello world!
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sometimes you might even find advice to place some contents in a file using the echo
command and redirections:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; echo &amp;#34;Hello world!&amp;#34; &amp;gt; hello_world.txt
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But, you have to be careful with this, because what &lt;code&gt;echo&lt;/code&gt; actually spits out is the
specified string followed by a newline. We can see this by passing the output of the
command to &lt;code&gt;hd&lt;/code&gt; which displays the hexadecimal representation of a file, which here, by
default, is &lt;code&gt;/dev/stdin&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; echo &amp;#34;Hello world!&amp;#34; | hd
00000000  48 65 6c 6c 6f 20 77 6f  72 6c 64 21 0a           |Hello world!.|
0000000d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; on the right-hand side indicates a character that can&amp;rsquo;t be displayed, but we see
the &lt;code&gt;0a&lt;/code&gt; on the left which is the ASCII and UTF-8 code point for a new line.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Oct   Dec   Hex   Char                        Oct   Dec   Hex   Char
────────────────────────────────────────────────────────────────────────
007   7     07    BEL &amp;#39;\a&amp;#39; (bell)             107   71    47    G
010   8     08    BS  &amp;#39;\b&amp;#39; (backspace)        110   72    48    H
011   9     09    HT  &amp;#39;\t&amp;#39; (horizontal tab)   111   73    49    I
012   10    0A    LF  &amp;#39;\n&amp;#39; (new line)         112   74    4A    J
013   11    0B    VT  &amp;#39;\v&amp;#39; (vertical tab)     113   75    4B    K
014   12    0C    FF  &amp;#39;\f&amp;#39; (form feed)        114   76    4C    L
015   13    0D    CR  &amp;#39;\r&amp;#39; (carriage ret)     115   77    4D    M
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&#34;caption&#34;&gt;In this excerpt from the ASCII man page (&lt;code&gt;man 7 ascii&lt;/code&gt;),
you can see new line specified on the line starting with &lt;code&gt;012&lt;/code&gt;, which is
octal for 10, or &lt;code&gt;0a&lt;/code&gt; in hexadecimal.&lt;/p&gt;
&lt;p&gt;But, why is this such a big deal? Well, computers are very precise and if you were to,
say, drop in a password or an API token using this strategy into a file, you&amp;rsquo;ll find
that using the contents will likely give you access denied errors.&lt;/p&gt;
&lt;p&gt;There are a few ways to avoid this. The easy one is the &lt;code&gt;-n&lt;/code&gt; flag for echo that
explicitly tells &lt;code&gt;echo&lt;/code&gt; not to print out the new line.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; echo -n &amp;#34;Hello world!&amp;#34; | hd
00000000  48 65 6c 6c 6f 20 77 6f  72 6c 64 21              |Hello world!|
0000000c
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&#34;caption&#34;&gt;You might also note that the second line has changed. It&#39;s just
changed to reflect that the input is one byte smaller.&lt;/p&gt;
&lt;p&gt;You can also consider turning to &lt;code&gt;printf&lt;/code&gt;. &lt;code&gt;printf&lt;/code&gt; is designed to explicitly print out
what you specify, but that &lt;code&gt;f&lt;/code&gt; is really important as it stands for format. While,
replacing &lt;code&gt;echo&lt;/code&gt; with &lt;code&gt;printf&lt;/code&gt; works for this use case:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; printf &amp;#34;Hello world!&amp;#34; | hd
00000000  48 65 6c 6c 6f 20 77 6f  72 6c 64 21              |Hello world!|
0000000c
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;things get weird if your string include &lt;code&gt;%&lt;/code&gt; symbols:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; printf &amp;#34;Hello 50% of the world!&amp;#34;
Hello 500f the world!&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&#34;caption&#34;&gt;That trailing `&gt;` is actually our prompt. Because we didn&#39;t output a
new line, it showed up right after the text we wrote.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; printf &amp;#34;Hello 50% of the world!&amp;#34; | hd
00000000  48 65 6c 6c 6f 20 35 30  30 66 20 74 68 65 20 77  |Hello 500f the w|
00000010  6f 72 6c 64 21                                    |orld!|
00000015
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is because the &lt;code&gt;%&lt;/code&gt; indicates that we are going to add things to the format string.
This particular format string in particular is &lt;code&gt;% o&lt;/code&gt;, which says place the octal
representation of the passed in parameter here. We didn&amp;rsquo;t pass a parameter, and so it
ended up with the default value &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That space also holds value: it tells &lt;code&gt;printf&lt;/code&gt; to prepend the octal string with spaces
to fill the specified width, though we never passed a width, so there wasn&amp;rsquo;t any reason
to prepend anything. But, if we did, we&amp;rsquo;d have gotten a bunch of spaces:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; printf &amp;#34;Hello 50% 8of the world!&amp;#34; | hd
00000000  48 65 6c 6c 6f 20 35 30  20 20 20 20 20 20 20 30  |Hello 50       0|
00000010  66 20 74 68 65 20 77 6f  72 6c 64 21              |f the world!|
0000001c
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And you can do more, like right-aligning or prepending with zeroes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; printf &amp;#34;%-8o %08o\n&amp;#34; &amp;#34;21&amp;#34; &amp;#34;22&amp;#34;
25       00000026
&lt;/code&gt;&lt;/pre&gt;&lt;p class=&#34;caption&#34;&gt;Here we have multiple formatting fields and pass multiple arguments
to fill in each field. If we had passed in more than there were fields, `printf` would
reuse the format string until it had consumed all arguments.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Printf#Format_specifier&#34;&gt;Wikipedia actually has a pretty detailed section on format strings.&lt;/a&gt; In fact, this syntax is not just
specific to this tool, but also by the identically named &lt;a href=&#34;https://en.cppreference.com/w/c/io/fprintf&#34;&gt;C function&lt;/a&gt;, &lt;a href=&#34;https://docs.python.org/3/library/string.html#format-examples&#34;&gt;Python&amp;rsquo;s string formatting&lt;/a&gt;, and all sorts of other
places.&lt;/p&gt;
&lt;p&gt;Now, we&amp;rsquo;ve wandered a bit far from where we started of avoiding adding surprise new
lines to strings. But, hopefully this guide has given you some extra insight into
displaying strings that can help prime your exploration next time you need to get fancy.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; printf &amp;#34;% 10s %-10s\n&amp;#34; &amp;#34;abc&amp;#34; &amp;#34;efghi&amp;#34; &amp;#34;jklmno&amp;#34; &amp;#34;pqrstuv&amp;#34; &amp;#34;wxy&amp;#34; &amp;#34;z&amp;#34;
       abc efghi
    jklmno pqrstuv
       wxy z
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And, as some final tips, if you want to get even better at managing your secrets,
consider reading directly from your clipboard instead of typing the secret on the
command line:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Linux running X11
xclip -selection clipboard -o &amp;gt; secret.txt
# MacOS
pbpaste &amp;gt; secret.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And to go further, use a tool like &lt;a href=&#34;https://github.com/getsops/sops&#34;&gt;sops&lt;/a&gt; to keep your
secrets from being exposed while at rest.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;xclip -selection clipboard -o | sops -e /dev/stdin &amp;gt; secret.txt
&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    
  </channel>
</rss>

