<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Minty Blog</title><description>An archive of articles about software, software development, privacy, and cybersecurity.</description><link>https://blog.mintc2.me</link><item><title>A practical guide to Semantic Versioning in Go</title><link>https://blog.mintc2.me/posts/001</link><guid isPermaLink="true">https://blog.mintc2.me/posts/001</guid><description>A practical guide to Semantic Versioning in Golang and the infamous Major Version Suffix Rule.</description><pubDate>Tue, 12 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Why does this article exist?&lt;/h2&gt;
&lt;p&gt;One would think that semantic versioning is a simple enough concept that there would be nothing to talk about. &quot;Major, minor, patch,&quot; and we&apos;re done.&lt;/p&gt;
&lt;p&gt;But over the years of working with Go professionally and having the privilege of maintaining a couple of libraries, I&apos;ve seen so many peculiar things, knowledge gaps, and misuses that by now I&apos;m convinced that this topic has some depth to it and that it’s worth explaining in detail.&lt;/p&gt;
&lt;h2&gt;What is Semantic Versioning?&lt;/h2&gt;
&lt;p&gt;I will use a slightly modified definition from the &lt;a href=&quot;https://go.dev/doc/modules/version-numbers&quot;&gt;Go docs&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;It&apos;s a versioning scheme where a module’s developer uses each part of a module’s version number to signal the version’s stability and backward compatibility. For each new release, a module’s release version number specifically reflects the nature of the module’s changes since the preceding release.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;./semver.webp&quot; alt=&quot;Semantic versioning&quot; title=&quot;Semantic Versioning&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Version example&lt;/th&gt;
&lt;th&gt;What bumping the stage means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Major&lt;/td&gt;
&lt;td&gt;v&lt;strong&gt;1&lt;/strong&gt;.X.X&lt;/td&gt;
&lt;td&gt;Signals &lt;strong&gt;backward-incompatible public API changes&lt;/strong&gt;. The &quot;public API&quot; is not only code declarations but also expectations for how the library behaves. This release carries no guarantee that it will be backward compatible with preceding major versions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minor&lt;/td&gt;
&lt;td&gt;vX.&lt;strong&gt;1&lt;/strong&gt;.X&lt;/td&gt;
&lt;td&gt;Signals &lt;strong&gt;backward-compatible public API changes&lt;/strong&gt;. This release guarantees backward compatibility and stability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Patch&lt;/td&gt;
&lt;td&gt;vX.X.&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Signals &lt;strong&gt;changes that don&apos;t affect the module&apos;s public API&lt;/strong&gt; or its dependencies. This release guarantees backward compatibility and stability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-release&lt;/td&gt;
&lt;td&gt;vX.X.X-&lt;strong&gt;rc.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Signals that this is a &lt;strong&gt;pre-release milestone, such as an alpha, beta, or release candidate&lt;/strong&gt;. This release carries no stability guarantees.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;How to decide which stage to bump&lt;/h2&gt;
&lt;p&gt;First, you just have to ask yourself a question, &quot;If the clients were to pull this new version I&apos;m about to publish, would they need to modify anything in the code or adjust their assumptions about how the library functions to be able to continue using it?&quot; If the answer is &quot;yes, they need to adjust,&quot; that&apos;s a breaking change that warrants a new major version.&lt;/p&gt;
&lt;p&gt;Otherwise, it&apos;s a standoff between &quot;minor&quot; and &quot;patch&quot; versions. If you&apos;ve added something new, it&apos;s a minor version bump. If you didn&apos;t add anything new, it&apos;s a patch version bump. In rare cases where you can&apos;t decide between those two, just bump the minor version.&lt;/p&gt;
&lt;h2&gt;Special meaning behind v0.X.X&lt;/h2&gt;
&lt;p&gt;The major v0.X.X carries the special meaning of &quot;this is a development version; backward compatibility is not guaranteed.&quot; What that means in practice is that it throws the whole semantic versioning out the window and you can even expect to get a breaking change between v0.0.0 and v0.0.1.&lt;/p&gt;
&lt;p&gt;You probably shouldn&apos;t publish the library as v0 unless you specifically want to experiment with the API before you settle on something you can guarantee backward compatibility on going forward.&lt;/p&gt;
&lt;h2&gt;Major Version Suffix Rule (MVSR) and v2.X.X&lt;/h2&gt;
&lt;p&gt;Now let&apos;s take a look at the semantic lord of confusion, the dreaded major version 2. If you simply try to publish the tag v2.0.0 and then fetch it, you will get this infamous error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;go: github.com/mintc2/lib@v2.0.0: invalid version: go.mod has post-v2 module path &quot;github.com/mintc2/lib/v2&quot; at revision v2.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s because any module with major version v2.X.X or more has to append the suffix &lt;code&gt;/v{major_version}&lt;/code&gt; to the module name in the go.mod file and when importing it.&lt;/p&gt;
&lt;p&gt;It&apos;s confusing and rightfully so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This rule has many names and none of them are short.&lt;/li&gt;
&lt;li&gt;It only affects major versions v2.X.X and above (for example, v3.X.X and v4.X.X).&lt;/li&gt;
&lt;li&gt;It has a &quot;magical string&quot; vibe to it.&lt;/li&gt;
&lt;li&gt;Most importantly, it messes with a mental model many developers have that &quot;module name = Git repository path.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./mvsr_model.webp&quot; alt=&quot;New MVSR Mental Model&quot; title=&quot;New MVSR Mental Model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To make understanding easier, you can use this mental model instead. Imagine that v0.X.X and v1.X.X also have suffixes, but those suffixes are phantom ones. They are the OGs; they were there early, so they have the special privilege of being invisible, while all versions that come afterward do not get such special treatment.&lt;/p&gt;
&lt;h2&gt;The many benefits of MVSR&lt;/h2&gt;
&lt;p&gt;As confusing as it is, the MVSR has a formidable list of things that make it good.&lt;/p&gt;
&lt;p&gt;The first benefit is that the process of appending a suffix to the import path when fetching a new version and importing it into code makes accidental major version upgrades all but impossible. It has to be a conscious decision on the part of the library consumer.&lt;/p&gt;
&lt;p&gt;The second thing is that you can have as many different major versions imported at the same time. At first glance, it doesn&apos;t seem like it would have any practical use, but it does. For example, if you have a Kafka wrapper library that defines publishers and consumers, you may decide that you only need to upgrade to the v4.X.X consumer while keeping the publisher on v3.X.X. This kind of flexibility is very valuable when you have deadlines to meet.&lt;/p&gt;
&lt;p&gt;And the third thing is that the development of different major versions may continue in parallel, which, in combination with the point above, grants the developers even more flexibility.&lt;/p&gt;
&lt;h2&gt;The zero-major dodge&lt;/h2&gt;
&lt;p&gt;Not everyone likes the MVSR. Some dislike it so much they resort to an unofficial trick—I’ll call it the “zero-major dodge.”&lt;/p&gt;
&lt;p&gt;It works like this: the versioning scheme v&lt;strong&gt;MAJOR&lt;/strong&gt;.&lt;strong&gt;MINOR&lt;/strong&gt;.&lt;strong&gt;PATCH&lt;/strong&gt; is shifted to v0.&lt;strong&gt;MAJOR&lt;/strong&gt;.&lt;strong&gt;PATCH&lt;/strong&gt;, sidestepping the need to append a &lt;strong&gt;/vN&lt;/strong&gt; suffix to the module path.&lt;/p&gt;
&lt;p&gt;And it’s a bad idea: it violates Go’s conventions, misleads users about stability, and can silently break consumers who don’t get the clear migration signal a proper &lt;strong&gt;/vN&lt;/strong&gt; path provides.&lt;/p&gt;
&lt;p&gt;So don’t use this trick, it isn’t worth it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this article has convinced you that following semantic versioning is a pretty good idea and given you the knowledge to do so.&lt;/p&gt;
</content:encoded><author>mintc2</author></item><item><title>Tactically denying the internet access to the built-in MacOS services</title><link>https://blog.mintc2.me/posts/002</link><guid isPermaLink="true">https://blog.mintc2.me/posts/002</guid><description>I used an AI to analyze which built-in MacOS services can be denied the internet access without breaking anything important. Now you can use this data to figure out your perfect blocklist too.</description><pubDate>Sun, 07 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The Background&lt;/h2&gt;
&lt;p&gt;One day I woke up and decided it was time to turn LittleSnitch loose on the built-in Apple services. If a service isn&apos;t required for the system to work properly, then at best it&apos;s a waste of bandwidth. At worst, well, that&apos;s anyone&apos;s guess.&lt;/p&gt;
&lt;p&gt;But I didn&apos;t just want to block everything. I still wanted updates, the ability to verify signatures, and to keep my clock synced. Blocking anything related to the Apple ecosystem, though, felt like fair game to me.&lt;/p&gt;
&lt;p&gt;So I sat down and started browsing, looking to see if anyone had already done the research on what various macOS services do and compiled a blocklist. Alas, the only worthwhile thing I found was &lt;a href=&quot;https://inteltechniques.com/blog/2021/08/03/minimizing-macos-telemetry&quot;&gt;this article on IntelTechniques from 2021&lt;/a&gt;. The author went nuclear, blocking almost everything. That would definitely break things I wanted to keep working.&lt;/p&gt;
&lt;p&gt;That left me in a tough spot. Either I&apos;d have to experiment heavily to figure out which services were safe to block, which would take a long, long time. Or I&apos;d need to scour the internet for scraps of information on dozens of services, which wouldn&apos;t be swift either.&lt;/p&gt;
&lt;p&gt;Then it hit me: AI could do the research for me and estimate the consequences of blocking each service. Even though the list wouldn&apos;t be perfect, it would let me cull the majority of useless services while giving me a solid starting point to test the rest.&lt;/p&gt;
&lt;h2&gt;The Crawler&lt;/h2&gt;
&lt;p&gt;I extracted the paths and descriptions (where available) of the built-in services LittleSnitch recognized and made a nicely formatted file out of that. I decided to use TOML, since it&apos;s a structured, well-known format. I figured the AI would be more attentive to it, potentially have built-in tools to work with it, and be less likely to drop entries. TOML was also the most readable option I could think of, so I went with it.&lt;/p&gt;
&lt;p&gt;Then I gave that file to GPT-5, crafted a precise prompt describing what I wanted, and sent it crawling away in deep search mode. About 40 minutes later, it came back with the completed list. I had told it to fill in missing service descriptions, figure out any potential consequences of blocking internet access, and give a certainty score for each conclusion.&lt;/p&gt;
&lt;p&gt;You can find this file &lt;a href=&quot;https://gist.github.com/mintc2/42d6699b22d8b4bfd75215d64ff29a61&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Time To Block&lt;/h2&gt;
&lt;p&gt;After reading the analysis and experimenting, I settled on the following whitelist.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;mDNSResponder&lt;/td&gt;
&lt;td&gt;Blocking it may lead to DNS resolution issues.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;syspolicyd&lt;/td&gt;
&lt;td&gt;Blocking it could disrupt Gatekeeper’s ability to verify apps, potentially preventing some applications or plugins from launching.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;swtransparencyd&lt;/td&gt;
&lt;td&gt;Blocking it disrupts Gatekeeper/notarization checks; launching apps may be slow or fail if their security cannot be verified online.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;idleassetsd&lt;/td&gt;
&lt;td&gt;Contains some buggy code; blocking it may trigger an infinite retry loop that burns CPU.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;networkserviceproxy&lt;/td&gt;
&lt;td&gt;A proxy used by other Apple services. Blocking it could cause cascading problems that are hard to diagnose. I didn’t really want to deal with that, so I left it allowed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mobileassetd&lt;/td&gt;
&lt;td&gt;Responsible for downloading fonts, dictionaries, and other system assets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;softwareupdated&lt;/td&gt;
&lt;td&gt;Part of the system update mechanism. I want to continue getting updates.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nbagent&lt;/td&gt;
&lt;td&gt;Also part of the system update mechanism.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NRDUpdated&lt;/td&gt;
&lt;td&gt;Also part of the system update mechanism.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UpdateBrainService&lt;/td&gt;
&lt;td&gt;Also part of the system update mechanism.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nsurlsessiond&lt;/td&gt;
&lt;td&gt;Any application can delegate data transfers to this daemon. Since I’m not sure which apps rely on it, I left it in the allow list.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;geod&lt;/td&gt;
&lt;td&gt;The map inside LittleSnitch stopped working when I blocked this service, so I allow it now.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trustd&lt;/td&gt;
&lt;td&gt;Responsible for certificate checks—pretty important security stuff.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timed&lt;/td&gt;
&lt;td&gt;Keeps the clock synced.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All in all, I blocked 43 out of 57 built-in services (~76%). My system continues to function well, and I recently received the macOS Sequoia 15.6.1 update without issues.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Once again, you can find the analysis &lt;a href=&quot;https://gist.github.com/mintc2/42d6699b22d8b4bfd75215d64ff29a61&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in blocking some of the built-in services but have different needs, maybe you&apos;re more extreme about privacy, or inversely need parts of the Apple ecosystem to work, you can use the report as a starting point to build an allow list that fits your needs.&lt;/p&gt;
</content:encoded><author>mintc2</author></item></channel></rss>