<?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: Muhammad Umair | DevOmniTools</title>
    <description>The latest articles on DEV Community by Muhammad Umair | DevOmniTools (@devomnitools).</description>
    <link>https://dev.to/devomnitools</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%2F4122013%2Fe62ea0d3-ab56-48d0-bf28-ab9b98fef2bf.png</url>
      <title>DEV Community: Muhammad Umair | DevOmniTools</title>
      <link>https://dev.to/devomnitools</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devomnitools"/>
    <language>en</language>
    <item>
      <title>Auditing CVE-2026-109461: Insecure Token Handling &amp; Safe In-Browser Verification</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:56:42 +0000</pubDate>
      <link>https://dev.to/devomnitools/auditing-cve-2026-109461-insecure-token-handling-safe-in-browser-verification-4md8</link>
      <guid>https://dev.to/devomnitools/auditing-cve-2026-109461-insecure-token-handling-safe-in-browser-verification-4md8</guid>
      <description>&lt;p&gt;&lt;em&gt;Original Technical Research &amp;amp; Offline Verification provided by &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools Security Suite&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability Context &amp;amp; Overview
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;2026-10-23T22:16:44Z&lt;/strong&gt;, the security advisory &lt;strong&gt;CVE-2026-109461&lt;/strong&gt; disclosed a critical token and credential leak mechanism in developer workflows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;MSAL Node authorization code flow may disclose parameters to an attacker-controlled endpoint&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When applications expose internal loopback listeners or misconfigure cookie serialization attributes, authentication secrets and signing tokens can be scraped across local origin boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Root Cause: The Loopback Cookie Trap
&lt;/h2&gt;

&lt;p&gt;Under standard web architectures, session cookies and authorization tokens often carry the following implicit assumptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Set-Cookie: auth_session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; Path=/; HttpOnly; SameSite=Lax
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, when developer dev-servers or local microservices run on &lt;code&gt;127.0.0.1&lt;/code&gt; or shared developer containers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Origin Rebinding:&lt;/strong&gt; Any local web script or malicious iframe can query the loopback port if DNS rebinding protection is omitted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Token Bounds:&lt;/strong&gt; Tokens without explicit audience (&lt;code&gt;aud&lt;/code&gt;) and issuer (&lt;code&gt;iss&lt;/code&gt;) validation allow cross-tenant impersonation.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Remediation: In-Browser Token Inspection (Zero Server Telemetry)
&lt;/h2&gt;

&lt;p&gt;When debugging compromised tokens, &lt;strong&gt;never paste production credentials into ad-cluttered online formatters&lt;/strong&gt; that transmit data to third-party servers.&lt;/p&gt;

&lt;p&gt;Instead, verify tokens strictly in local browser memory using the Web Crypto API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Offline HMAC-SHA256 signature verification in browser&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyTokenSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;headerB64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payloadB64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signatureB64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;headerB64&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payloadB64&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HMAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Compare signature against WebCrypto verification&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect payload claims, expiration badges, and detached JWS signatures safely without installation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools Offline JWT &amp;amp; Token Inspector&lt;/a&gt;&lt;/strong&gt; executes 100% in local memory with zero external network requests.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key Hardening Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rotate Leaked Secrets Immediately:&lt;/strong&gt; Revoke exposed tokens in your provider dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce Strict Origin Checks:&lt;/strong&gt; Bind developer HTTP listeners exclusively to &lt;code&gt;127.0.0.1&lt;/code&gt; with explicit Host header guards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Lossless Client-Side Utilities:&lt;/strong&gt; Always prefer zero-upload browser workbenches when verifying sensitive developer 
payloads.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Research conducted in air-gapped sandbox environments by DevOmniTools Security Suite. Verify tokens securely via &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools JWT Decoder&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Auditing the Vitest Dev Server Secret Leak (CVE-2026-112948): How to Rotate Leaked .env Tokens Safely</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:40:31 +0000</pubDate>
      <link>https://dev.to/devomnitools/auditing-the-vitest-dev-server-secret-leak-cve-2026-112948-how-to-rotate-leaked-env-tokens-1dd2</link>
      <guid>https://dev.to/devomnitools/auditing-the-vitest-dev-server-secret-leak-cve-2026-112948-how-to-rotate-leaked-env-tokens-1dd2</guid>
      <description>&lt;p&gt;On October 21, 2026, the GitHub Advisory Database published &lt;strong&gt;CVE-2026-112948&lt;/strong&gt; (GHSA-p458-7748-mcp3), exposing a critical file access misconfiguration in Vitest 5 dev servers.&lt;/p&gt;

&lt;p&gt;Under specific developer configurations—specifically when running the interactive UI (&lt;code&gt;vitest --ui&lt;/code&gt;), Browser Mode (&lt;code&gt;@vitest/browser&lt;/code&gt;), or when enabling the remote &lt;code&gt;api&lt;/code&gt; flag—Vitest's underlying HTTP server failed to apply Vite's default &lt;code&gt;server.fs.deny&lt;/code&gt; rules.&lt;/p&gt;

&lt;p&gt;The outcome: sensitive project assets that Vite ordinarily guards by default—including &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.env.local&lt;/code&gt;, &lt;code&gt;.npmrc&lt;/code&gt;, private SSL certificates, &lt;code&gt;.git&lt;/code&gt; metadata, and Vitest's internal authentication token file (&lt;code&gt;.vitest-secret-token&lt;/code&gt;)—could be fetched over plain HTTP by any client reachable on the network or by a malicious browser tab via cross-origin requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F844ma7y9bj6ts1f6uwxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F844ma7y9bj6ts1f6uwxg.png" alt="Vitest dev server.fs.deny bypass architecture diagram showing plain HTTP requests leaking .env and API secret files (CVE-2026-112948)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an engineering audit of what went wrong, how to check if your workstation was exposed, and how to safely rotate compromised tokens.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vulnerability Breakdown: Why &lt;code&gt;server.fs.deny&lt;/code&gt; Was Overridden
&lt;/h2&gt;

&lt;p&gt;In a standard Vite configuration, &lt;code&gt;server.fs.deny&lt;/code&gt; protects sensitive paths using built-in globs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Vite's built-in default deny patterns:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.env.*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*.{crt,pem}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/.git/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/.npmrc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Vitest 5 introduced its internal API server and UI token authentication (&lt;code&gt;.vitest-secret-token&lt;/code&gt;), it programmatically appended its token file to the deny list. However, two underlying Vite runtime behaviors caused a silent bypass:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Premature Array Overwrite&lt;/strong&gt;: Vite only injects its default deny list when &lt;code&gt;server.fs.deny&lt;/code&gt; is completely &lt;code&gt;undefined&lt;/code&gt;. When Vitest assigned an array containing only &lt;code&gt;['.vitest-secret-token']&lt;/code&gt;, Vite's default rules were discarded rather than merged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matcher Pre-Compilation&lt;/strong&gt;: Vite compiles the &lt;code&gt;deny&lt;/code&gt; list into an internal regex matcher during config resolution. Dynamic runtime mutations pushed later had zero effect on the active HTTP file serving pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you ran &lt;code&gt;vitest --ui&lt;/code&gt; on a local network or tested apps inside a browser with multi-tab sessions, any script capable of hitting &lt;code&gt;localhost:51204&lt;/code&gt; could scrape your secret keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  Am I Affected?
&lt;/h2&gt;

&lt;p&gt;The advisory confirms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Affected&lt;/strong&gt;: Vitest 5 running with &lt;code&gt;--ui&lt;/code&gt;, &lt;code&gt;@vitest/browser&lt;/code&gt;, or &lt;code&gt;api: true&lt;/code&gt; in &lt;code&gt;vitest.config.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not Affected&lt;/strong&gt;: Headless command-line runs (&lt;code&gt;vitest run&lt;/code&gt; or simple &lt;code&gt;vitest&lt;/code&gt; in terminal mode) because they do not spawn an HTTP listener.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Immediate Remediation Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Upgrade Vitest to Patched Release
&lt;/h3&gt;

&lt;p&gt;Vitest maintainers released patched versions addressing the matcher compilation flaw. Upgrade Vitest alongside its peer dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vitest@latest @vitest/ui@latest @vitest/browser@latest
&lt;span class="c"&gt;# or with pnpm:&lt;/span&gt;
pnpm update vitest @vitest/ui @vitest/browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Audit and Invalidate Leaked Secrets
&lt;/h3&gt;

&lt;p&gt;If you ran Vitest UI on shared Wi-Fi (coworking spaces, cafes) or had active third-party tabs open during browser mode tests, treat all keys residing in your &lt;code&gt;.env&lt;/code&gt; as &lt;strong&gt;compromised&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Common credentials stored in &lt;code&gt;.env&lt;/code&gt; requiring immediate rotation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud provider access tokens (AWS, GCP, Cloudflare)&lt;/li&gt;
&lt;li&gt;Database credentials (Postgres, Redis, MongoDB connection URIs)&lt;/li&gt;
&lt;li&gt;OAuth 2.0 Client Secrets and JWT Signing Passphrases&lt;/li&gt;
&lt;li&gt;Third-party API keys (Stripe, Resend, GitHub PATs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4spav3bd0rnz448uv9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4spav3bd0rnz448uv9d.png" alt="Cryptographic token rotation workflow: revoking compromised .env tokens and generating 256-bit CSPRNG API keys and TOTP Base32 secrets offline" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Rotate Tokens with High-Entropy CSPRNG
&lt;/h3&gt;

&lt;p&gt;When re-issuing API tokens or generating new application secrets, never use pseudo-random generators (&lt;code&gt;Math.random()&lt;/code&gt;) or web services that log requests to central databases.&lt;/p&gt;

&lt;p&gt;Use the Web Crypto API (&lt;code&gt;crypto.getRandomValues&lt;/code&gt;) to generate cryptographically unguessable keys with adequate entropy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Generate a 256-bit high-entropy secret token (RFC 6750 compliant)
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateSecureToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byteLength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byteLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example Output: 64-char Hex (256 bits of entropy)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;generateSecureToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need ready-to-copy Bearer tokens (&lt;code&gt;sk_live_...&lt;/code&gt;), RFC 4648 Base32 keys (for TOTP/2FA secrets), or URL-safe Base64URL tokens without writing boilerplate code, you can use the free, client-side &lt;a href="https://devomnitools.com/en/tools/token-generator/" rel="noopener noreferrer"&gt;DevOmniTools Token Generator&lt;/a&gt; — all tokens are generated strictly in browser memory via CSPRNG with zero server calls.&lt;/p&gt;

&lt;p&gt;Likewise, if you need to inspect decoded JWT headers and audience claims from your rotated tokens to verify expiration and issuer claims without sending your payload across the web, use the &lt;a href="https://devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools Offline JWT Decoder&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defensive Coding: Explicitly Guarding &lt;code&gt;server.fs.deny&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Even after updating, you can explicitly reinforce your &lt;code&gt;vite.config.ts&lt;/code&gt; or &lt;code&gt;vitest.config.ts&lt;/code&gt; to ensure sensitive files can never be served under any circumstances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/.env*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.pem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/.git/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/.npmrc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/.vitest-secret-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// your test settings&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;[ ] Update &lt;code&gt;vitest&lt;/code&gt; and &lt;code&gt;@vitest/ui&lt;/code&gt; to the latest security patch.&lt;/li&gt;
&lt;li&gt;[ ] Revoke and re-issue all API tokens present in &lt;code&gt;.env&lt;/code&gt; files accessed during Vitest UI sessions.&lt;/li&gt;
&lt;li&gt;[ ] Verify that &lt;code&gt;server.fs.strict: true&lt;/code&gt; is configured in your project settings.&lt;/li&gt;
&lt;li&gt;[ ] Check Git history to ensure no &lt;code&gt;.env&lt;/code&gt; files were accidentally committed during debugging.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Fixing the MCP Bearer Auth Flaw (CVE-2026-111771): Why Cross-Service Tokens Were Accepted</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Wed, 21 Oct 2026 13:31:23 +0000</pubDate>
      <link>https://dev.to/devomnitools/fixing-the-mcp-bearer-auth-flaw-cve-2026-111771-why-cross-service-tokens-were-accepted-1944</link>
      <guid>https://dev.to/devomnitools/fixing-the-mcp-bearer-auth-flaw-cve-2026-111771-why-cross-service-tokens-were-accepted-1944</guid>
      <description>&lt;p&gt;On October 20, 2026, GitHub Advisory Database published &lt;strong&gt;CVE-2026-111771&lt;/strong&gt; (GHSA-rvq5-wwqv-78pq), detailing an authentication delegation flaw in the official Model Context Protocol (&lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;) TypeScript implementation. &lt;/p&gt;

&lt;p&gt;When running MCP servers over remote HTTP transports (such as Server-Sent Events), the default bearer authentication middleware accepted valid tokens issued for completely different services within the same identity ecosystem.&lt;/p&gt;

&lt;p&gt;If an attacker held a legitimate user token for a low-privilege internal dashboard, they could replay that token against a high-privilege MCP server, executing unauthorized tool calls and extracting sensitive prompt context.&lt;/p&gt;

&lt;p&gt;Here is the root-cause analysis, reproducible code fixtures, and how to verify your tokens safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root Cause: Confused Deputy on Audience (&lt;code&gt;aud&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Under &lt;a href="https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3" rel="noopener noreferrer"&gt;RFC 7519 (JSON Web Token)&lt;/a&gt;, the &lt;code&gt;aud&lt;/code&gt; (Audience) claim identifies the intended recipients of the token. Furthermore, &lt;a href="https://datatracker.ietf.org/doc/html/rfc6750" rel="noopener noreferrer"&gt;RFC 6750 (OAuth 2.0 Bearer Tokens)&lt;/a&gt; dictates that resource servers MUST reject tokens not explicitly intended for their service.&lt;/p&gt;

&lt;p&gt;In affected versions of &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt;, the default HTTP transport verified:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Token format and cryptographic signature integrity using the configured public key or JWKS endpoint.&lt;/li&gt;
&lt;li&gt;Token expiration timestamp (&lt;code&gt;exp&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, it &lt;strong&gt;omitted strict evaluation of the &lt;code&gt;aud&lt;/code&gt; claim&lt;/strong&gt;. If an organization used a single Identity Provider (such as Auth0, Keycloak, or Okta) across multiple internal microservices, any valid token issued for &lt;code&gt;Service-A&lt;/code&gt; was erroneously accepted by &lt;code&gt;MCP-Server-B&lt;/code&gt;.&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;"iss"&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://auth.company.internal/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_dev_8841"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&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://analytics-dashboard.company.internal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1792576800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read:analytics"&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;&lt;em&gt;Vulnerable MCP servers checked the signature against &lt;code&gt;auth.company.internal&lt;/code&gt;, ignored that &lt;code&gt;aud&lt;/code&gt; was intended only for &lt;code&gt;analytics-dashboard&lt;/code&gt;, and granted agent-level tool access.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Fix: Before vs After
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Vulnerable Implementation
&lt;/h3&gt;

&lt;p&gt;In affected implementations, middleware passed a generic verification callback without specifying audience constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ VULNERABLE: Lacks audience (aud) and issuer (iss) enforcement&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SSEServerTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@modelcontextprotocol/sdk/server/sse.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;InternalAgent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/sse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Only verifies cryptographic signature; permits any token issued by JWKS&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IDP_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SSEServerTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Hardened Production Configuration
&lt;/h3&gt;

&lt;p&gt;To mitigate CVE-2026-111771, upgrade &lt;code&gt;@modelcontextprotocol/sdk&lt;/code&gt; to the latest release and enforce explicit &lt;code&gt;audience&lt;/code&gt; and &lt;code&gt;issuer&lt;/code&gt; validation options in your verification chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ HARDENED: Strict audience, issuer, and algorithm validation&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SSEServerTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@modelcontextprotocol/sdk/server/sse.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MCP_RESOURCE_AUDIENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mcp.company.internal/api/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TRUSTED_ISSUER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://auth.company.internal/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/sse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bearer &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing or malformed bearer token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Enforces RFC 7519 audience and issuer validation strictly&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IDP_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MCP_RESOURCE_AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TRUSTED_ISSUER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RS256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ES256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Forbidden: Token rejected due to invalid audience or expired credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SSEServerTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/messages&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Inspecting Tokens Locally (Zero Server Upload)
&lt;/h2&gt;

&lt;p&gt;Before deploying security patches, developers should verify their token claims client-side to ensure compliance with intended scopes and target audiences.&lt;/p&gt;

&lt;p&gt;Never paste internal production tokens into web tools that send payloads to remote backends. You can inspect your headers and claims 100% locally in your browser memory using the open-source &lt;strong&gt;&lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools JWT &amp;amp; JWS Decoder&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paste your Bearer token into the decoder.&lt;/li&gt;
&lt;li&gt;Confirm the &lt;code&gt;aud&lt;/code&gt; claim matches your MCP endpoint URI.&lt;/li&gt;
&lt;li&gt;Check the &lt;code&gt;exp&lt;/code&gt; timestamp locally without network telemetry.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Transport Layer Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport Layer&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Vulnerable to CVE-2026-111771?&lt;/th&gt;
&lt;th&gt;Required Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;stdio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local IPC / UNIX Pipes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Process isolation; local CLI arguments only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSE (HTTP)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server-Sent Events&lt;/td&gt;
&lt;td&gt;⚠️ &lt;strong&gt;Yes (High)&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Enforce &lt;code&gt;audience&lt;/code&gt; parameter in JWT verify hook.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WebSockets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WSS Handshake&lt;/td&gt;
&lt;td&gt;⚠️ &lt;strong&gt;Yes (Medium)&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Validate token headers during initial upgrade handshake.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom Gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reverse Proxy / Envoy&lt;/td&gt;
&lt;td&gt;❌ No (If proxy validates)&lt;/td&gt;
&lt;td&gt;Validate JWT at Envoy/Ingress using &lt;code&gt;jwt_authn&lt;/code&gt; filter with exact &lt;code&gt;audiences&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Original advisory documentation and multilingual references available on &lt;a href="https://www.devomnitools.com/en/guides/mcp-server-bearer-auth-jwt-vulnerability/" rel="noopener noreferrer"&gt;DevOmniTools Research&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>securitytypescriptwebdevai</category>
    </item>
    <item>
      <title>Why We Rebuilt Popular Web Utilities to Run 100% Client-Side</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Tue, 20 Oct 2026 11:34:45 +0000</pubDate>
      <link>https://dev.to/devomnitools/why-we-rebuilt-popular-web-utilities-to-run-100-client-side-gpm</link>
      <guid>https://dev.to/devomnitools/why-we-rebuilt-popular-web-utilities-to-run-100-client-side-gpm</guid>
      <description>&lt;h2&gt;
  
  
  The Silent Security Risk in Modern Developer Utilities
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dqphgah4h3e321lbtbj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dqphgah4h3e321lbtbj.jpg" alt="The Problem With Traditional Online Developer Utilities - Server Logging Risks" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As software engineers, we are drilled on defense-in-depth: zero-trust network policies, encrypted-at-rest databases, rotating service principals, and strict CORS headers.&lt;/p&gt;

&lt;p&gt;Yet, during daily incident triage or local testing, a common developer workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An authentication issue arises on staging -&amp;gt; you grab an active user's &lt;strong&gt;Bearer JWT token&lt;/strong&gt; and paste it into a web decoder.&lt;/li&gt;
&lt;li&gt;Search crawlers start indexing internal endpoints -&amp;gt; you paste your draft &lt;strong&gt;robots.txt file containing private staging and API routes&lt;/strong&gt; into an online validator.&lt;/li&gt;
&lt;li&gt;A backend service throws an encoding error -&amp;gt; you copy-paste &lt;strong&gt;base64-encoded binary blobs or SQL dump queries&lt;/strong&gt; into an online formatter.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Problem With Server-Side Utilities
&lt;/h3&gt;

&lt;p&gt;A massive percentage of free utility websites operate on traditional server-side rendering pipelines (Node.js/Express, Python/Flask, Go, or PHP). When you click &lt;strong&gt;"Decode"&lt;/strong&gt;, &lt;strong&gt;"Format"&lt;/strong&gt;, or &lt;strong&gt;"Validate"&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your token or payload travels over HTTP to a 3rd-party server.&lt;/li&gt;
&lt;li&gt;Reverse proxies (Nginx/Envoy/Cloudflare) terminate TLS and record request bodies or headers in application access logs.&lt;/li&gt;
&lt;li&gt;Cloud Application Performance Monitoring (APM) tools (Datadog, New Relic, Sentry) capture unscrubbed parameters during debug logging.&lt;/li&gt;
&lt;li&gt;Telemetry engines and analytics pixels observe user interactions with the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that server gets breached, or a rogue analytics script scrapes the DOM, your confidential tokens, user scopes, and internal architecture become public knowledge.&lt;/p&gt;

&lt;p&gt;We believed developers shouldn't have to choose between convenience and confidentiality. That is why we built and open-sourced &lt;strong&gt;&lt;a href="https://www.devomnitools.com/" rel="noopener noreferrer"&gt;DevOmniTools&lt;/a&gt;&lt;/strong&gt; ## The Silent Security Risk in Modern Developer Utilities&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dqphgah4h3e321lbtbj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dqphgah4h3e321lbtbj.jpg" alt="The Problem With Traditional Online Developer Utilities - Server Logging Risks" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As software engineers, we are drilled on defense-in-depth: zero-trust network policies, encrypted-at-rest databases, rotating service principals, and strict CORS headers.&lt;/p&gt;

&lt;p&gt;Yet, during daily incident triage or local testing, a common developer workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An authentication issue arises on staging -&amp;gt; you grab an active user's &lt;strong&gt;Bearer JWT token&lt;/strong&gt; and paste it into a web decoder.&lt;/li&gt;
&lt;li&gt;Search crawlers start indexing internal endpoints -&amp;gt; you paste your draft &lt;strong&gt;robots.txt file containing private staging and API routes&lt;/strong&gt; into an online validator.&lt;/li&gt;
&lt;li&gt;A backend service throws an encoding error -&amp;gt; you copy-paste &lt;strong&gt;base64-encoded binary blobs or SQL dump queries&lt;/strong&gt; into an online formatter.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Problem With Server-Side Utilities
&lt;/h3&gt;

&lt;p&gt;A massive percentage of free utility websites operate on traditional server-side rendering pipelines (Node.js/Express, Python/Flask, Go, or PHP). When you click &lt;strong&gt;"Decode"&lt;/strong&gt;, &lt;strong&gt;"Format"&lt;/strong&gt;, or &lt;strong&gt;"Validate"&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your token or payload travels over HTTP to a 3rd-party server.&lt;/li&gt;
&lt;li&gt;Reverse proxies (Nginx/Envoy/Cloudflare) terminate TLS and record request bodies or headers in application access logs.&lt;/li&gt;
&lt;li&gt;Cloud Application Performance Monitoring (APM) tools (Datadog, New Relic, Sentry) capture unscrubbed parameters during debug logging.&lt;/li&gt;
&lt;li&gt;Telemetry engines and analytics pixels observe user interactions with the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that server gets breached, or a rogue analytics script scrapes the DOM, your confidential tokens, user scopes, and internal architecture become public knowledge.&lt;/p&gt;

&lt;p&gt;We believed developers shouldn't have to choose between convenience and confidentiality. That is why we built and open-sourced &lt;strong&gt;&lt;a href="https://www.devomnitools.com/" rel="noopener noreferrer"&gt;DevOmniTools&lt;/a&gt;&lt;/strong&gt; - an ecosystem of 79+ developer tools running with &lt;strong&gt;100% client-side execution, zero API proxies, and zero telemetry&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architectural Breakdown: How Zero-Leakage Actually Works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0qouzk6180lk5fjiyju.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn0qouzk6180lk5fjiyju.jpg" alt="DevOmniTools Client-Side Architecture Diagram" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the engineering reality behind running complex developer tooling inside the browser sandbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------------------+
|                   BROWSER ISOLATION                    |
|                                                        |
|  [Sensitive Input]                                     |
|        |                                               |
|        v                                               |
|  [Web Worker Thread] --- RFC 9309 Parser (Zero Telemetry)
|                      --- Base64URL Stream Decode       |
|                      --- W3C Web Cryptography API      |
|        v                                               |
|  [Structured DOM]                                      |
+--------------------------------------------------------+
                 X NO HTTP BACKEND CALLS X
+--------------------------------------------------------+
|                  STATIC CDN / EDGE                     |
|                (Pre-rendered Assets)                   |
+--------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Decoding JWTs &amp;amp; JWS Safely (RFC 7515 / 7519)
&lt;/h3&gt;

&lt;p&gt;A standard signed JWT is a compact serialization of three dot-separated Base64URL strings: &lt;code&gt;Header.Payload.Signature&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of transmitting this token to a remote server, our &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;JWT &amp;amp; JWS Decoder&lt;/a&gt; decodes the segments locally using native browser string operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decodeJwtLocally&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid JWT: Expected 3 compact serialization segments.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Base64URL to Base64 standard padding&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodeSegment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeSegment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeSegment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;signaturePresent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Your sensitive claims (&lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;roles&lt;/code&gt;, &lt;code&gt;tenant_id&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;) remain exclusively in client RAM and disappear the moment you close the tab.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Validating Robots.txt Rules Without Exposing Private Paths
&lt;/h3&gt;

&lt;p&gt;When configuring &lt;code&gt;robots.txt&lt;/code&gt;, engineers often declare &lt;code&gt;Disallow: /admin-internal/&lt;/code&gt;, &lt;code&gt;Disallow: /staging-api/&lt;/code&gt;, or specify secret crawler user-agents (&lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, &lt;code&gt;Googlebot&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Online validators that require a public URL or fetch through their servers leak these staging patterns. With our &lt;a href="https://www.devomnitools.com/en/tools/robots-txt-tester/" rel="noopener noreferrer"&gt;Robots.txt Tester &amp;amp; Validator&lt;/a&gt;, syntax validation strictly complies with &lt;strong&gt;RFC 9309&lt;/strong&gt; inside a dedicated client-side worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client-side RFC 9309 Path Matching Logic&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isPathAllowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// RFC 9309 rule precedence: end-of-line marker '$' and wildcard '*'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escaped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;.+?^${}()|[&lt;/span&gt;&lt;span class="se"&gt;\]\\]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;$&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\*&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;^&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;escaped&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can simulate any bot path matching, verify AI bot disallows, and catch syntax errors without a single packet leaving your laptop.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Converting SVG to Icon Fonts Client-Side
&lt;/h3&gt;

&lt;p&gt;Converting vector icons to web fonts (&lt;code&gt;TTF&lt;/code&gt;, &lt;code&gt;WOFF2&lt;/code&gt;) traditionally requires heavy server-side Python libraries (&lt;code&gt;fontforge&lt;/code&gt;, &lt;code&gt;fonttools&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Through client-side WebAssembly and JavaScript vector compilers in our &lt;a href="https://www.devomnitools.com/en/tools/svg-to-font/" rel="noopener noreferrer"&gt;SVG to Font Converter&lt;/a&gt;, vector coordinates are parsed directly into TrueType glyph tables in browser memory, generating downloadable font packages with zero server uploads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Lessons Learned Building Client-Side Developer Tools
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Modern Browser Standards:&lt;/strong&gt; The Web Platform in 2026 is absurdly powerful. The &lt;strong&gt;W3C SubtleCrypto API&lt;/strong&gt; is hardware-accelerated. &lt;strong&gt;Web Workers&lt;/strong&gt; eliminate UI freezing during 50MB JSON parses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Compromise on Network Cleanliness:&lt;/strong&gt; Check your Network Tab. When using a developer tool, if you see POST or PUT requests containing your payload, find an alternative or build a local script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-Source Everything:&lt;/strong&gt; Trust is earned through verifiable code. We keep the entire architecture open for review so any developer can audit the source.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try It &amp;amp; Check Your Network Tab
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdh06nrv9er52najrdkq9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdh06nrv9er52najrdkq9.jpg" alt="DevOmniTools Developer Utilities UI Showcase" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want fast, privacy-first developer utilities that respect your data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore the tools: &lt;strong&gt;&lt;a href="https://www.devomnitools.com/" rel="noopener noreferrer"&gt;DevOmniTools&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Inspect the &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;JWT &amp;amp; JWS Decoder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Test your crawler configurations with the &lt;a href="https://www.devomnitools.com/en/tools/robots-txt-tester/" rel="noopener noreferrer"&gt;Robots.txt Tester&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Open Chrome DevTools (&lt;code&gt;F12&lt;/code&gt; -&amp;gt; Network Tab), paste your tokens, and verify: &lt;strong&gt;0 network requests&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;How does your team handle confidential data when debugging in development environments? Let's discuss in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Why Decoding a JWT Is Not Verifying It (And How Security Bugs Slip Through)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Tue, 06 Oct 2026 07:20:20 +0000</pubDate>
      <link>https://dev.to/devomnitools/why-decoding-a-jwt-is-not-verifying-it-and-how-security-bugs-slip-through-3af</link>
      <guid>https://dev.to/devomnitools/why-decoding-a-jwt-is-not-verifying-it-and-how-security-bugs-slip-through-3af</guid>
      <description>&lt;p&gt;Last Tuesday, a staging auth bug had three devs stumped for half an afternoon.&lt;/p&gt;

&lt;p&gt;Nginx was spitting &lt;code&gt;401 Unauthorized&lt;/code&gt;. But the tester dumped their Bearer token into Chrome's DevTools console, ran &lt;code&gt;JSON.parse(atob(token.split('.')[1]))&lt;/code&gt;, and saw:&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;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_9912"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1791592000&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;The expiration was hours away. The role string was right.&lt;/p&gt;

&lt;p&gt;So why did the API gateway reject it?&lt;/p&gt;

&lt;p&gt;Because Base64URL decoding doesn't verify signatures. It just prints whatever text is encoded in the middle chunk. The actual signing key on the auth service had been rotated thirty minutes earlier, so every signature check was failing cold at the reverse proxy.&lt;/p&gt;

&lt;p&gt;I see this confusion constantly in backend code reviews. Developers assume that because a token decodes cleanly, it must be valid.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;Decoding a token and verifying a token are completely different operations. Conflating them is how privilege escalation bugs and auth bypasses make it to production.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Decoding Is Just String Unmasking (There Is Zero Encryption Here)
&lt;/h3&gt;

&lt;p&gt;Standard JSON Web Tokens (RFC 7519) aren't encrypted. Unless you specifically implement JWE (RFC 7516), your token is just three Base64URL strings separated by dots:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;header.payload.signature&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That middle chunk is plain text wrapped in Base64URL. You don't need private keys, certificates, or an auth server to read it. A ten-line script in Node or Python will extract the data instantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOTkxMiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc5MTU5MjAwMH0.sample_signature_hash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Split and decode middle chunk&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decodedString&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Prints: { sub: "usr_9912", role: "admin", exp: 1791592000 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what we needed to get that data: nothing. No secrets, no certificates, no cryptographic hashing.&lt;/p&gt;

&lt;p&gt;Because of that, decoding only tells you what the payload claims to say. It tells you nothing about whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The token was actually created by your authentication server.&lt;/li&gt;
&lt;li&gt;An attacker tampered with the claims in transit.&lt;/li&gt;
&lt;li&gt;The signature matches the payload contents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a backend service accepts &lt;code&gt;role: admin&lt;/code&gt; just by reading the JSON payload without validating the cryptographic signature, an attacker can base64-encode whatever claims they want, slap on a random signature string, and your system will grant them full access.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. What Verification Actually Does Under the Hood
&lt;/h3&gt;

&lt;p&gt;Signature verification is pure mathematics, not string parsing.&lt;/p&gt;

&lt;p&gt;When your auth service issues a token, it glues the header and payload together:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;base64(header) + "." + base64(payload)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then it hashes those bytes using a cryptographic algorithm (HMAC-SHA256 for symmetric, or RSA/ECDSA for asymmetric) alongside a private key or secret. The resulting output is appended as the third part: the signature.&lt;/p&gt;

&lt;p&gt;When the token reaches your backend API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your server grabs the exact same incoming header and payload.&lt;/li&gt;
&lt;li&gt;It recomputes the cryptographic hash using its trusted key.&lt;/li&gt;
&lt;li&gt;It compares the newly generated hash against the signature attached to the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If an attacker changes even a single character in the payload—say, editing &lt;code&gt;"usr_9912"&lt;/code&gt; to &lt;code&gt;"usr_9913"&lt;/code&gt;—the recomputed hash won't match. The signature check fails immediately, and the request drops with a &lt;code&gt;401 Unauthorized&lt;/code&gt; before touching your database.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. The Classic Frontend Mistake: Leaking Symmetric Keys
&lt;/h3&gt;

&lt;p&gt;I still see teams try to "verify" tokens inside React, Vue, or mobile apps using symmetric HMAC keys (&lt;code&gt;HS256&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Don't do this.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;HS256&lt;/code&gt;, the exact same secret key is used to both sign and verify tokens. If you put that secret into your frontend build configuration, it gets compiled straight into your client-side JavaScript bundle. Any user can open DevTools, copy your secret, and mint their own valid tokens with arbitrary permissions for any account in your database.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Safe Split:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend clients (React, Vue, mobile apps):&lt;/strong&gt; Decode only. Use decoded claims strictly for cosmetic UI states—like showing the user's username or checking &lt;code&gt;exp&lt;/code&gt; to schedule a silent refresh call. Never make security decisions based on client-decoded data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend services &amp;amp; API gateways:&lt;/strong&gt; Verify the signature on every single incoming request using public keys (&lt;code&gt;RS256&lt;/code&gt;/&lt;code&gt;ES256&lt;/code&gt;) or server-side secrets (&lt;code&gt;HS256&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Two Implementation Flaws to Watch Out For
&lt;/h3&gt;

&lt;p&gt;If you write custom authentication middleware, keep an eye on these two traps:&lt;/p&gt;

&lt;h4&gt;
  
  
  Trap A: The &lt;code&gt;alg: "none"&lt;/code&gt; Bypass
&lt;/h4&gt;

&lt;p&gt;Older versions of libraries like &lt;code&gt;python-jwt&lt;/code&gt; and &lt;code&gt;node-jsonwebtoken&lt;/code&gt; had a critical design flaw: if an incoming token specified &lt;code&gt;"alg": "none"&lt;/code&gt; in the header, the parser skipped signature validation entirely and treated the token as valid. Attackers could forge admin tokens, strip off the signature, set &lt;code&gt;alg: "none"&lt;/code&gt;, and bypass auth completely.&lt;/p&gt;

&lt;p&gt;Modern libraries block this by default, but you should always hard-pin the accepted algorithms in your verifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;

&lt;span class="c1"&gt;# Always hard-pin algorithms on the verifier side
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;raw_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Pin strictly! Never let token choose.
&lt;/span&gt;        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpiredSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Trap B: Algorithm Confusion (RS256 vs HS256)
&lt;/h4&gt;

&lt;p&gt;If your auth service signs tokens with asymmetric RSA (&lt;code&gt;RS256&lt;/code&gt;), your API services verify using the RSA Public Key.&lt;/p&gt;

&lt;p&gt;In an algorithm confusion attack, an attacker takes your public key (which is publicly accessible), changes the token header from &lt;code&gt;RS256&lt;/code&gt; to &lt;code&gt;HS256&lt;/code&gt;, and signs the token using your public key string as an HMAC secret! If your backend verifier doesn't enforce the algorithm, it might treat the public key as raw HMAC bytes and validate the forged signature as legitimate.&lt;/p&gt;

&lt;p&gt;Always pass &lt;code&gt;algorithms=["RS256"]&lt;/code&gt; explicitly on your verifier. Never allow the token header to dictate which cryptographic algorithm to execute.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Debugging Tokens Locally Without Leaking Secrets
&lt;/h3&gt;

&lt;p&gt;During local development and testing, you frequently need to inspect token payloads, check &lt;code&gt;exp&lt;/code&gt; timestamps, and debug custom claims.&lt;/p&gt;

&lt;p&gt;Pasting real staging or production tokens into random web decoders is risky. If the site routes requests through a backend server that logs incoming payloads, active session tokens can end up in third-party cloud logs.&lt;/p&gt;

&lt;p&gt;If you need to inspect a token quickly in the browser without sending data across the network, you can use our in-browser tool:&lt;br&gt;
👉 &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools JWT Decoder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs 100% locally in your browser memory using client-side JavaScript. Zero backend uploads, zero server requests, and zero remote logging.&lt;/p&gt;




&lt;h3&gt;
  
  
  Quick Architecture Checklist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Frontends decode tokens solely for UI rendering—never for authorization logic.&lt;/li&gt;
&lt;li&gt;Backends verify the cryptographic signature on every single protected API route.&lt;/li&gt;
&lt;li&gt;Never expose symmetric HMAC secrets inside client-side bundles or mobile binaries.&lt;/li&gt;
&lt;li&gt;Always hard-pin the allowed algorithms (&lt;code&gt;RS256&lt;/code&gt; or &lt;code&gt;HS256&lt;/code&gt;) in your verification middleware.&lt;/li&gt;
&lt;li&gt;Verify expiration (&lt;code&gt;exp&lt;/code&gt;), issuer (&lt;code&gt;iss&lt;/code&gt;), and audience (&lt;code&gt;aud&lt;/code&gt;) alongside the signature.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why Your SVG Icons Break When Converted to Web Fonts (And How to Fix Inverted Glyphs)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Mon, 05 Oct 2026 17:07:20 +0000</pubDate>
      <link>https://dev.to/devomnitools/why-your-svg-icons-break-when-converted-to-web-fonts-and-how-to-fix-inverted-glyphs-3h7n</link>
      <guid>https://dev.to/devomnitools/why-your-svg-icons-break-when-converted-to-web-fonts-and-how-to-fix-inverted-glyphs-3h7n</guid>
      <description>&lt;p&gt;Converting SVGs into custom icon fonts sounds straightforward until you compile your first glyph set. You export ten clean icons from Figma or Illustrator, feed them into an icon font generator, and open the preview. Half the icons are upside down. Two icons have solid black fills where transparent cutouts should be. And one icon has blurry, clipped strokes.&lt;/p&gt;

&lt;p&gt;If you have run into this, you are not alone. These issues are not random bugs in your font compiler; they stem from fundamental mathematical differences between how browser SVGs render vector coordinates and how OpenType/TrueType font engines parse glyph contours. Here is what actually breaks under the hood, and how to fix your vectors so they compile cleanly every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Y-Axis Inversion Trap (Why Your Icons Turn Upside Down)
&lt;/h2&gt;

&lt;p&gt;In modern web graphics, SVGs use a &lt;strong&gt;top-left origin&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;(0,0) Converting SVGs into custom icon fonts sounds straightforward until you compile your first glyph set. You export ten clean icons from Figma or Illustrator, feed them into an icon font generator, and open the preview. Half the icons are upside down. Two icons have solid black fills where transparent cutouts should be. And one icon has blurry, clipped strokes.

If you have run into this, you are not alone. These issues are not random bugs in your font compiler; they stem from fundamental mathematical differences between how browser SVGs render vector coordinates and how OpenType/TrueType font engines parse glyph contours. Here is what actually breaks under the hood, and how to fix your vectors so they compile cleanly every time.
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gu"&gt;## 1. The Y-Axis Inversion Trap (Why Your Icons Turn Upside Down)&lt;/span&gt;

In modern web graphics, SVGs use a &lt;span class="gs"&gt;**top-left origin**&lt;/span&gt;:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(0,0) ------- +X&lt;br&gt;
  |&lt;br&gt;
  |&lt;br&gt;
 +Y&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Increasing `y` values push coordinates downwards. TrueType and OpenType font systems use a **Cartesian bottom-left origin**:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;+Y&lt;br&gt;
  |&lt;br&gt;
  |&lt;br&gt;
(0,0) ------- +X&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Increasing `y` values push coordinates upwards. When a compiler parses an SVG path like:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
xml&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
It reads `(10, 20)` and `(50, 80)` as points in a Cartesian space unless the coordinate transform explicitly mirrors the Y-axis across the font's unit box. If your build pipeline or converter omits an affine matrix inversion (`scale(1, -1)` + `translate(0, -baseline)`), the glyph compiles upside down.

### The Fix

Before compiling, ensure your vector compiler normalizes coordinates against the standard Units Per Em (UPM) grid (typically **1000 UPM** or **1024 UPM**). If converting manually or via script, apply the vertical reflection transformation matrix:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
[x']   [1   0] [x]   [   0    ]&lt;br&gt;
[y'] = [0  -1] [y] + [ascender]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

## 2. Non-Zero Winding vs Even-Odd: The Black Cutout Problem

Ever convert a search icon or a badge with a hole in the middle, only to find the hole filled with solid black? This happens because SVGs often rely on `fill-rule="evenodd"`, whereas TrueType font interpreters rely strictly on the **Non-Zero Winding Rule**:

* **SVG `evenodd`:** Draws a ray from any point to infinity. If the ray crosses an odd number of path segments, the point is inside (filled). If even, it is outside (hollow). Path direction does not matter.
* **TrueType Non-Zero Rule:** Clockwise contours define solid shapes; counter-clockwise contours define holes (or vice versa depending on winding mode). If an inner circle and an outer circle in your SVG both travel clockwise, TrueType considers both of them solid fills. The hole disappears.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
SVG (Even-Odd)                   TrueType (Winding Order)&lt;br&gt;
Outer: Clockwise                 Outer: Clockwise (+)&lt;br&gt;
Inner: Clockwise                 Inner: Counter-Clockwise (-)&lt;br&gt;
Result: Hole preserved           Result: Hole preserved ONLY IF reversed!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### The Fix

In Figma or Illustrator, run **Outline Stroke** followed by **Simplify Path / Flatten**. Alternatively, run an SVG pre-processor or a dedicated client-side converter that normalizes path direction before generating TTF curves.

---

## 3. B## 3. Bezier Curve Degree Mismatch (Cubic vs Quadratic)

SVG paths use **Cubic Bezier curves** (`C` or `S` path commands): each segment has two control points (CP1 and CP2). TrueType fonts (`.ttf`) natively support **Quadratic Bezier curves**: each segment has only **one** control point (CP).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
SVG Cubic Curve:       Start --- CP1 ------- CP2 --- End&lt;br&gt;
TTF Quadratic Curve:   Start ------- CP ------------ End&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
When converting Cubic to Quadratic:
1. The compiler must approximate every cubic curve by splitting it into multiple quadratic arcs.
2. If your SVG has redundant anchor points, sub-pixel curves, or complex clipping masks, this approximation explodes in size and introduces visual artifacts.

### Rules for Clean Conversion:
* Convert all strokes to outlined fills (`stroke-width` is ignored by font glyph tables).
* Keep your viewbox square (e.g., `24x24`, `32x32`, or `512x512`).
* Strip hidden `&amp;lt;defs&amp;gt;`, empty `&amp;lt;clipPath&amp;gt;`, and inline styling before compiling.

---

## 4. Production `@font-face` Integration

Once compiled to `.ttf` and converted to `.woff2` for web delivery, load your custom icon font with `font-display: block` to prevent layout shift:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
css&lt;br&gt;
@font-face {&lt;br&gt;
  font-family: 'DevOmniIcons';&lt;br&gt;
  src: url('/fonts/devomni-icons.woff2') format('woff2'),&lt;br&gt;
       url('/fonts/devomni-icons.ttf') format('truetype');&lt;br&gt;
  font-weight: normal;&lt;br&gt;
  font-style: normal;&lt;br&gt;
  font-display: block;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;.icon {&lt;br&gt;
  font-family: 'DevOmniIcons', sans-serif;&lt;br&gt;
  speak: never;&lt;br&gt;
  font-style: normal;&lt;br&gt;
  font-weight: normal;&lt;br&gt;
  font-variant: normal;&lt;br&gt;
  text-transform: none;&lt;br&gt;
  line-height: 1;&lt;br&gt;
  -webkit-font-smoothing: antialiased;&lt;br&gt;
  -moz-osx-font-smoothing: grayscale;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;/* Individual Glyphs mapped via Unicode PUA (Private Use Area) */&lt;br&gt;
.icon-search:before { content: "\e900"; }&lt;br&gt;
.icon-terminal:before { content: "\e901"; }&lt;br&gt;
.icon-lock:before { content: "\e902"; }&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Using the Unicode Private Use Area (`\e900` to `\f8ff`) prevents screen readers from trying to pronounce random alphanumeric characters when encountering icons.

---

## Testing Your SVGs in the Browser

If you need to batch-convert SVG icons into `.ttf` or `.woff2` without uploading your proprietary vectors to third-party cloud servers, test them directly in-browser:

-&amp;gt; **[SVG to Font Converter (Free &amp;amp; Private) - DevOmniTools](https://www.devomnitools.com/en/tools/svg-to-font/)**

It handles Y-axis inversion, path winding normalization, and 1000 UPM grid scaling entirely in client-side memory. You get instant live previews and copy-pasteable CSS code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>svg</category>
      <category>css</category>
      <category>frontend</category>
    </item>
    <item>
      <title>JWT Decoding vs Cryptographic Signature Verification: What Developers Can and Cannot Learn from a Token</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:20:28 +0000</pubDate>
      <link>https://dev.to/devomnitools/jwt-decoding-vs-cryptographic-signature-verification-what-developers-can-and-cannot-learn-from-a-4eai</link>
      <guid>https://dev.to/devomnitools/jwt-decoding-vs-cryptographic-signature-verification-what-developers-can-and-cannot-learn-from-a-4eai</guid>
      <description>&lt;p&gt;Last Tuesday, a staging auth bug had three devs stumped for half an afternoon.&lt;/p&gt;

&lt;p&gt;Nginx was spitting &lt;code&gt;401 Unauthorized&lt;/code&gt;. But the tester dumped their Bearer token into Chrome's DevTools console, ran &lt;code&gt;JSON.parse(atob(token.split('.')[1]))&lt;/code&gt;, and saw:&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;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_9912"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1791592000&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;The expiration was hours away. The role string was right.&lt;/p&gt;

&lt;p&gt;So why did the API gateway reject it?&lt;/p&gt;

&lt;p&gt;Because Base64URL decoding doesn't verify signatures. It just prints whatever text is encoded in the middle chunk. The actual signing key on the auth service had been rotated thirty minutes earlier, so every signature check was failing cold at the reverse proxy.&lt;/p&gt;

&lt;p&gt;I see this confusion constantly in backend code reviews. Developers assume that because a token decodes cleanly, it must be valid.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;Decoding a token and verifying a token are completely different operations. Conflating them is how privilege escalation bugs and auth bypasses make it to production.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Decoding Is Just String Unmasking (There Is Zero Encryption Here)
&lt;/h3&gt;

&lt;p&gt;Standard JSON Web Tokens (RFC 7519) aren't encrypted. Unless you specifically implement JWE (RFC 7516), your token is just three Base64URL strings separated by dots:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;header.payload.signature&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That middle chunk is plain text wrapped in Base64URL. You don't need private keys, certificates, or an auth server to read it. A ten-line script in Node or Python will extract the data instantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfOTkxMiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc5MTU5MjAwMH0.sample_signature_hash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Split and decode middle chunk&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decodedString&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Prints: { sub: "usr_9912", role: "admin", exp: 1791592000 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what we needed to get that data: nothing. No secrets, no certificates, no cryptographic hashing.&lt;/p&gt;

&lt;p&gt;Because of that, decoding only tells you what the payload claims to say. It tells you nothing about whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The token was actually created by your authentication server.&lt;/li&gt;
&lt;li&gt;An attacker tampered with the claims in transit.&lt;/li&gt;
&lt;li&gt;The signature matches the payload contents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a backend service accepts &lt;code&gt;role: admin&lt;/code&gt; just by reading the JSON payload without validating the cryptographic signature, an attacker can base64-encode whatever claims they want, slap on a random signature string, and your system will grant them full access.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. What Verification Actually Does Under the Hood
&lt;/h3&gt;

&lt;p&gt;Signature verification is pure mathematics, not string parsing.&lt;/p&gt;

&lt;p&gt;When your auth service issues a token, it glues the header and payload together:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;base64(header) + "." + base64(payload)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then it hashes those bytes using a cryptographic algorithm (HMAC-SHA256 for symmetric, or RSA/ECDSA for asymmetric) alongside a private key or secret. The resulting output is appended as the third part: the signature.&lt;/p&gt;

&lt;p&gt;When the token reaches your backend API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your server grabs the exact same incoming header and payload.&lt;/li&gt;
&lt;li&gt;It recomputes the cryptographic hash using its trusted key.&lt;/li&gt;
&lt;li&gt;It compares the newly generated hash against the signature attached to the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If an attacker changes even a single character in the payload—say, editing &lt;code&gt;"usr_9912"&lt;/code&gt; to &lt;code&gt;"usr_9913"&lt;/code&gt;—the recomputed hash won't match. The signature check fails immediately, and the request drops with a &lt;code&gt;401 Unauthorized&lt;/code&gt; before touching your database.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. The Classic Frontend Mistake: Leaking Symmetric Keys
&lt;/h3&gt;

&lt;p&gt;I still see teams try to "verify" tokens inside React, Vue, or mobile apps using symmetric HMAC keys (&lt;code&gt;HS256&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Don't do this.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;HS256&lt;/code&gt;, the exact same secret key is used to both sign and verify tokens. If you put that secret into your frontend build configuration, it gets compiled straight into your client-side JavaScript bundle. Any user can open DevTools, copy your secret, and mint their own valid tokens with arbitrary permissions for any account in your database.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Safe Split:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend clients (React, Vue, mobile apps):&lt;/strong&gt; Decode only. Use decoded claims strictly for cosmetic UI states—like showing the user's username or checking &lt;code&gt;exp&lt;/code&gt; to schedule a silent refresh call. Never make security decisions based on client-decoded data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend services &amp;amp; API gateways:&lt;/strong&gt; Verify the signature on every single incoming request using public keys (&lt;code&gt;RS256&lt;/code&gt;/&lt;code&gt;ES256&lt;/code&gt;) or server-side secrets (&lt;code&gt;HS256&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Two Implementation Flaws to Watch Out For
&lt;/h3&gt;

&lt;p&gt;If you write custom authentication middleware, keep an eye on these two traps:&lt;/p&gt;

&lt;h4&gt;
  
  
  Trap A: The &lt;code&gt;alg: "none"&lt;/code&gt; Bypass
&lt;/h4&gt;

&lt;p&gt;Older versions of libraries like &lt;code&gt;python-jwt&lt;/code&gt; and &lt;code&gt;node-jsonwebtoken&lt;/code&gt; had a critical design flaw: if an incoming token specified &lt;code&gt;"alg": "none"&lt;/code&gt; in the header, the parser skipped signature validation entirely and treated the token as valid. Attackers could forge admin tokens, strip off the signature, set &lt;code&gt;alg: "none"&lt;/code&gt;, and bypass auth completely.&lt;/p&gt;

&lt;p&gt;Modern libraries block this by default, but you should always hard-pin the accepted algorithms in your verifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;

&lt;span class="c1"&gt;# Always hard-pin algorithms on the verifier side
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;raw_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Pin strictly! Never let token choose.
&lt;/span&gt;        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iss&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpiredSignatureError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Trap B: Algorithm Confusion (RS256 vs HS256)
&lt;/h4&gt;

&lt;p&gt;If your auth service signs tokens with asymmetric RSA (&lt;code&gt;RS256&lt;/code&gt;), your API services verify using the RSA Public Key.&lt;/p&gt;

&lt;p&gt;In an algorithm confusion attack, an attacker takes your public key (which is publicly accessible), changes the token header from &lt;code&gt;RS256&lt;/code&gt; to &lt;code&gt;HS256&lt;/code&gt;, and signs the token using your public key string as an HMAC secret! If your backend verifier doesn't enforce the algorithm, it might treat the public key as raw HMAC bytes and validate the forged signature as legitimate.&lt;/p&gt;

&lt;p&gt;Always pass &lt;code&gt;algorithms=["RS256"]&lt;/code&gt; explicitly on your verifier. Never allow the token header to dictate which cryptographic algorithm to execute.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Debugging Tokens Locally Without Leaking Secrets
&lt;/h3&gt;

&lt;p&gt;During local development and testing, you frequently need to inspect token payloads, check &lt;code&gt;exp&lt;/code&gt; timestamps, and debug custom claims.&lt;/p&gt;

&lt;p&gt;Pasting real staging or production tokens into random web decoders is risky. If the site routes requests through a backend server that logs incoming payloads, active session tokens can end up in third-party cloud logs.&lt;/p&gt;

&lt;p&gt;If you need to inspect a token quickly in the browser without sending data across the network, you can use our in-browser tool:&lt;br&gt;
👉 &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools JWT Decoder&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs 100% locally in your browser memory using client-side JavaScript. Zero backend uploads, zero server requests, and zero remote logging.&lt;/p&gt;




&lt;h3&gt;
  
  
  Quick Architecture Checklist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Frontends decode tokens solely for UI rendering—never for authorization logic.&lt;/li&gt;
&lt;li&gt;Backends verify the cryptographic signature on every single protected API route.&lt;/li&gt;
&lt;li&gt;Never expose symmetric HMAC secrets inside client-side bundles or mobile binaries.&lt;/li&gt;
&lt;li&gt;Always hard-pin the allowed algorithms (&lt;code&gt;RS256&lt;/code&gt; or &lt;code&gt;HS256&lt;/code&gt;) in your verification middleware.&lt;/li&gt;
&lt;li&gt;Verify expiration (&lt;code&gt;exp&lt;/code&gt;), issuer (&lt;code&gt;iss&lt;/code&gt;), and audience (&lt;code&gt;aud&lt;/code&gt;) alongside the signature.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>python</category>
    </item>
    <item>
      <title>Why your cURL command works in terminal but fails in Python (4 gotchas that wasted my afternoon)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Tue, 29 Sep 2026 09:10:00 +0000</pubDate>
      <link>https://dev.to/devomnitools/why-your-curl-command-works-in-terminal-but-fails-in-python-4-gotchas-that-wasted-my-afternoon-3o1c</link>
      <guid>https://dev.to/devomnitools/why-your-curl-command-works-in-terminal-but-fails-in-python-4-gotchas-that-wasted-my-afternoon-3o1c</guid>
      <description>&lt;p&gt;We've all been there: you open Chrome DevTools, click &lt;strong&gt;Copy as cURL&lt;/strong&gt;, paste it into your terminal, and it works like a charm. &lt;/p&gt;

&lt;p&gt;Then you rewrite it into Python &lt;code&gt;requests&lt;/code&gt; or &lt;code&gt;httpx&lt;/code&gt;, hit run, and suddenly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP 400 Bad Request
{"error": "Malformed JSON payload"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or worse, a silent &lt;code&gt;HTTP 403 Forbidden&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I spent an hour yesterday debugging an internal webhook call that worked in bash but kept failing in our automated runner. Here are the 4 subtle edge-cases where translating cURL to Python breaks—and how to fix them.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. The Trailing Slash &amp;amp; Automated Redirect Trap
&lt;/h3&gt;

&lt;p&gt;When you run this in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/v1/auth &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"user":"test"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the endpoint expects &lt;code&gt;https://api.example.com/v1/auth/&lt;/code&gt; (with a trailing slash), modern curl silently follows or handles it depending on server config.&lt;/p&gt;

&lt;p&gt;In Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Silently drops POST body if server returns a 301/308 redirect!
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/v1/auth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, standard &lt;code&gt;requests.post()&lt;/code&gt; will follow a 301/302 redirect by downgrading the method to &lt;code&gt;GET&lt;/code&gt; and dropping the payload entirely! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Always verify whether the server enforces a trailing slash. If you need automatic redirect preservation, use a Session or inspect &lt;code&gt;r.history&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Double-Encoded JSON String vs. Raw Dict
&lt;/h3&gt;

&lt;p&gt;In curl, people often write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"items": [1, 2, 3], "active": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When porting to Python, junior devs often do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Double-stringification trap
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the mistake? Passing &lt;code&gt;json=json.dumps(...)&lt;/code&gt; serializes the string &lt;strong&gt;twice&lt;/strong&gt;. The server receives a string literal instead of a JSON object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;json=my_dict&lt;/code&gt; (requests handles serialization and headers for you).&lt;/li&gt;
&lt;li&gt;OR use &lt;code&gt;data=json.dumps(my_dict)&lt;/code&gt; with explicit &lt;code&gt;headers={'Content-Type': 'application/json'}&lt;/code&gt;. Never mix both.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. The Pseudo-Headers Copied from Chrome
&lt;/h3&gt;

&lt;p&gt;When you click "Copy as cURL" from browser DevTools, Chrome copies everything, including HTTP/2 pseudo-headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://service.com/api'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'sec-ch-ua: "Chromium";v="128"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'sec-fetch-dest: empty'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'sec-fetch-mode: cors'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'sec-fetch-site: same-origin'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept-Encoding: gzip, deflate, br, zstd'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you blindly paste all those headers into Python:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Some WAFs (like Cloudflare or Akamai) detect that the TLS fingerprint does NOT match a real browser Chrome TLS handshake, and they flag the request as a spoofed bot.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;Accept-Encoding: br&lt;/code&gt; (Brotli) or &lt;code&gt;zstd&lt;/code&gt; is sent, Python's &lt;code&gt;requests&lt;/code&gt; library cannot decode it natively unless you have &lt;code&gt;brotli&lt;/code&gt; installed, leaving you with raw binary gibberish in &lt;code&gt;r.text&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Strip out browser-specific &lt;code&gt;sec-ch-*&lt;/code&gt; headers and let Python handle encoding headers naturally. Keep only &lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Content-Type&lt;/code&gt;, and your custom headers.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Escaped Quotes in Bash Shells
&lt;/h3&gt;

&lt;p&gt;If your curl payload contains shell variables or nested quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;John's Report&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; https://api.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bash string escaping rules differ wildly from Python string escaping. Unescaping backslashes by hand on a 50-line payload is a recipe for syntax errors.&lt;/p&gt;




&lt;h3&gt;
  
  
  How I Handle This Now
&lt;/h3&gt;

&lt;p&gt;After hitting these gotchas one too many times during staging tests, I stopped doing manual string surgery. &lt;/p&gt;

&lt;p&gt;If you just want clean Python Requests code without spending 15 minutes stripping pseudo-headers and fixing JSON quotes, you can drop your curl command into this in-browser converter:&lt;br&gt;
👉 &lt;a href="https://www.devomnitools.com/en/tools/curl-converter/" rel="noopener noreferrer"&gt;DevOmniTools cURL to Python &amp;amp; Fetch Converter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs 100% in your local browser memory (zero server uploads, so no private API tokens leak into cloud logs) and cleans up the headers automatically.&lt;/p&gt;

&lt;p&gt;Hope this saves someone a headache next time a "working" cURL command refuses to run in production scripts!&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Decode &amp; Inspect X.509 SSL Certificates and CSRs Without OpenSSL (or Leaking Data to Unknown Servers)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Mon, 28 Sep 2026 08:10:49 +0000</pubDate>
      <link>https://dev.to/devomnitools/how-to-decode-inspect-x509-ssl-certificates-and-csrs-without-openssl-or-leaking-data-to-unknown-5398</link>
      <guid>https://dev.to/devomnitools/how-to-decode-inspect-x509-ssl-certificates-and-csrs-without-openssl-or-leaking-data-to-unknown-5398</guid>
      <description>&lt;p&gt;A few months back, I was helping migrate an internal API gateway to a fresh Kubernetes cluster. It was late on a Friday afternoon. We generated a new Certificate Signing Request (CSR), submitted it to our enterprise CA, wired up the issued certificate to the Ingress controller, and were immediately greeted by this lovely browser screen:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;NET::ERR_CERT_COMMON_NAME_INVALID&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Why? Because whoever generated the CSR forgot to include the wildcard subdomain in the &lt;strong&gt;Subject Alternative Names (SANs)&lt;/strong&gt; list.&lt;/p&gt;

&lt;p&gt;When you hit a roadblock like that, you usually have two choices:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fire up a terminal and fight with OpenSSL CLI flags (&lt;code&gt;openssl req -in request.csr -noout -text | grep -A 2 "Subject Alternative Name"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Or open a browser, Google &lt;strong&gt;"parse csr online"&lt;/strong&gt;, and paste your PEM block into the first result on Google.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most people do #2. But here is the problem: pasting internal certificates and CSRs into random websites is an operational security hazard waiting to happen.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Silent Problem with Online "Certificate Checkers"
&lt;/h2&gt;

&lt;p&gt;Even if your certificate only contains public keys, &lt;strong&gt;your certificate metadata is far from harmless.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you paste a certificate or CSR into an online decoder, three things often happen behind the scenes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Internal Network Topology Leakage
&lt;/h3&gt;

&lt;p&gt;Subject Alternative Names (SANs) don't just secure &lt;code&gt;example.com&lt;/code&gt;. In enterprise environments, they almost always list internal hostnames and infrastructure endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auth-internal.stg.corp.net&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;k8s-control-plane.vpn.company.internal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vault-cluster-01.eu-west-1.private&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The moment you click "Decode" on a server-rendered tool, that domain topology travels over an HTTP POST request, passes through a remote reverse proxy, and sits indefinitely in third-party access logs or APM traces (Datadog, CloudWatch, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The "Accidental Private Key" Disaster
&lt;/h3&gt;

&lt;p&gt;How many times have you opened a combined &lt;code&gt;.pem&lt;/code&gt; bundle that contained both the certificate chain and the private key?&lt;br&gt;
If someone blindly copies that buffer and hits "Paste" into an online tool that runs server-side parsing, that private key just left your perimeter.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Server-Side Execution Overhead
&lt;/h3&gt;

&lt;p&gt;Most online utilities are just thin wrappers: an Express or Flask backend that takes your string, writes it to a temporary file in &lt;code&gt;/tmp/cert.pem&lt;/code&gt;, runs &lt;code&gt;openssl x509 -text -noout&lt;/code&gt;, and pipes the stdout back as JSON. If that server gets compromised, every certificate ever submitted is sitting in temporary storage or shell history.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Actually Is an X.509 Certificate?
&lt;/h2&gt;

&lt;p&gt;To understand how to inspect certificates safely, you have to understand how they are built under the hood.&lt;/p&gt;

&lt;p&gt;An X.509 certificate is &lt;strong&gt;not text&lt;/strong&gt;. It is an &lt;strong&gt;ASN.1 (Abstract Syntax Notation One)&lt;/strong&gt; data structure serialized into a compact binary format called &lt;strong&gt;DER (Distinguished Encoding Rules)&lt;/strong&gt;, and then wrapped in Base64 with ASCII boundary headers to create a &lt;strong&gt;PEM file&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   ## What Actually Is an X.509 Certificate?

To understand how to inspect certificates safely, you have to understand how they are built under the hood.

An X.509 certificate is **not text**. It is an **ASN.1 (Abstract Syntax Notation One)** data structure serialized into a compact binary format called **DER (Distinguished Encoding Rules)**, and then wrapped in Base64 with ASCII boundary headers to create a **PEM file**:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text&lt;br&gt;
+--------------------------------------------------------+&lt;br&gt;
|                    PEM File (.crt)                     |&lt;br&gt;
|  -----BEGIN CERTIFICATE-----                           |&lt;br&gt;
|  MIID0zCCA3igAwIBAgIRANUJklyNsQ4ZE6Ohyxx7BaswCgYIK...  |&lt;br&gt;
|  -----END CERTIFICATE-----                             |&lt;br&gt;
+--------------------------+-----------------------------+&lt;br&gt;
                           | 1. Strip headers &amp;amp; Base64 decode&lt;br&gt;
                           v&lt;br&gt;
+--------------------------------------------------------+&lt;br&gt;
|                   Binary DER Stream                    |&lt;br&gt;
|  [0x30, 0x82, 0x03, 0xD3, 0x30, 0x82, 0x02, ...]      |&lt;br&gt;
+--------------------------+-----------------------------+&lt;br&gt;
                           | 2. Walk Tag-Length-Value (TLV)&lt;br&gt;
                           v&lt;br&gt;
+--------------------------------------------------------+&lt;br&gt;
|                 Parsed Certificate Tree                |&lt;br&gt;
|  |-- Version: v3 (0x02)                                |&lt;br&gt;
|  |-- Serial Number: 0d:51:92:5c:8d:b1:0e...            |&lt;br&gt;
|  |-- Signature Algorithm: ecdsa-with-SHA256            |&lt;br&gt;
|  |-- Issuer: CN=WE1, O=Google Trust Services, C=US     |&lt;br&gt;
|  |-- Validity Period:                                  |&lt;br&gt;
|  |    |-- Not Before: 2026-09-05 22:29:39 UTC          |&lt;br&gt;
|  |    |-- Not After:  2026-12-04 23:29:33 UTC          |&lt;br&gt;
|  |-- Subject: CN=cloudflare.com                        |&lt;br&gt;
|  |-- Extensions:                                       |&lt;br&gt;
|       |-- Subject Alternative Names (SANs):            |&lt;br&gt;
|            |-- DNS: cloudflare.com                     |&lt;br&gt;
|            |-- DNS: *.cloudflare.com                   |&lt;br&gt;
|            |-- DNS: *.secondary.cloudflare.com         |&lt;br&gt;
+--------------------------------------------------------+&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

## How ASN.1 Tag-Length-Value (TLV) Works in Practice

Every element in a binary DER stream is structured ## How ASN.1 Tag-Length-Value (TLV) Works in Practice

Every element in a binary DER stream is structured as a **Tag-Length-Value (TLV)** triplet:

1. **Tag (1 Byte):** Tells the parser what type of data is coming up:
   - `0x30`: SEQUENCE (a container holding other fields)
   - `0x02`: INTEGER (serial numbers, version numbers)
   - `0x03`: BIT STRING (public keys, digital signatures)
   - `0x06`: OBJECT IDENTIFIER (OID, e.g. `2.5.4.3` for Common Name)
   - `0x13` / `0x0C`: PrintableString / UTF8String (human-readable names)
   - `0x17` / `0x18`: UTCTime / GeneralizedTime (expiration dates)

2. **Length:**
   - If length is &amp;lt; 128 bytes: The length is stored directly in a single byte (`0x00` to `0x7F`).
   - If length is &amp;gt;= 128 bytes: The highest bit is set (`0x80`). The first byte tells you how many subsequent bytes hold the length value (e.g. `0x82` means the next two bytes represent the length).

3. **Value:** The raw payload bytes.

### Decoding DER Bytes in Browser Memory (TypeScript)

Here is how you can strip PEM armor and parse the raw binary DER bytes entirely in-memory:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
typescript&lt;br&gt;
// 1. Strip ASCII PEM boundaries and whitespace&lt;br&gt;
export function pemToBinary(pem: string): Uint8Array {&lt;br&gt;
  const base64 = pem&lt;br&gt;
    .replace(/-----BEGIN [^-]+-----/g, '')&lt;br&gt;
    .replace(/-----END [^-]+-----/g, '')&lt;br&gt;
    .replace(/\s+/g, '');&lt;/p&gt;

&lt;p&gt;const rawString = atob(base64);&lt;br&gt;
  const bytes = new Uint8Array(rawString.length);&lt;br&gt;
  for (let i = 0; i &amp;lt; rawString.length; i++) {&lt;br&gt;
    bytes[i] = rawString.charCodeAt(i);&lt;br&gt;
  }&lt;br&gt;
  return bytes;&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
typescript&lt;br&gt;
// 2. Recursive Tag-Length-Value (TLV) walker&lt;br&gt;
export interface ASN1Node {&lt;br&gt;
  tag: number;&lt;br&gt;
  length: number;&lt;br&gt;
  headerLen: number;&lt;br&gt;
  raw: Uint8Array;&lt;br&gt;
  children: ASN1Node[];&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export function parseASN1(bytes: Uint8Array, offset = 0): ASN1Node {&lt;br&gt;
  const tag = bytes[offset];&lt;br&gt;
  let lenByte = bytes[offset + 1];&lt;br&gt;
  let length = 0;&lt;br&gt;
  let headerLen = 2;&lt;/p&gt;

&lt;p&gt;// Multi-byte length handler&lt;br&gt;
  if (lenByte &amp;amp; 0x80) {&lt;br&gt;
    const numBytes = lenByte &amp;amp; 0x7f;&lt;br&gt;
    headerLen += numBytes;&lt;br&gt;
    for (let i = 0; i &amp;lt; numBytes; i++) {&lt;br&gt;
      length = (length &amp;lt;&amp;lt; 8) | bytes[offset + 2 + i];&lt;br&gt;
    }&lt;br&gt;
  } else {&lt;br&gt;
    length = lenByte;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;const raw = bytes.slice(offset + headerLen, offset + headerLen + length);&lt;br&gt;
  const node: ASN1Node = { tag, length, headerLen, raw, children: [] };&lt;/p&gt;

&lt;p&gt;// If this node is a SEQUENCE (0x30), unpack its nested children&lt;br&gt;
  if (tag === 0x30) {&lt;br&gt;
    let childOffset = offset + headerLen;&lt;br&gt;
    const end = childOffset + length;&lt;br&gt;
    while (childOffset &amp;lt; end) {&lt;br&gt;
      const child = parseASN1(bytes, childOffset);&lt;br&gt;
      node.children.push(child);&lt;br&gt;
      childOffset += child.headerLen + child.length;&lt;br&gt;
    }&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return node;&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
---

## Extracting What Really Matters: SANs &amp;amp; Fingerprints

### 1. Subject Alternative Names (SANs) ## Extracting What Really Matters: SANs &amp;amp; Fingerprints

### 1. Subject Alternative Names (SANs) - OID `2.5.29.17`
Since RFC 2818, browsers completely ignore the `Common Name (CN)` for hostname validation. Only the **Subject Alternative Names (SANs)** extension matters.

In ASN.1 DER, each SAN entry has a context-specific tag:
- Tag `0x82` = `dNSName` (e.g. `*.example.com`)
- Tag `0x87` = `iPAddress` (e.g. `192.168.1.1`)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
typescript&lt;br&gt;
export function extractSANs(sanExtensionRaw: Uint8Array): string[] {&lt;br&gt;
  const sans: string[] = [];&lt;br&gt;
  const sequence = parseASN1(sanExtensionRaw);&lt;/p&gt;

&lt;p&gt;for (const item of sequence.children) {&lt;br&gt;
    // 0x82 is context tag [2] for dNSName&lt;br&gt;
    if (item.tag === 0x82) {&lt;br&gt;
      sans.push(new TextDecoder().decode(item.raw));&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
  return sans;&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### 2. Computing SHA-256 Fingerprints in the Browser
You don't need OpenSSL to compute a certificate fingerprint. The native browser **W3C Web Cryptography API (`crypto.subtle`)** computes it with hardware acceleration in milliseconds:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
typescript&lt;br&gt;
export async function getFingerprint(derBytes: Uint8Array): Promise {&lt;br&gt;
  const hashBuffer = await crypto.subtle.digest('SHA-256', derBytes);&lt;br&gt;
  return Array.from(new Uint8Array(hashBuffer))&lt;br&gt;
    .map(byte =&amp;gt; byte.toString(16).padStart(2, '0'))&lt;br&gt;
    .join(':')&lt;br&gt;
    .toUpperCase();&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This produces the exact same fingerprint string as running:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
bash&lt;br&gt;
openssl x509 -noout -fingerprint -sha256 -in cert.crt&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


---

## The Zero-Exfiltration Approach: Inspecting Offline

Because remembering OpenSSL syntax is tedious when you are trying to resolve an outage quickly, we built an entirely client-side inspection tool:

## The Zero-Exfiltration Approach: Inspecting Offline

Because remembering OpenSSL syntax is tedious when you are trying to resolve an outage quickly, we built an entirely client-side inspection tool:

**[DevOmniTools X.509 Certificate &amp;amp; CSR Decoder](https://www.devomnitools.com/en/tools/x509-certificate-decoder/)**

### Why we built it this way:
1. **Zero Network Traffic:** The parser runs 100% inside your browser's local JavaScript execution engine. You can literally disconnect your Wi-Fi or turn on Airplane Mode, and it parses full X.509 certs and CSRs instantaneously.
2. **Immediate Expiration Calculation:** Live calculation showing whether a certificate is active, expiring soon, or already past its `Not After` date.
3. **Full CSR Parsing:** Inspect public keys, signature algorithms, and SANs in a `.csr` *before* paying a Certificate Authority or waiting hours for a re-issuance.
4. **Air-Gapped Friendly:** Built for engineers on restricted VPNs or secure networks where sending data to random external servers is strictly forbidden by policy.

If you want to read our full deep-dive on offline certificate inspection algorithms and RFC standards, check out the engineering guide:  
**[How to Decode X.509 Certificates Offline - Comprehensive Specification](https://www.devomnitools.com/en/solutions/decode-x509-certificate-offline/)**

---

## Quick Reference Cheat Sheet

| Task | OpenSSL CLI Command | DevOmniTools Equivalent |
|---|---|---|
| **View Expiration Dates** | `openssl x509 -noout -dates -in cert.crt` | Instant countdown badge &amp;amp; validity panel |
| **Check SANs** | `openssl x509 -noout -text -in cert.crt \| grep -A 2 "Subject Alternative Name"` | Dedicated SANs tag list |
| **Inspect a CSR** | `openssl req -noout -text -verify -in req.csr` | Paste `.csr` PEM directly into decoder |
| **SHA-256 Fingerprint** | `openssl x509 -noout -fingerprint -sha256 -in cert.crt` | Hardware WebCrypto SHA-256 hash |

---

How does your team handle certificate and CSR validation before deploying to staging or production? Have you ever had an outage caused by an expired cert or a missing SAN? Let me know in the comments!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
      <category>ssl</category>
    </item>
    <item>
      <title>How to Convert SVGs into Clean Web Icon Fonts Entirely in the Browser (No Node, No Canvas, No Server Uploads)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Sat, 26 Sep 2026 12:05:56 +0000</pubDate>
      <link>https://dev.to/devomnitools/how-to-convert-svgs-into-clean-web-icon-fonts-entirely-in-the-browser-no-node-no-canvas-no-4ap8</link>
      <guid>https://dev.to/devomnitools/how-to-convert-svgs-into-clean-web-icon-fonts-entirely-in-the-browser-no-node-no-canvas-no-4ap8</guid>
      <description>&lt;p&gt;A while back I needed a custom icon font for a side project. Nothing fancy — maybe 30 icons, all SVGs I had already exported from Figma. The "proper" way to do this is apparently to install some Node toolchain, run a CLI, and hope the output isn't garbage. I didn't want any of that. My whole app runs client-side, so why should the font pipeline need a server?&lt;/p&gt;

&lt;p&gt;Turns out, you can build a real, valid TTF icon font in pure browser JavaScript. No canvas, no Node, no uploading your SVGs to someone's server. This post walks through exactly how I did it, with working code.&lt;/p&gt;

&lt;p&gt;If you're in a hurry, I ended up polishing the whole thing into a tool you can use right now: &lt;a href="https://devomnitools.com/en/tools/svg-to-font/" rel="noopener noreferrer"&gt;devomnitools.com/en/tools/svg-to-font/&lt;/a&gt;. But stick around if you want to know how it actually works under the hood — it's less magic than you'd think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother with icon fonts at all in 2026?
&lt;/h2&gt;

&lt;p&gt;Fair question. SVG sprites and inline SVGs are great, and for most cases I'd reach for those first. But icon fonts still win in a few specific situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need legacy / standalone embedded icon support&lt;/li&gt;
&lt;li&gt;You're theming via &lt;code&gt;color&lt;/code&gt; and &lt;code&gt;font-size&lt;/code&gt; — with icon fonts, &lt;code&gt;currentColor&lt;/code&gt; just works, no CSS variables gymnastics&lt;/li&gt;
&lt;li&gt;Huge icon sets where inline SVGs would bloat your HTML DOM for no reason&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, honestly, it's just a fun engineering problem to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two libraries that make this possible
&lt;/h2&gt;

&lt;p&gt;Everything hinges on two battle-tested libraries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;svgpath&lt;/code&gt;&lt;/strong&gt; — parses SVG path data (&lt;code&gt;d&lt;/code&gt; attributes) and lets you transform it mathematically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;opentype.js&lt;/code&gt;&lt;/strong&gt; — a full font compiler that runs directly in JavaScript. It can build fonts from scratch, not just read them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No canvas needed, because we never rasterize anything — a font glyph is vector data, so the conversion is mostly just moving numbers around.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Get clean path data from your SVGs
&lt;/h3&gt;

&lt;p&gt;The first gotcha: an icon font only understands path outlines. Your SVG might contain &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;rect&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;line&amp;gt;&lt;/code&gt;, strokes with round caps — a font glyph has none of that vocabulary. Everything must be a single filled path (or a few paths combined).&lt;/p&gt;

&lt;p&gt;If you're exporting from Figma/Illustrator/Inkscape, use &lt;strong&gt;"Outline Stroke"&lt;/strong&gt; before export. In Figma it's &lt;code&gt;Shift + Ctrl/Cmd + O&lt;/code&gt;. Do this before exporting, and 90% of your pain disappears.&lt;/p&gt;

&lt;p&gt;Then, in the browser, read the file and extract all &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;svgToPathData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DOMParser&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;parseFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/svg+xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: no &amp;lt;path&amp;gt; elements found. Outline your strokes first!`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// multiple subpaths are fine in one glyph&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; If your SVG has transforms on the paths (&lt;code&gt;transform="rotate(...)"&lt;/code&gt; etc.), you need to apply them to the path data or your icons will come out rotated/skewed. The &lt;code&gt;svgpath&lt;/code&gt; library can do this, but cleaning up in your vector editor beforehand is usually less debugging.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 2: Normalize everything to the em square
&lt;/h3&gt;

&lt;p&gt;Fonts are drawn on an em square — typically 1000 units tall for TTFs. Your SVGs have their own random &lt;code&gt;viewBox&lt;/code&gt;, maybe &lt;code&gt;24x24&lt;/code&gt;, maybe &lt;code&gt;48x48&lt;/code&gt;. If you skip this step, every glyph will be a different size and your icons will look misaligned.&lt;/p&gt;

&lt;p&gt;So for each icon: read the &lt;code&gt;viewBox&lt;/code&gt;, compute the scale factor, and resize with &lt;code&gt;svgpath&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;svgpath&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/svgpath&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,,&lt;/span&gt; &lt;span class="nx"&gt;vbW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vbH&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewBox&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vbW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vbH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;svgpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// flip Y to font coordinate space&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One thing that bit me:&lt;/strong&gt; SVG's Y-axis points down, but font coordinates point up! For a 24-unit &lt;code&gt;viewBox&lt;/code&gt; mapped to a 1000-unit em, you flip with &lt;code&gt;.translate(0, EM_SIZE).scale(1, -1)&lt;/code&gt;. If your icons render upside down (they will the first time), that's why!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 3: Build glyphs and compile the font
&lt;/h3&gt;

&lt;p&gt;Now the fun part. &lt;code&gt;opentype.js&lt;/code&gt; lets you define glyphs as path strings and compiles a valid TTF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;opentype&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/opentype.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;icons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// icons = [{ name: "home", pathData: "M0 0L..." }, ...]&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notdef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Glyph&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.notdef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unicode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;advanceWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;glyphs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;icons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codepoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0xE000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Private Use Area — no emoji conflicts&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Glyph&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;unicode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;codepoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;advanceWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opentype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromSVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Font&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;familyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyIcons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;styleName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Regular&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unitsPerEm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ascender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EM_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;descender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EM_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;glyphs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;notdef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;glyphs&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// font.toArrayBuffer() gives you raw TTF bytes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download it directly in the browser as a Blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;downloadFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font/ttf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-icons.ttf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 4: Actually use the font with generated CSS
&lt;/h3&gt;

&lt;p&gt;Generate a tiny CSS file alongside the TTF, mapping each icon to its Unicode codepoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateCss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;icons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;icons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xE000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`.icon-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:before { content: "&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"; }`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
@font-face {
  font-family: "MyIcons";
  src: url("my-icons.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
}

[class^="icon-"]:before, [class*=" icon-"]:before {
  font-family: "MyIcons";
  display: inline-block;
  speak: never; /* don't let screen readers read out codepoints */
}

&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in HTML it's just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-settings"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"font-size: 24px; color: rebeccapurple;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Where this approach falls short (being honest)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No auto-hinting:&lt;/strong&gt; Dedicated desktop font tools hint glyphs for tiny render sizes. For icons inside modern high-DPI displays this rarely matters, but text fonts would suffer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compound paths:&lt;/strong&gt; Overlapping paths with &lt;code&gt;fill-rule="evenodd"&lt;/code&gt; can render unexpectedly in some legacy renderers. Always test your exported icons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WOFF2 output:&lt;/strong&gt; Not built into &lt;code&gt;opentype.js&lt;/code&gt; — you get TTF and WOFF. For most icon sets, TTF + WOFF is plenty for browser usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No advanced font mastering:&lt;/strong&gt; Kerning tables, ligatures, variable axes are out of scope. It's an icon font, not Helvetica!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Export SVGs with strokes outlined.&lt;/li&gt;
&lt;li&gt;Parse with &lt;code&gt;DOMParser&lt;/code&gt;, extract &lt;code&gt;d&lt;/code&gt; attributes.&lt;/li&gt;
&lt;li&gt;Normalize to a 1000-unit em square with &lt;code&gt;svgpath&lt;/code&gt; (and flip the Y axis!).&lt;/li&gt;
&lt;li&gt;Compile with &lt;code&gt;opentype.js&lt;/code&gt; glyphs.&lt;/li&gt;
&lt;li&gt;Download as a blob, ship with a generated CSS file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole pipeline is under 150 lines of vanilla JavaScript.&lt;/p&gt;

&lt;p&gt;If you'd rather skip the plumbing and just get a font, my finished version handles multi-file upload, naming, live glyph preview, and CSS generation — it's free and everything stays in your browser with zero server data leakage:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://devomnitools.com/en/tools/svg-to-font/" rel="noopener noreferrer"&gt;devomnitools.com/en/tools/svg-to-font/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have you built anything with &lt;code&gt;opentype.js&lt;/code&gt;? I keep finding new uses for it — font subsetting in the browser is probably my next rabbit hole. Let me know in the comments what you'd build!&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>svg</category>
    </item>
    <item>
      <title>Stop Pasting Your JWT Tokens Into Random Websites — I Built a Safer Alternative</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:29:28 +0000</pubDate>
      <link>https://dev.to/devomnitools/stop-pasting-your-jwt-tokens-into-random-websites-i-built-a-safer-alternative-4m6e</link>
      <guid>https://dev.to/devomnitools/stop-pasting-your-jwt-tokens-into-random-websites-i-built-a-safer-alternative-4m6e</guid>
      <description>&lt;p&gt;Every developer uses online utilities daily:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgm6jfk6ue0cv93ylk6sd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgm6jfk6ue0cv93ylk6sd.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JSON formatters, JWT decoders, SQL beautifiers, Base64 encoders, regex testers, and password generators.&lt;/p&gt;

&lt;p&gt;But there's a dirty secret hiding behind most of these "free" online tools: your sensitive data is being sent to a remote backend server every single time you hit that format button.&lt;/p&gt;

&lt;p&gt;Your JWT token with its payload claims? Logged. Your proprietary SQL schema? Stored. Your AES encryption key? Sent over the wire.&lt;/p&gt;

&lt;p&gt;That frustration became the foundation of DevOmniTools — 56+ developer utilities where zero bytes of your sensitive data ever leave your browser.&lt;/p&gt;

&lt;p&gt;🚨 The Problem: Your Data Is Not as Safe as You Think&lt;br&gt;
Privacy Problem — Traditional Tools vs DevOmniTools&lt;/p&gt;

&lt;p&gt;Most developers don't think twice about pasting a JWT token, a database connection string, or an API key into an online formatter. But the backend of these sites receives and often logs every request.&lt;/p&gt;

&lt;p&gt;Consider what you're actually exposing:&lt;/p&gt;

&lt;p&gt;JWT tokens → contain user identity, roles, expiry claims&lt;br&gt;
SQL queries → reveal your database schema and table structures&lt;br&gt;
AES keys and salts → the literal secrets protecting your encrypted data&lt;br&gt;
Bcrypt hashes → password verification data&lt;br&gt;
The fix isn't to stop using tools. The fix is to use tools that never touch a server.&lt;/p&gt;

&lt;p&gt;⚡ The Architecture: 100% Client-Side Execution&lt;br&gt;
Tech Stack Architecture&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzz0rui0djx61zn4wvjy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzz0rui0djx61zn4wvjy.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every single tool in DevOmniTools runs entirely within the browser's local sandbox using native browser APIs:&lt;/p&gt;

&lt;p&gt;🔐 Security &amp;amp; Cryptography&lt;br&gt;
Tool    Browser API Used&lt;br&gt;
AES-256-GCM Encrypt/Decrypt SubtleCrypto (Web Crypto API)&lt;br&gt;
Bcrypt Hash Generator   Client-side WASM bcrypt&lt;br&gt;
JWT Decoder &amp;amp; Inspector Local Base64 parsing&lt;br&gt;
SHA-256 / SHA-512 Hashing   SubtleCrypto.digest()&lt;br&gt;
HMAC Generator  SubtleCrypto.sign()&lt;br&gt;
Not a single byte of your keys, tokens, or hashes reaches our servers. Because we don't have any.&lt;/p&gt;

&lt;p&gt;💻 Data &amp;amp; Schema Converters&lt;br&gt;
SQL Formatter (PostgreSQL, MySQL, SQLite, IBM Db2) — Client-side AST parser&lt;br&gt;
JSON ↔ YAML ↔ XML — In-browser bidirectional parsers&lt;br&gt;
JSON to TypeScript — Recursive interface inference, all local&lt;br&gt;
SQL to JSON/CSV — Multi-row INSERT parser, no backend&lt;br&gt;
🎨 Design &amp;amp; Vector Tools&lt;br&gt;
SVG to PNG Rasterizer — HTML5 Canvas with up to 8K print quality&lt;br&gt;
CSS Glassmorphism Generator — Pure visual builder&lt;br&gt;
CSS Flexbox Playground — Interactive drag stage, exports Tailwind tokens&lt;br&gt;
Color Contrast Checker — WCAG AAA / APCA compliance, all local&lt;br&gt;
🛠️ Why Astro? The Performance Decision&lt;br&gt;
When I chose the framework for this project, the requirements were clear:&lt;/p&gt;

&lt;p&gt;Zero unnecessary JavaScript for non-interactive pages&lt;br&gt;
Sub-100ms Time to Interactive for tool pages&lt;br&gt;
Static HTML output for maximum CDN cacheability&lt;br&gt;
Astro delivers all three. It generates pure static HTML at build time and ships zero JavaScript to pages that don't need it. For tool pages, only the minimum interactive script ships — no framework runtime overhead.&lt;/p&gt;

&lt;p&gt;Framework:    Astro 7 (Static Site Generation)&lt;br&gt;
Styling:      Tailwind CSS 4&lt;br&gt;
Computation:  Web Workers (heavy tasks off main thread)&lt;br&gt;
Crypto:       SubtleCrypto (Web Crypto API)&lt;br&gt;
Testing:      Vitest — 440+ unit tests&lt;br&gt;
Hosting:      Cloudflare Pages (global edge network)&lt;br&gt;
The result: tools that respond in milliseconds, not seconds.&lt;/p&gt;

&lt;p&gt;🌍 Multilingual From Day One&lt;br&gt;
Developer tools are used globally. DevOmniTools launched with 4 complete locales:&lt;/p&gt;

&lt;p&gt;🇺🇸 English → devomnitools.com/en/&lt;br&gt;
🇸🇦 Arabic (RTL) → devomnitools.com/ar/&lt;br&gt;
🇪🇸 Spanish → devomnitools.com/es/&lt;br&gt;
🇩🇪 German → devomnitools.com/de/&lt;br&gt;
Every tool — its UI, meta descriptions, error messages, and SEO tags — is fully translated. Not just the landing pages, but the actual utility interfaces themselves.&lt;/p&gt;

&lt;p&gt;Building RTL (Right-to-Left) support properly in Astro was one of the most interesting challenges. Arabic layout flipping for a complex multi-panel tool UI requires careful CSS logical properties usage throughout.&lt;/p&gt;

&lt;p&gt;✅ Quality: 440+ Automated Tests&lt;br&gt;
Before any tool ships, it must pass its full test suite.&lt;/p&gt;

&lt;p&gt;With 56 tools across 4 locales, that's a lot of surface area for edge cases. Vitest runs the full suite on every commit, covering:&lt;/p&gt;

&lt;p&gt;Parser accuracy (malformed JSON, nested YAML, multi-dialect SQL)&lt;br&gt;
Crypto correctness (known-good vectors for AES, SHA, HMAC)&lt;br&gt;
Unicode handling (Arabic, German umlauts, special characters)&lt;br&gt;
Edge inputs (empty strings, deeply nested structures, large files)&lt;br&gt;
442 tests. 442 passed. Every time.&lt;/p&gt;

&lt;p&gt;🚀 What's Live Right Now&lt;br&gt;
56+ production tools across these categories:&lt;/p&gt;

&lt;p&gt;Security &amp;amp; Cryptography: AES-256-GCM, Bcrypt, JWT Decoder, SHA Hashing, HMAC, Password Entropy Tester, CSP Builder, X.509 Certificate Decoder, JWT Signature Verifier&lt;/p&gt;

&lt;p&gt;Data &amp;amp; Code: JSON Formatter, YAML↔JSON, SQL Formatter, XML Inspector, JSON→TypeScript, SQL→JSON/CSV, Markdown→HTML, Markdown Table Generator, Code Minifier, Base64 Encoder/Decoder&lt;/p&gt;

&lt;p&gt;Design &amp;amp; Visual: QR Code Generator, SVG Rasterizer, Flexbox Playground, Glassmorphism Generator, Clip-Path Generator, Color Contrast Checker, Color Converter&lt;/p&gt;

&lt;p&gt;DevOps &amp;amp; Infra: Cron Builder, Docker Compose Generator, Docker Run→Compose Converter, Docker Compose→K8s Converter, Regex Tester&lt;/p&gt;

&lt;p&gt;Generators &amp;amp; Calculators: UUID Generator, Loan EMI Calculator, Unix Timestamp Converter, Prompt Token Counter&lt;/p&gt;

&lt;p&gt;💬 I'd Love Your Feedback&lt;br&gt;
The platform is completely free and open for the community:&lt;/p&gt;

&lt;p&gt;🔗 Live Site: &lt;a href="https://devomnitools.com" rel="noopener noreferrer"&gt;https://devomnitools.com&lt;/a&gt; ⭐ Community Repo: github.com/contactumairrana/omnitools-community&lt;/p&gt;

&lt;p&gt;A few questions for the community:&lt;/p&gt;

&lt;p&gt;Which utility do you reach for most often in your daily workflow?&lt;br&gt;
Any tool or format converter you wish existed but can't find a trustworthy client-side version of?&lt;br&gt;
RTL developers — what specific challenges have you faced with developer tools lacking Arabic/RTL support?&lt;br&gt;
Drop your answers below! Every piece of feedback directly shapes the next tool that gets built. 🚀&lt;/p&gt;

&lt;p&gt;Built with Astro, deployed on Cloudflare Pages, tested with Vitest. No backend. No tracking. No cookies. Just tools.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Why I built 56+ Developer Tools that run 100% Client-Side (Zero Server Logs)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Tue, 15 Sep 2026 15:57:44 +0000</pubDate>
      <link>https://dev.to/devomnitools/why-i-built-56-developer-tools-that-run-100-client-side-zero-server-logs-4epd</link>
      <guid>https://dev.to/devomnitools/why-i-built-56-developer-tools-that-run-100-client-side-zero-server-logs-4epd</guid>
      <description>&lt;p&gt;Every developer uses online utilities daily: JSON formatters, JWT decoders, SQL beautifiers, regex testers, and base64 encoders.&lt;/p&gt;

&lt;p&gt;However, a serious issue persists across most popular utility sites: &lt;strong&gt;your data gets sent to a remote backend server.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether it's an authorization bearer token, a proprietary database schema, or client configuration secrets, pasting sensitive data into third-party web tools is an unnecessary security hazard.&lt;/p&gt;

&lt;p&gt;That is why I set out to build &lt;strong&gt;&lt;a href="https://devomnitools.com" rel="noopener noreferrer"&gt;DevOmniTools&lt;/a&gt;&lt;/strong&gt; — a comprehensive suite of 56+ developer tools where &lt;strong&gt;zero bytes of sensitive data leave your browser.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ The Architecture: 100% In-Browser Execution
&lt;/h2&gt;

&lt;p&gt;To ensure absolute privacy and sub-millisecond responsiveness, every tool relies entirely on local browser primitives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security &amp;amp; Cryptography:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JWT Inspector:&lt;/strong&gt; Decodes headers and claims instantly in the client sandbox with a live token expiration countdown timer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AES-256-GCM &amp;amp; Bcrypt:&lt;/strong&gt; Uses the native &lt;code&gt;SubtleCrypto&lt;/code&gt; (Web Crypto API) and client-side salt hashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashing:&lt;/strong&gt; MD5, SHA-1, SHA-256, and SHA-512 calculated locally via Web Workers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data &amp;amp; Schema Conversions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL Formatter &amp;amp; Converters:&lt;/strong&gt; Multi-dialect SQL formatter (PostgreSQL, MySQL, SQLite, Db2) and SQL-to-JSON/CSV converters that run entirely via client-side AST parsers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON / YAML / XML:&lt;/strong&gt; Real-time bi-directional syntax validators and converters.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Design &amp;amp; Vector Suite:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SVG to Vector Rasterizer:&lt;/strong&gt; High-multiplier (up to 8K print quality) rendering using HTML5 Canvas sandboxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS Glassmorphism &amp;amp; Flexbox Playground:&lt;/strong&gt; Live interactive stage exporting pure CSS and Tailwind tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚡ Tech Stack &amp;amp; Performance Decisions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; — generates pure static HTML with zero unnecessary runtime JavaScript overhead for non-interactive content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; Modern Tailwind CSS with dark-mode first contrast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Execution:&lt;/strong&gt; Web Workers for heavy computational jobs (encryption/large file parsing) to keep the main thread fluid at 60 FPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; 440+ automated unit tests powered by Vitest to guarantee parser accuracy.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌐 Community &amp;amp; Open Source
&lt;/h2&gt;

&lt;p&gt;We also maintain an open community repository where developers can contribute new tool ideas or inspect the ecosystem architecture:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://github.com/contactumairrana/omnitools-community" rel="noopener noreferrer"&gt;GitHub Community Repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out the live platform here:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://devomnitools.com" rel="noopener noreferrer"&gt;https://devomnitools.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I'd love your feedback:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Which utility tool do you find yourself needing most frequently in your daily workflow?&lt;/li&gt;
&lt;li&gt;Any specific tool or format you'd like to see added next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your thoughts and feature requests below! 🚀&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
