<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: withuse</title>
    <description>The latest articles on DEV Community by withuse (@_4143d12ca219f32cb635).</description>
    <link>https://dev.to/_4143d12ca219f32cb635</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4165388%2Fa8e1e287-5a80-4993-87fb-d40d6d0e4a1b.png</url>
      <title>DEV Community: withuse</title>
      <link>https://dev.to/_4143d12ca219f32cb635</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_4143d12ca219f32cb635"/>
    <language>en</language>
    <item>
      <title>I shipped Chrome extensions with an empty permissions list. Here is what that costs you.</title>
      <dc:creator>withuse</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:48:43 +0000</pubDate>
      <link>https://dev.to/_4143d12ca219f32cb635/i-shipped-chrome-extensions-with-an-empty-permissions-list-here-is-what-that-costs-you-5a94</link>
      <guid>https://dev.to/_4143d12ca219f32cb635/i-shipped-chrome-extensions-with-an-empty-permissions-list-here-is-what-that-costs-you-5a94</guid>
      <description>&lt;p&gt;Every Chrome extension I install asks for something. Read and change all your data on all websites, usually. So when I built five small developer utilities as extensions, I wanted to know how far I could get with the permissions array empty.&lt;/p&gt;

&lt;p&gt;All five shipped with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Base64 Encoder &amp;amp; Decoder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"16"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"48"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"128"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"popup.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"homepage_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://base64.withuse.io"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole manifest. No &lt;code&gt;permissions&lt;/code&gt;. No &lt;code&gt;host_permissions&lt;/code&gt;. No &lt;code&gt;content_scripts&lt;/code&gt;. No &lt;code&gt;background&lt;/code&gt;. Not an empty array — the keys are absent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually give up
&lt;/h2&gt;

&lt;p&gt;Three things, and they are the three that most tutorials reach for reflexively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No access to the current page.&lt;/strong&gt; Without &lt;code&gt;activeTab&lt;/code&gt; or a host permission you cannot read the selected text, the URL, or anything in the DOM. For a tool that encodes text the user types into your own popup, that turns out not to matter. For "encode whatever I highlighted", it does — that is a real feature you are declining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No storage.&lt;/strong&gt; No &lt;code&gt;chrome.storage&lt;/code&gt;, so nothing survives closing the popup. No history, no remembered settings. I replaced the usual "last 10 results" list with nothing and have not missed it; the work in these tools is a single transformation, not a session. &lt;code&gt;localStorage&lt;/code&gt; is still available inside the popup's own origin if you want per-install state without declaring the permission — worth knowing, though I did not use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No clipboard permission.&lt;/strong&gt; You do not need one. &lt;code&gt;navigator.clipboard.writeText()&lt;/code&gt; works from a user gesture inside the popup without &lt;code&gt;clipboardWrite&lt;/code&gt; in MV3. This is the one that surprised me; a lot of extension code still declares it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you keep
&lt;/h2&gt;

&lt;p&gt;More than I expected. The popup is a normal web page with a normal origin, so Web Crypto, &lt;code&gt;TextEncoder&lt;/code&gt;, &lt;code&gt;Intl&lt;/code&gt;, and the whole DOM API are there. Every one of these tools is a pure function over text:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UUID generation — &lt;code&gt;crypto.getRandomValues&lt;/code&gt;, RFC 9562 layout, no network&lt;/li&gt;
&lt;li&gt;Base64 — &lt;code&gt;TextEncoder&lt;/code&gt; plus &lt;code&gt;btoa&lt;/code&gt;, with the padding rules handled locally&lt;/li&gt;
&lt;li&gt;JSON formatting and duplicate-key detection — a scan over the raw source text&lt;/li&gt;
&lt;li&gt;JWT decoding — three base64url segments, split and decoded in place&lt;/li&gt;
&lt;li&gt;cron parsing — field expansion and arithmetic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them has a reason to touch the page you are on, and none has a reason to make a request. I grepped the five source trees for &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;XMLHttpRequest&lt;/code&gt; and any CDN-shaped import: zero hits. The token you paste into the JWT decoder cannot leave your machine, because there is no code in the bundle that could send it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dependency decision that follows from this
&lt;/h2&gt;

&lt;p&gt;I did not bundle anything from npm. The UUID logic, the base64 helpers, the cron parser — all hand-written, totalling a few hundred lines across the five.&lt;/p&gt;

&lt;p&gt;That is not about bundle size, though the zips do come out around 16 KB each. It is that &lt;strong&gt;the source a reviewer reads is the source that ships.&lt;/strong&gt; A build step that pulls a transitive dependency tree breaks that property, and an extension is exactly the place where it matters: it runs with whatever permissions you declared, in the browser, forever, updated silently.&lt;/p&gt;

&lt;p&gt;The cost is real. I reimplemented UUID v7 and got the same-millisecond ordering wrong on the first pass, which a mature library would have had right. I caught it with a test, but only because I went looking. A dependency would have saved me that bug and cost me the review property. For five files of pure logic I took that trade; for anything with a protocol or a parser of real complexity I would not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the store does with it
&lt;/h2&gt;

&lt;p&gt;Two observations from actually submitting these.&lt;/p&gt;

&lt;p&gt;The review was fast — both published within a day. I have no way to know whether the empty permissions list is why, and I would not claim it. But a reviewer's job is easier when there is nothing to assess.&lt;/p&gt;

&lt;p&gt;The thing that actually blocked me was unrelated and badly documented: &lt;strong&gt;a new developer account can have only two extensions published at a time.&lt;/strong&gt; Not two per day — two total, until you request an increase from the dashboard. I had five packaged and found this out by hitting it. The error that surfaced first was a generic file-upload failure whose only useful detail was a line about enabling 2-Step Verification, which is a separate prerequisite. Searching for the limit turned up very little, so: two, total, request an increase in the developer dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth trying before you declare anything
&lt;/h2&gt;

&lt;p&gt;Write the manifest with no permissions first and add them one at a time as something actually breaks. I expected to need three and needed none.&lt;/p&gt;

&lt;p&gt;The exercise is cheap and it tells you something about the tool. If nothing breaks, your extension never needed access to the user's pages — and the install prompt that says so is the most honest thing in the listing.&lt;/p&gt;

&lt;p&gt;Two of the five are on the store so far; the two-at-a-time limit above is exactly why. The UUID one is &lt;a href="https://uuid.withuse.io/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Correction, 12 Oct 2026: this sentence originally said all five were on the store. Two are. The limit described in the section above is the reason, and the original wording contradicted it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chrome</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>I measured whether .NET Guid sorting is broken. It isn't.</title>
      <dc:creator>withuse</dc:creator>
      <pubDate>Thu, 08 Oct 2026 02:12:52 +0000</pubDate>
      <link>https://dev.to/_4143d12ca219f32cb635/i-measured-whether-net-guid-sorting-is-broken-it-isnt-4o2</link>
      <guid>https://dev.to/_4143d12ca219f32cb635/i-measured-whether-net-guid-sorting-is-broken-it-isnt-4o2</guid>
      <description>&lt;p&gt;"&lt;code&gt;Guid&lt;/code&gt; sorting in .NET is weird" is a claim you run into often enough that I wanted a number for it. So I measured &lt;code&gt;CompareTo&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It sorts in logical value order.&lt;/strong&gt; The claim is wrong, and the thing people are actually hitting is something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  You need a sample that can tell the two hypotheses apart
&lt;/h2&gt;

&lt;p&gt;This is the part that took me two attempts. .NET stores the first three groups of a Guid in little-endian internally, so to distinguish "sorts by string order" from "sorts by raw byte order" you need values where those two orders &lt;strong&gt;disagree&lt;/strong&gt;. Most hand-written samples do not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;value                                   ToByteArray() first 4 bytes
00000100-0000-0000-0000-000000000000    00-01-00-00
00000001-0000-0000-0000-000000000000    01-00-00-00
01000000-0000-0000-0000-000000000000    00-00-00-01
00010000-0000-0000-0000-000000000000    00-00-01-00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorted by string, those come out in one order. Sorted by raw bytes, exactly the reverse. Now the test means something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CompareTo order == string order       True
CompareTo order == raw byte order     False
sample distinguishes the hypotheses   True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That third line matters as much as the other two. Without it you cannot tell a passing test from a test that could not have failed.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;Guid.CompareTo&lt;/code&gt; is independent of the &lt;code&gt;ToByteArray()&lt;/code&gt; layout. &lt;strong&gt;The folk claim conflates the comparer with the byte layout.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The real trap is the byte layout
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ToByteArray()&lt;/code&gt; does not return RFC 9562 network byte order. The first three groups come back reversed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Guid        00112233-4455-6677-8899-aabbccddeeff

default     33-22-11-00-55-44-77-66-88-99-AA-BB-CC-DD-EE-FF
bigEndian   00-11-22-33-44-55-66-77-88-99-AA-BB-CC-DD-EE-FF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the first four bytes. &lt;code&gt;33-22-11-00&lt;/code&gt; against &lt;code&gt;00-11-22-33&lt;/code&gt;. Groups one, two and three are byte-swapped; the last two groups are not.&lt;/p&gt;

&lt;p&gt;Since .NET 8 you can ask for RFC order explicitly with &lt;code&gt;ToByteArray(bigEndian: true)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually bites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; gives you both and names them: &lt;code&gt;.bytes&lt;/code&gt; is RFC order, &lt;code&gt;.bytes_le&lt;/code&gt; is the .NET default layout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL Server&lt;/strong&gt;'s &lt;code&gt;uniqueidentifier&lt;/code&gt; comparison uses yet another order, which is where most of the "sorting is weird" stories actually come from&lt;/li&gt;
&lt;li&gt;Anywhere you write a Guid to bytes and read it back with a different library&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So
&lt;/h2&gt;

&lt;p&gt;Sorting is not broken. &lt;strong&gt;The layout differs when you serialise.&lt;/strong&gt; It only matters when a value crosses a system boundary, and at that boundary you can just say which order you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified
&lt;/h2&gt;

&lt;p&gt;.NET 10.0.400, run 2026-10-08. An earlier run on 2026-09-14 gave identical results on .NET 10.0.11.&lt;/p&gt;

&lt;p&gt;Full write-up, including the Python and SQL Server side: &lt;a href="https://uuid.withuse.io/uuid-vs-guid/" rel="noopener noreferrer"&gt;https://uuid.withuse.io/uuid-vs-guid/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>database</category>
      <category>uuid</category>
    </item>
    <item>
      <title>atob() does not require padding. It rejects half-padding.</title>
      <dc:creator>withuse</dc:creator>
      <pubDate>Tue, 06 Oct 2026 05:09:28 +0000</pubDate>
      <link>https://dev.to/_4143d12ca219f32cb635/atob-does-not-require-padding-it-rejects-half-padding-11oe</link>
      <guid>https://dev.to/_4143d12ca219f32cb635/atob-does-not-require-padding-it-rejects-half-padding-11oe</guid>
      <description>&lt;p&gt;A common claim about Base64 in the browser is that the input length must be a multiple of 4. It isn't. &lt;code&gt;atob&lt;/code&gt; decodes unpadded input fine. What it rejects is something else.&lt;/p&gt;

&lt;p&gt;Here are all five cases, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"YQ=="     len % 4 = 0   -&amp;gt;  "a"
"YQ"       len % 4 = 2   -&amp;gt;  "a"        &amp;lt;- no padding, still decodes
"YWJjZGU"  len % 4 = 3   -&amp;gt;  "abcde"    &amp;lt;- decodes
"YWJjZ"    len % 4 = 1   -&amp;gt;  throws
"YQ="      len % 4 = 3   -&amp;gt;  throws     &amp;lt;- half-padded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the rule has nothing to do with multiples of 4.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Remainder 2 or 3 decodes without padding.&lt;/strong&gt; The leftover bits are enough to reconstruct the bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remainder 1 always fails.&lt;/strong&gt; Six bits cannot encode a byte, so an encoder can never produce that length in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you pad, pad fully.&lt;/strong&gt; &lt;code&gt;"YQ="&lt;/code&gt; is rejected even though its length mod 4 is 3 — the same remainder as &lt;code&gt;"YWJjZGU"&lt;/code&gt;, which passes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The actual contract of &lt;code&gt;atob&lt;/code&gt; is "pad correctly, or do not pad at all", not "length must be a multiple of 4".&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this bites
&lt;/h2&gt;

&lt;p&gt;The rule is not the same across languages. Same input, &lt;code&gt;"YQ"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JavaScript   atob("YQ")                             -&amp;gt;  "a"
Python       base64.b64decode("YQ")                 -&amp;gt;  binascii.Error: Incorrect padding
Go           base64.StdEncoding.DecodeString("YQ")  -&amp;gt;  illegal base64 data at input byte 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript is the lenient one and Go is the strictest. A value that round-trips fine in the browser and is then handed to a Python or Go service is a common way to get an error that looks like it came from nowhere — the data was never wrong, the two decoders just disagree about what counts as well-formed.&lt;/p&gt;

&lt;p&gt;If you generate Base64 in the browser and consume it elsewhere, normalize the padding before it crosses the boundary, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified
&lt;/h2&gt;

&lt;p&gt;Node v23.7.0, Python 3, Go 1.22.0, run on 2026-09-14. The longer write-up — including Go's four encodings, and why picking the wrong one can pass every test and still break in production on one payload — is here: &lt;a href="https://base64.withuse.io/base64-in-go/" rel="noopener noreferrer"&gt;https://base64.withuse.io/base64-in-go/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>go</category>
      <category>python</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
