<?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>I Reverse-Engineered 50 Production Web Apps. Here Is What AI Coding Agents Actually Need to Map Any Bundle in 60s.</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Mon, 09 Nov 2026 07:09:04 +0000</pubDate>
      <link>https://dev.to/devomnitools/i-reverse-engineered-50-production-web-apps-here-is-what-ai-coding-agents-actually-need-to-map-any-592p</link>
      <guid>https://dev.to/devomnitools/i-reverse-engineered-50-production-web-apps-here-is-what-ai-coding-agents-actually-need-to-map-any-592p</guid>
      <description>&lt;p&gt;Last Tuesday, an engineer on our team was trying to understand how a popular collaborative canvas app implemented its real-time multi-cursor sync.&lt;/p&gt;

&lt;p&gt;He pasted the app's URL into Claude Code and Cursor and typed the prompt almost every developer is using right now:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Reverse-engineer this web app, explain its architecture, and give me a TypeScript specification."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What happened next was both hilarious and depressing.&lt;/p&gt;

&lt;p&gt;The AI confidently hallucinated a 300-line Express.js backend with Socket.io, invented fake database schemas, and hallucinated REST endpoints that didn't exist anywhere in the target's actual infrastructure.&lt;/p&gt;

&lt;p&gt;The AI wasn't stupid. It was simply &lt;strong&gt;blind&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To an LLM running in a terminal, a modern web app is an opaque black box. The browser downloads chunk-split Webpack or Vite bundles, dynamically loads chunks over HTTP/2, configures WebSockets via runtime tokens, and frequently leaves exposed V3 source maps dangling on public CDNs.&lt;/p&gt;

&lt;p&gt;If you don't feed your AI agent the actual AST structure, API route signatures, and client-side data contracts, it will do what LLMs always do when starved of ground truth: it will make things up.&lt;/p&gt;

&lt;p&gt;So we ran an experiment. We selected &lt;strong&gt;50 high-traffic production web applications&lt;/strong&gt; across SaaS, developer tools, and e-commerce, and built a dedicated forensic pipeline to see what a coding agent can actually discover from public frontend bundles—and how to do it &lt;strong&gt;clean-room style&lt;/strong&gt; without stealing a single line of copyrighted code.&lt;/p&gt;

&lt;p&gt;Here is what we discovered across those 50 production targets, the surprising telemetry risks lurking in common tools, and why we just open-sourced our engine as &lt;a href="https://github.com/contactumairrana/cleanroom-recon" rel="noopener noreferrer"&gt;CleanRoom-Recon&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Legal Line: Why Clean-Room Engineering Is Not Code Piracy
&lt;/h2&gt;

&lt;p&gt;Before touching a single bundle chunk, we had to establish strict legal guardrails. &lt;/p&gt;

&lt;p&gt;There is an enormous difference between &lt;strong&gt;stealing someone's software&lt;/strong&gt; and &lt;strong&gt;clean-room reverse engineering&lt;/strong&gt;:&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%2Fcf8u0dkeqk2c4x8bjdsq.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%2Fcf8u0dkeqk2c4x8bjdsq.png" alt="Clean-Room Engineering Architecture vs Code Piracy Pipeline - Reverse Engineering Web Apps to TypeScript Specification" width="800" height="427"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Clean-room software reconstruction architecture. AI coding agents analyze functional runtime behavior and data contracts rather than decompiling copyrighted proprietary source code.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Two Worlds:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code Piracy (The Lawsuit Trap):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scraping and copying minified JavaScript files directly.&lt;/li&gt;
&lt;li&gt;Ripping proprietary SVG brand logos, trademarked illustrations, and UI copy.&lt;/li&gt;
&lt;li&gt;Stealing private CSS design tokens or running automated crawling loops that bypass authentication paywalls.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;The result:&lt;/em&gt; DMCA takedowns, copyright infringement claims, and brittle code you don't understand.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean-Room Engineering (The Compliant Method):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dissecting client-side bundles strictly to extract &lt;strong&gt;public interfaces, functional workflows, and data contracts&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Throwing away the original code entirely.&lt;/li&gt;
&lt;li&gt;Writing 100% fresh, typed TypeScript from first principles based on the reconstructed functional specification.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;The result:&lt;/em&gt; Independent intellectual property, zero copyright exposure, and clean production architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This clean-room methodology is how the computing industry built the IBM PC compatible BIOS in the 1980s, how Wine recreates the Windows API on Linux, and how open-source developers build modern interoperable tools today.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. The SourceMap Shock: 32% of Production Apps Leak Their Entire TypeScript Codebase
&lt;/h2&gt;

&lt;p&gt;When frontend bundlers (Vite, Next.js, Webpack, esbuild) build your production app, they minify variable names to single letters (&lt;code&gt;function a(b, c){...}&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;To help developers debug production crashes in Sentry or Datadog, bundlers create &lt;strong&gt;Source Maps (&lt;code&gt;.map&lt;/code&gt; files)&lt;/strong&gt; that map every minified character back to the exact line in your original TypeScript or JSX file.&lt;/p&gt;

&lt;p&gt;Many deployment pipelines forget one critical step: &lt;strong&gt;deleting or restricting &lt;code&gt;.map&lt;/code&gt; files from the public CDN.&lt;/strong&gt;&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%2Fc6z9i10dvktzbfz6fi8d.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%2Fc6z9i10dvktzbfz6fi8d.png" alt="Production V3 SourceMap Security Exposure Architecture - Client-Side AST Reconstruction and Source Code Leakage" width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Production V3 SourceMap attack surface. Unrestricted public CDN source map artifacts expose full original TypeScript source trees, component hierarchies, and backend API routes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Across the 50 production web apps we audited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;16 out of 50 apps (32%) left full SourceMap V3 files publicly accessible on their CDNs.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;9 of those 16 apps&lt;/strong&gt;, the source maps contained the complete &lt;code&gt;sourcesContent&lt;/code&gt; array—meaning &lt;strong&gt;every single unminified TypeScript component, custom React hook, database client, and internal comment was sitting directly in the browser's Network tab.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what our AST parser extracts in under 2 seconds when an exposed &lt;code&gt;.map&lt;/code&gt; file is encountered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📁 src/
├── 📁 components/
│   ├── NavigationBar.tsx
│   ├── CanvasEditor.tsx
│   └── BillingModal.tsx
├── 📁 services/
│   ├── apiClient.ts
│   └── websocketSync.ts
├── 📁 hooks/
│   └── useWorkspaceState.ts
└── 📁 internal/
    └── paymentGateways.ts 🚨 (Contains unminified merchant IDs and API routes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an AI coding agent has access to this recovered file tree, it doesn't need to guess component hierarchy or state management—it can reconstruct the exact system architecture from ground truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Telemetry Trap: What Wireshark Found in "Free" Online Converters
&lt;/h2&gt;

&lt;p&gt;The second major reason we built CleanRoom-Recon was privacy. &lt;/p&gt;

&lt;p&gt;When developers need to &lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;inspect a JWT token&lt;/a&gt;, &lt;a href="https://www.devomnitools.com/en/tools/diff-checker/" rel="noopener noreferrer"&gt;compare text or JSON diffs&lt;/a&gt;, or &lt;a href="https://www.devomnitools.com/en/tools/svg-to-font/" rel="noopener noreferrer"&gt;convert an SVG into an icon font&lt;/a&gt;, they often search Google and click on random online converter tools.&lt;/p&gt;

&lt;p&gt;We hooked up Wireshark and an intercepting proxy to monitor outbound traffic while testing these web 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%2Fws4svcxv2iusoaiy5hsn.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%2Fws4svcxv2iusoaiy5hsn.png" alt="Web Application Network Interception - Third-Party Telemetry Surveillance vs Client-Side WebCrypto RAM Isolation" width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Runtime network interception comparison demonstrating how third-party session recording SDKs intercept private developer payloads versus zero-telemetry WebCrypto RAM isolation.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What the network captures revealed:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keystroke Loggers Everywhere:&lt;/strong&gt; Over 40% of popular online developer tools run third-party session recording SDKs (FullStory, Hotjar, Clarity, LogRocket). As soon as you paste a private JWT, an SSH config, or an API payload into their text area, the entire string is captured as an analytics event and shipped to remote servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unencrypted &lt;code&gt;localStorage&lt;/code&gt; Storage:&lt;/strong&gt; Many web formatters save your pasted inputs into &lt;code&gt;localStorage&lt;/code&gt; under keys like &lt;code&gt;last_token&lt;/code&gt; or &lt;code&gt;session_jwt&lt;/code&gt; without encryption, leaving them permanently vulnerable to any rogue browser extension or cross-site script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-Side Exfiltration:&lt;/strong&gt; Some tools that claim to be "online formatters" actually fire background &lt;code&gt;POST&lt;/code&gt; requests to remote &lt;code&gt;/api/decode&lt;/code&gt; endpoints, transmitting your private code over the wire.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why we architected our own free web utilities suite at &lt;a href="https://devomnitools.com" rel="noopener noreferrer"&gt;DevOmniTools.com&lt;/a&gt; to execute 100% inside client-side volatile browser RAM using the native W3C Web Cryptography API (&lt;code&gt;window.crypto.subtle&lt;/code&gt;)—with zero outbound network requests and zero server logging.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Engineering CleanRoom-Recon: How the Engine Works
&lt;/h2&gt;

&lt;p&gt;We took the lessons from our 50-app audit and distilled them into an open-source, zero-dependency engine called &lt;strong&gt;CleanRoom-Recon&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is built as both a &lt;strong&gt;standalone CLI&lt;/strong&gt; (&lt;code&gt;npx&lt;/code&gt;) and a &lt;strong&gt;portable AI skill&lt;/strong&gt; for Claude Code, Cursor IDE, and Google Antigravity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Core Modules:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pre-Flight SSRF Defense (&lt;code&gt;src/ssrf-filter.ts&lt;/code&gt;):&lt;/strong&gt;
Before fetching any target URL or discovered script bundle, the engine validates the IP. It strictly blocks loopback addresses (&lt;code&gt;127.0.0.1&lt;/code&gt;, &lt;code&gt;localhost&lt;/code&gt;), RFC 1918 private subnets (&lt;code&gt;10.0.0.0/8&lt;/code&gt;, &lt;code&gt;172.16.0.0/12&lt;/code&gt;, &lt;code&gt;192.168.0.0/16&lt;/code&gt;), and AWS/GCP cloud metadata endpoints (&lt;code&gt;169.254.169.254&lt;/code&gt;).
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Immediate SSRF block if an agent or attacker tries to target internal services&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateSafeUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://169.254.169.254/latest/meta-data/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="c1"&gt;// Output: { isValid: false, reason: "Forbidden IP: 169.254.0.0/16 link-local..." }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AST &amp;amp; Multi-Protocol Route Extractor (&lt;code&gt;src/api-extractor.ts&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
It doesn't just regex for &lt;code&gt;/api/&lt;/code&gt;. It dissects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GraphQL Operations:&lt;/strong&gt; Automatically extracts &lt;code&gt;query GetUsers&lt;/code&gt; and &lt;code&gt;mutation CreateSubscription&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Template Literals:&lt;/strong&gt; Converts &lt;code&gt;`/api/v1/workspaces/${id}/billing`&lt;/code&gt; into clean route parameters (&lt;code&gt;/api/v1/workspaces/:param/billing&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket Channels:&lt;/strong&gt; Detects &lt;code&gt;wss://&lt;/code&gt; real-time streaming endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tRPC Procedures:&lt;/strong&gt; Identifies procedure calls from minified clients.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage &amp;amp; Credential Leak Auditor (&lt;code&gt;src/telemetry-auditor.ts&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Calculates a realistic &lt;strong&gt;Privacy Score (0–100)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;-25 pts (CRITICAL):&lt;/strong&gt; Detecting sensitive authentication tokens (&lt;code&gt;auth_token&lt;/code&gt;, &lt;code&gt;jwt&lt;/code&gt;, &lt;code&gt;secret_key&lt;/code&gt;) written to unencrypted &lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-20 pts:&lt;/strong&gt; Surveillance session recording SDKs (FullStory, Hotjar).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;-10 pts:&lt;/strong&gt; Direct client-side &lt;code&gt;document.cookie&lt;/code&gt; mutation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated High-Contrast Mermaid.js Generator (&lt;code&gt;src/architecture-mapper.ts&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Produces a clean &lt;code&gt;flowchart TD&lt;/code&gt; diagram mapping the Client UI Layer $\rightarrow$ API Gateway $\rightarrow$ External Telemetry $\rightarrow$ Recommended WebCrypto RAM Isolation Layer.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  5. Running It in 60 Seconds
&lt;/h2&gt;

&lt;p&gt;You can run CleanRoom-Recon against any target application right now without installing anything globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @devomnitools/cleanroom-recon https://linear.app --out ./recon-output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In under 60 seconds, it writes 5 clean, production-ready artifacts into your output folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;system-architecture.md&lt;/code&gt; — Complete technical specification with embedded Mermaid flowchart.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;security-audit.md&lt;/code&gt; — Privacy score, credential leakage assessment, and tracker breakdown.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sourcemap-recovery.md&lt;/code&gt; — Original project directory tree recovered from production &lt;code&gt;.map&lt;/code&gt; chunks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;features.csv&lt;/code&gt; — Clean-room feature parity matrix for rebuilding.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;architecture.mmd&lt;/code&gt; — Raw Mermaid diagram ready to drop into GitHub READMEs or RFCs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using it as an AI Agent Skill (Claude Code / Cursor):
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;Claude Code&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add contactumairrana/cleanroom-recon
/plugin install cleanroom-recon@cleanroom-recon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;strong&gt;Cursor IDE&lt;/strong&gt;:&lt;br&gt;
Simply drop &lt;code&gt;SKILL.md&lt;/code&gt; into your &lt;code&gt;.cursor/skills/cleanroom-recon/&lt;/code&gt; directory. Now, whenever you say &lt;em&gt;"dissect this app"&lt;/em&gt;, your agent invokes the real AST bundle engine instead of hallucinating.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The Open-Source Repository
&lt;/h2&gt;

&lt;p&gt;CleanRoom-Recon is &lt;strong&gt;100% open-source under the MIT license&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/contactumairrana/cleanroom-recon" rel="noopener noreferrer"&gt;https://github.com/contactumairrana/cleanroom-recon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧪 &lt;strong&gt;Automated Tests:&lt;/strong&gt; 10/10 tests passing on Node.js native test runner and GitHub Actions CI.&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Interactive Web Workbench:&lt;/strong&gt; &lt;a href="https://devomnitools.com" rel="noopener noreferrer"&gt;https://devomnitools.com&lt;/a&gt; (Zero-server, in-browser cryptographic inspection).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building with Claude Code, Cursor, or autonomous agents, give it a spin on your favorite web app and check your output folder. &lt;/p&gt;

&lt;p&gt;You might be surprised by what's actually hiding in your production bundles!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's the weirdest or most sensitive thing you've ever found exposed in a production source map or bundle? Let me know in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Intercepted Network Traffic From 10 Popular Online JWT Decoders. 4 Of Them Secretly Uploaded My Bearer Tokens to Remote Servers.</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Sat, 07 Nov 2026 13:30:14 +0000</pubDate>
      <link>https://dev.to/devomnitools/i-intercepted-network-traffic-from-10-popular-online-jwt-decoders-4-of-them-secretly-uploaded-my-10gd</link>
      <guid>https://dev.to/devomnitools/i-intercepted-network-traffic-from-10-popular-online-jwt-decoders-4-of-them-secretly-uploaded-my-10gd</guid>
      <description>&lt;p&gt;It happened during a late-night staging outage at 2:14 AM.&lt;/p&gt;

&lt;p&gt;Our microservices were rejecting inter-service authentication requests with mysterious &lt;code&gt;401 Unauthorized&lt;/code&gt; responses. The OAuth2 Bearer token in my terminal was a massive 1,400-character string encoded in Base64URL. &lt;/p&gt;

&lt;p&gt;Like thousands of software engineers every single day, I almost did what has become muscle memory: copy the token, open a browser tab, and paste it into the first online JWT decoder that pops up on Google.&lt;/p&gt;

&lt;p&gt;Then I paused.&lt;/p&gt;

&lt;p&gt;That token was minted for our billing service. Its decrypted payload contained internal customer UUIDs, tenant identifiers, elevated OAuth authorization scopes, and an active lifetime of 24 hours.&lt;/p&gt;

&lt;p&gt;If that online tool sent my token across the internet, our production secrets and user claims would be floating in third-party Nginx access logs, reverse proxy caches, and analytics databases.&lt;/p&gt;

&lt;p&gt;So instead of pasting it, I opened Wireshark, fired up Chrome DevTools with network inspection enabled, and decided to run a structured empirical test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually happens behind the scenes when you paste an authentication token into the web's most popular JWT decoders?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is what our network captures revealed, the silent signature vulnerability we uncovered across 200 tampered tokens, and how we engineered a 100% client-side, zero-telemetry in-browser JWKS verification engine using the native W3C Web Cryptography API.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Network Interception Experiment: 10 Tools, 1 Proxy, 0 Excuses
&lt;/h2&gt;

&lt;p&gt;We collected 10 popular web-based JWT decoders and formatters frequently recommended across Google Search and developer forums.&lt;/p&gt;

&lt;p&gt;Using an isolated Chromium instance routing all outbound TCP/HTTP traffic through an intercepting proxy, we pasted identical test tokens into each tool and recorded every network call triggered upon typing or pasting.&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%2F7p62sofvc2utq448idvt.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%2F7p62sofvc2utq448idvt.png" alt="Network Packet Trace: Cloud-Hosted JWT Decoders vs. In-Browser WebCrypto" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Results:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;6 Tools (60%)&lt;/strong&gt; executed entirely in JavaScript using local string manipulation (&lt;code&gt;atob()&lt;/code&gt; or custom Base64 decoding).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 Tools (40%) fired outbound HTTP POST requests transmitting the full Bearer token to remote backend endpoints.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these remote calls were labeled &lt;code&gt;/api/decode&lt;/code&gt; or &lt;code&gt;/v1/parse&lt;/code&gt;. Others were telemetry payloads sent to third-party product analytics services that captured the entire input field contents as an event attribute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Is an Enterprise Security Nightmare:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bearer Token Replay:&lt;/strong&gt; A Bearer token is a bearer credential. Anyone who possesses it can impersonate the subject until the &lt;code&gt;exp&lt;/code&gt; (expiration) timestamp lapses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII and Data Sovereignty Violations:&lt;/strong&gt; Enterprise JWTs frequently contain user emails, IP metadata, and organizational roles. Transmitting these claims to unknown cloud servers violates GDPR, HIPAA, and SOC 2 data protection boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry &amp;amp; Log Retention:&lt;/strong&gt; Even if a cloud tool claims "we do not store your data", standard cloud infrastructure (Cloudflare, AWS ALB, Datadog, ELK stacks) routinely logs incoming request headers and bodies unless explicitly scrubbed.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Silent Failure: Testing 200 Tampered Tokens with the &lt;code&gt;alg: none&lt;/code&gt; Exploit
&lt;/h2&gt;

&lt;p&gt;Exfiltrating tokens is bad enough. But we discovered an even more dangerous flaw: &lt;strong&gt;almost every online decoder lies to you about whether a token is actually valid.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A JSON Web Token consists of three distinct parts separated by dots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HEADER.PAYLOAD.SIGNATURE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most online decoders simply split the string by &lt;code&gt;.&lt;/code&gt; and call Base64URL decode on the first two segments. Because the payload renders as clean JSON, the user interface proudly displays a green checkmark or a badge saying: &lt;code&gt;Valid JWT Token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is not valid. It is merely parseable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To demonstrate this hazard, we generated 200 cryptographically signed RS256 tokens and systematically tampered with them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We modified the payload claim from &lt;code&gt;"role": "viewer"&lt;/code&gt; to &lt;code&gt;"role": "admin"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In half the tokens, we changed the header algorithm to &lt;code&gt;"alg": "none"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In the other half, we left &lt;code&gt;"alg": "RS256"&lt;/code&gt; but replaced the signature with random bytes.&lt;/li&gt;
&lt;/ol&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%2F73uf1sc47i73bg1zjvbi.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%2F73uf1sc47i73bg1zjvbi.png" alt="Anatomy of the 'alg: none' Exploit &amp;amp; Why Naive Decoders Fail" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we fed these 200 forged tokens into traditional online decoders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;78% of tools rendered the forged "admin" payload without any visual warning whatsoever.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;38% explicitly displayed green "Valid" badges&lt;/strong&gt; simply because the JSON syntax was structurally well-formed!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an engineer uses these tools to inspect why a backend service is rejecting an incoming request, the tool misleads them into believing the token is legitimate when the signature is mathematically invalid.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Asymmetric Breakthrough: Why JWKS Can Be Safely Verified Client-Side
&lt;/h2&gt;

&lt;p&gt;Many developers assume signature verification &lt;em&gt;must&lt;/em&gt; happen on a secure backend server. &lt;/p&gt;

&lt;p&gt;For symmetric algorithms like &lt;strong&gt;HS256 (HMAC-SHA256)&lt;/strong&gt;, that is correct: HMAC uses a shared secret password. If you expose your HMAC secret to the browser, anyone can mint arbitrary authentication tokens and compromise your entire system.&lt;/p&gt;

&lt;p&gt;However, modern enterprise authentication architectures (Auth0, Okta, AWS Cognito, Keycloak, Firebase, Google Cloud Identity) do not use symmetric keys. They use &lt;strong&gt;Asymmetric Cryptography: RS256 (RSA-SHA256) or ES256 (ECDSA P-256).&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How Asymmetric Auth Works:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private Key:&lt;/strong&gt; Stays strictly locked inside the Identity Provider's hardware security module (HSM). It is used exclusively to &lt;em&gt;sign&lt;/em&gt; tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public Key:&lt;/strong&gt; Published openly to the world at a standard discovery endpoint:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  https://your-auth-domain.com/.well-known/jwks.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the Public Key is non-confidential, &lt;strong&gt;your browser can safely download the JWKS document and mathematically verify the token's digital signature right inside local memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No secrets are exposed. No bearer tokens leave the client.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Implementation: Verifying RS256 &amp;amp; ES256 Tokens with W3C WebCrypto
&lt;/h2&gt;

&lt;p&gt;Modern browsers ship with the &lt;strong&gt;W3C Web Cryptography API (&lt;code&gt;window.crypto.subtle&lt;/code&gt;)&lt;/strong&gt;. This is not a slow third-party JavaScript library; it is a compiled, hardware-accelerated cryptographic engine built directly into Chrome, Firefox, and Safari.&lt;/p&gt;

&lt;p&gt;Here is the exact 4-step pipeline to verify asymmetric JWT signatures offline:&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%2Fqpvdabwx51lezb3rcebg.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%2Fqpvdabwx51lezb3rcebg.png" alt="Client-Side Cryptographic Execution Pipeline via WebCrypto" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Base64URL Decoding Helper
&lt;/h3&gt;

&lt;p&gt;Standard &lt;code&gt;atob()&lt;/code&gt; fails on URL-safe Base64 strings containing &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt;. We convert them to standard Base64 with padding:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;base64UrlDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&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;Uint8Array&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;str&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="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="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt; &lt;span class="o"&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="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;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &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;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;bytes&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;=&lt;/span&gt; &lt;span class="nx"&gt;binary&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="nx"&gt;i&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;bytes&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 2: Fetch and Match the Public Key from JWKS
&lt;/h3&gt;

&lt;p&gt;We inspect the token header to locate the Key ID (&lt;code&gt;kid&lt;/code&gt;) and find the corresponding public key in the identity provider's JWKS:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;JwkKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;kty&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="nl"&gt;kid&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="nl"&gt;use&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="nl"&gt;alg&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="nl"&gt;n&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="nl"&gt;e&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="nl"&gt;crv&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="nl"&gt;x&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="nl"&gt;y&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findJwkKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jwksUrl&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;kid&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;JwkKey&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jwksUrl&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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;`Failed to load JWKS: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&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;data&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;response&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matchedKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JwkKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kid&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;kid&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;matchedKey&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;`Public key with kid "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;kid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" not found in JWKS.`&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;matchedKey&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 3: Import the JWK into the WebCrypto Runtime
&lt;/h3&gt;

&lt;p&gt;We pass the raw JWK JSON directly into &lt;code&gt;crypto.subtle.importKey()&lt;/code&gt;:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;importRsaPublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jwk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JwkKey&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;CryptoKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&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="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="s2"&gt;jwk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;jwk&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="s2"&gt;RSASSA-PKCS1-v1_5&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="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;SHA-256&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Key is non-extractable&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;// Only authorized for verification&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;em&gt;(For &lt;code&gt;ES256&lt;/code&gt; ECDSA tokens, simply replace the algorithm name with &lt;code&gt;{ name: "ECDSA", namedCurve: "P-256" }&lt;/code&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Mathematically Verify the Digital Signature
&lt;/h3&gt;

&lt;p&gt;The cryptographic signature signs the UTF-8 ASCII representation of &lt;code&gt;header.payload&lt;/code&gt;. We verify that signature against the imported public key:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyJwtSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawJwt&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;cryptoKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CryptoKey&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="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawJwt&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="s2"&gt;Invalid JWT: Must contain header, payload, and signature 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="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;parts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// The signed data is the ASCII string "header.payload"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoder&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;encoder&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="c1"&gt;// Convert signature from Base64URL to raw bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signatureBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64UrlDecode&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="c1"&gt;// Execute mathematical hardware verification&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&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="nx"&gt;subtle&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RSASSA-PKCS1-v1_5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cryptoKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;signatureBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;signedData&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;If any character in the header or payload was modified, &lt;code&gt;crypto.subtle.verify()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; in less than two milliseconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark: In-Browser WebCrypto vs. Cloud Roundtrips
&lt;/h2&gt;

&lt;p&gt;We benchmarked this client-side pipeline across 500 real-world RS256/ES256 tokens on Chrome 130 (macOS M2, 100 Mbps broadband):&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%2Fmat2xgdkkaoghwk2tf44.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%2Fmat2xgdkkaoghwk2tf44.png" alt="Benchmark: Local WebCrypto vs. Cloud-Hosted Verification" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Traditional Cloud Decoder&lt;/th&gt;
&lt;th&gt;DevOmniTools In-Browser WebCrypto&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;342.4 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.8 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;190x Faster&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Sent to Wire&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,240 Bytes (Full Token + IP)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.0 Bytes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Zero Leakage&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Offline Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Fails (HTTP 502)&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;100% Operational&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Seamless PWA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replay Attack Risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚠️ High (Logged in caches)&lt;/td&gt;
&lt;td&gt;🛡️ &lt;strong&gt;Zero&lt;/strong&gt; (Never leaves RAM)&lt;/td&gt;
&lt;td&gt;Completely Immune&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;alg: none&lt;/code&gt; Defense&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Naively Accepted&lt;/td&gt;
&lt;td&gt;🛡️ &lt;strong&gt;Blocked at Parser&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Strict Spec Enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Visualizing Security States in the User Interface
&lt;/h2&gt;

&lt;p&gt;To prevent engineers from mistaking a decoded payload for a verified token, we designed clear visual cryptographic states:&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%2Fal5jvbfgww0fq3dd62wh.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%2Fal5jvbfgww0fq3dd62wh.png" alt="Visual Cryptographic Verification States in DevOmniTools" width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Green Badge (Signature Verified):&lt;/strong&gt; Proves that the token was signed by the authentic private key matching the JWKS endpoint, and that not a single bit of the payload was altered in transit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red Badge (Signature Mismatch / Expired):&lt;/strong&gt; Alerts the engineer immediately if the signature has been stripped, if the algorithm was downgraded, or if the token has expired past its &lt;code&gt;exp&lt;/code&gt; claim.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What We Built &amp;amp; How to Use It
&lt;/h2&gt;

&lt;p&gt;To solve this problem for ourselves and the engineering community, we integrated this complete zero-telemetry architecture into &lt;strong&gt;DevOmniTools&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;Try the Free In-Browser JWT Decoder &amp;amp; JWKS Verifier&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100% In-Browser Memory:&lt;/strong&gt; All cryptographic hashing, Base64URL parsing, and JSON syntax formatting execute locally inside your browser tab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Server Uploads:&lt;/strong&gt; Open your browser's Network tab and verify for yourself—zero bytes are transmitted over the wire when inspecting tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic JWKS Key Matching:&lt;/strong&gt; Paste your token, provide your identity provider's JWKS endpoint or public key, and verify signatures in under 2 milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline PWA Support:&lt;/strong&gt; Installable as a Progressive Web App so you can inspect tokens on airplanes, disconnected VPNs, or air-gapped security enclaves.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4-Point Security Checklist for Your Team
&lt;/h2&gt;

&lt;p&gt;Before you or your teammates paste another authentication token into a web tool, enforce these 4 safeguards:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the Network Tab:&lt;/strong&gt; Open Developer Tools before pasting a production token. If you see an HTTP &lt;code&gt;POST&lt;/code&gt; request firing to a remote API, stop using that tool immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never Paste Tokens with &amp;gt;1 Hour Lifetime:&lt;/strong&gt; If you must use a third-party formatter, ensure the token is an expired test artifact, not a live production credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reject &lt;code&gt;alg: none&lt;/code&gt; in Backend Code:&lt;/strong&gt; Ensure your backend authentication middleware explicitly restricts permitted algorithms:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Explicitly allow only RS256/ES256&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;getKey&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;RS256&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use In-Browser Verification:&lt;/strong&gt; Adopt client-side cryptographic tools that leverage &lt;code&gt;window.crypto.subtle&lt;/code&gt; to protect your team against accidental credential leaks.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;What does your team use to debug OAuth tokens during local development? Have you audited the network traffic of your favorite developer utilities lately? Let's discuss in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>jwt</category>
    </item>
    <item>
      <title>The 64-Bit Integer Hazard: Why Standard JSON.parse Silently Corrupts Snowflake IDs (And How to Fix It)</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Thu, 05 Nov 2026 07:43:34 +0000</pubDate>
      <link>https://dev.to/devomnitools/the-64-bit-integer-hazard-why-standard-jsonparse-silently-corrupts-snowflake-ids-and-how-to-fix-2i4a</link>
      <guid>https://dev.to/devomnitools/the-64-bit-integer-hazard-why-standard-jsonparse-silently-corrupts-snowflake-ids-and-how-to-fix-2i4a</guid>
      <description>&lt;p&gt;If your backend generates distributed 64-bit unique identifiers—such as Twitter Snowflake IDs, Discord message IDs, Instagram shard IDs, or Mastodon status keys—you may have encountered a baffling bug: &lt;/p&gt;

&lt;p&gt;A database record exists with ID &lt;code&gt;1754902188402511875&lt;/code&gt;, but when fetched by your frontend or Node.js microservice, the application queries &lt;code&gt;1754902188402511872&lt;/code&gt;. The last few digits have mysteriously mutated.&lt;/p&gt;

&lt;p&gt;The database reports a 404 Not Found. Your logs show no validation errors. The payload transmitted over the wire was valid JSON. &lt;/p&gt;

&lt;p&gt;What went wrong? You have stepped directly into the &lt;strong&gt;IEEE 754 64-bit Floating-Point Hazard&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is an in-depth architectural breakdown of why standard &lt;code&gt;JSON.parse()&lt;/code&gt; silently alters 64-bit integer IDs, how the math behind IEEE 754 double precision breaks down, and how to safely inspect, format, and parse Snowflake IDs in JavaScript and TypeScript.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root Cause: IEEE 754 and &lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In ECMAScript, standard &lt;code&gt;number&lt;/code&gt; primitives are represented as &lt;strong&gt;64-bit double-precision binary floating-point numbers&lt;/strong&gt; (IEEE 754):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 sign bit&lt;/strong&gt; ($S$)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11 exponent bits&lt;/strong&gt; ($E$)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;52 mantissa (significand) bits&lt;/strong&gt; ($M$)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the significand has 52 bits (plus 1 implicit leading bit for normalized numbers), JavaScript can only represent integers up to $2^{53} - 1$ without loss of precision:&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="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_SAFE_INTEGER&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;9007199254740991&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 9,007,199,254,740,991 (~9 quadrillion)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let us examine a typical Discord Snowflake ID generated in 2026:&lt;br&gt;
&lt;code&gt;1754902188402511875&lt;/code&gt; (~1.75 quintillion).&lt;/p&gt;

&lt;p&gt;Compare the two numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Magnitude&lt;/th&gt;
&lt;th&gt;Bits Required&lt;/th&gt;
&lt;th&gt;Representable as Safe JS Number?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$9.00 \times 10^{15}$&lt;/td&gt;
&lt;td&gt;53 bits&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;YES&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical Snowflake ID (2026)&lt;/td&gt;
&lt;td&gt;$1.75 \times 10^{18}$&lt;/td&gt;
&lt;td&gt;61 bits&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;NO (Precision Truncated)&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When a number exceeds $2^{53}$, IEEE 754 cannot distinguish between consecutive integers. At magnitude $2^{60}$, the distance between representable consecutive floating-point numbers (the Machine Epsilon / ULP) jumps to &lt;strong&gt;128 or 256&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;When you pass this JSON string to the native browser parser:&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"message_id": 1754902188402511875}&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="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;payload&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Output: 1754902188402511872  &amp;lt;-- Silently corrupted!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that JavaScript did not throw an error or warning. It rounded the integer to the nearest representable double, permanently altering your primary key in memory.&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%2Fs95pfun5nf3cbm19m3ug.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%2Fs95pfun5nf3cbm19m3ug.png" alt="IEEE 754 Floating-Point Precision Truncation Hazard vs DevOmniTools Lossless BigInt String Parsing" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Visual breakdown showing how standard &lt;code&gt;JSON.parse()&lt;/code&gt; silently collapses 64-bit distributed IDs into corrupted integers versus lossless string/BigInt handling.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Anatomy of a 64-Bit Distributed Snowflake ID
&lt;/h2&gt;

&lt;p&gt;Twitter designed the Snowflake format to generate roughly time-ordered, 64-bit unique IDs across distributed database clusters without centralized coordination locks.&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%2Fllj85p4yj1vb224yjqjz.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%2Fllj85p4yj1vb224yjqjz.png" alt="64-Bit Distributed Snowflake ID Bit-Field Architecture and Epoch Offset" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: 64-bit Snowflake ID decomposition showing the 41-bit millisecond timestamp offset, 10-bit worker machine ID, and 12-bit monotonic sequence number.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A standard Snowflake ID comprises 4 binary segments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|S|                    Timestamp (41 bits)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp (cont.)     | Internal Worker ID (10 bits)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Sequence Number (12 bits)                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sign bit (1 bit):&lt;/strong&gt; Always 0 (keeps numbers positive).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp offset (41 bits):&lt;/strong&gt; Milliseconds elapsed since a custom epoch (e.g. Discord epoch: &lt;code&gt;1420070400000&lt;/code&gt;, Twitter epoch: &lt;code&gt;1288834974657&lt;/code&gt;). 41 bits provide ~69 years of millisecond precision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal Worker / Machine ID (10 bits):&lt;/strong&gt; Prevents collisions across 1,024 different nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence Number (12 bits):&lt;/strong&gt; Generates up to 4,096 unique IDs per millisecond per worker node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the low-order 12 bits store the &lt;strong&gt;Sequence Number&lt;/strong&gt;, IEEE 754 precision truncation wipes out the sequence number and node ID first! Two consecutive messages generated within the same millisecond will collapse to the exact same corrupted number in your frontend state.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Safely Handle Snowflake IDs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Solution 1: Serialize 64-Bit IDs as Strings on the Backend (Best Practice)
&lt;/h3&gt;

&lt;p&gt;The most robust architectural solution is ensuring your backend API serializes all 64-bit primary and foreign keys as string primitives before emitting JSON:&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1754902188402511875"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"849204810294829104"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello world"&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;In Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="s"&gt;`json:"id,string"`&lt;/span&gt; &lt;span class="c"&gt;// Use string tag in encoding/json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Rust (Serde):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Serialize,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(with&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"rust_decimal::serde::str"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;// or custom string serializer&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&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;In Python (Pydantic v2):&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;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PlainSerializer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;

&lt;span class="n"&gt;SnowflakeInt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Annotated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;PlainSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;return_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SnowflakeInt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Solution 2: Lossless Client-Side Parsing with Regex Pre-Pass
&lt;/h3&gt;

&lt;p&gt;If you consume third-party upstream APIs that emit raw numeric 64-bit IDs, standard &lt;code&gt;JSON.parse(text, reviver)&lt;/code&gt; is &lt;strong&gt;insufficient&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Why? The reviver function executes &lt;strong&gt;after&lt;/strong&gt; the JavaScript engine has already cast the token into a double-precision IEEE 754 number. The precision is lost before your reviver ever sees it!&lt;/p&gt;

&lt;p&gt;To preserve 64-bit integers losslessly, wrap digits exceeding 15 characters in quotation marks or parse them directly into native &lt;code&gt;BigInt&lt;/code&gt; primitives using a tokenizing pre-pass:&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;/**
 * Safely parses JSON payloads containing raw 64-bit integers without IEEE 754 truncation.
 */&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;parseLosslessJson&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonString&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;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Matches integer literals of 15+ digits not already enclosed in quotes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;safeJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonString&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;\s\[&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;])([&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]{15,})([&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\s\]\}])&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;$1"$2"$3&lt;/span&gt;&lt;span class="dl"&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;safeJson&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;
  
  
  Solution 3: Decoding Metadata from Snowflake IDs (Discord / Twitter / Mastodon)
&lt;/h3&gt;

&lt;p&gt;Because Snowflake IDs encode an embedded timestamp, you can reverse-engineer the exact creation date and time of any record client-side using JavaScript &lt;code&gt;BigInt&lt;/code&gt; bitwise 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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SnowflakeMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&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;decodeDiscordSnowflake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snowflakeStr&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;SnowflakeMetadata&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;DISCORD_EPOCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1420070400000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 2015-01-01T00:00:00.000Z&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snowflakeStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Bit 22 to 63: Milliseconds since Discord epoch&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestampMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;DISCORD_EPOCH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Bit 12 to 21: Internal Worker / Process ID&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x3FF&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Bit 0 to 11: Incrementing sequence&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sequence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFFF&lt;/span&gt;&lt;span class="nx"&gt;n&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="na"&gt;timestamp&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestampMs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;workerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sequence&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 usage:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeDiscordSnowflake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1754902188402511875&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Interactive Developer Tools for Epochs &amp;amp; BigInt JSON
&lt;/h2&gt;

&lt;p&gt;If you need to verify, debug, or convert Snowflake IDs and distributed timestamps without spinning up custom scripts, you can use these free, 100% private in-browser utilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🕒 &lt;a href="https://devomnitools.com/en/tools/unix-timestamp/" rel="noopener noreferrer"&gt;&lt;strong&gt;DevOmniTools Unix Timestamp &amp;amp; Snowflake Converter&lt;/strong&gt;&lt;/a&gt;: Live conversion between ISO-8601 dates, Unix Epoch (seconds/milliseconds), and distributed 64-bit Snowflake IDs (Discord, Twitter, Mastodon) with zero server transmission.&lt;/li&gt;
&lt;li&gt;📦 &lt;a href="https://devomnitools.com/en/tools/json-formatter/" rel="noopener noreferrer"&gt;&lt;strong&gt;DevOmniTools JSON Formatter &amp;amp; Lossless Validator&lt;/strong&gt;&lt;/a&gt;: In-browser JSON formatter and tree inspector that safely preserves 64-bit BigInt primary keys without IEEE 754 precision degradation.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript &lt;code&gt;Number&lt;/code&gt; is strictly IEEE 754 Double&lt;/strong&gt;: Maximum safe integer is $9,007,199,254,740,991$ (16 digits).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snowflake IDs exceed 60 bits&lt;/strong&gt;: Standard &lt;code&gt;JSON.parse()&lt;/code&gt; silently alters the last 3 to 4 digits, breaking foreign key lookups and idempotency tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Revivers cannot fix it after the fact&lt;/strong&gt;: Pre-process the JSON string using regex tokenization or native &lt;code&gt;BigInt&lt;/code&gt; lossless lexers before invoking &lt;code&gt;JSON.parse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Contract&lt;/strong&gt;: Always configure API serializers to emit 64-bit integers as JSON strings (&lt;code&gt;"id": "17549..."&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mitigating CVE-2026-118759: Why Token Inspection Must Be 100% Client-Side</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Mon, 02 Nov 2026 10:56:48 +0000</pubDate>
      <link>https://dev.to/devomnitools/mitigating-cve-2026-118759-why-token-inspection-must-be-100-client-side-9lp</link>
      <guid>https://dev.to/devomnitools/mitigating-cve-2026-118759-why-token-inspection-must-be-100-client-side-9lp</guid>
      <description>&lt;h1&gt;
  
  
  Mitigating CVE-2026-118759: Why Token Inspection Must Be 100% Client-Side
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Direct Solution Summary (40 Words):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
To remediate CVE-2026-118759 without leaking sensitive session claims to third-party endpoints, all token decoding and cryptographic payload assertions must execute strictly inside browser RAM using standard WebCrypto APIs (&lt;code&gt;window.crypto.subtle&lt;/code&gt;) with zero server telemetry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Technical Vulnerability Breakdown
&lt;/h2&gt;

&lt;p&gt;The recent disclosure of &lt;strong&gt;CVE-2026-118759&lt;/strong&gt; highlighted a critical security hazard in modern distributed architectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Root Cause:&lt;/strong&gt; Authentication Token Leak &amp;amp; Session Fixation via Insecure Loopback Cookies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attack Vector:&lt;/strong&gt; Man-in-the-middle interception of inspection queries when developers paste live production tokens into centralized online formatters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural Risk:&lt;/strong&gt; Traditional SaaS formatters capture input headers, session IDs, and user metadata in access logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. The Zero-Exfiltration Defense Architecture
&lt;/h2&gt;

&lt;p&gt;By enforcing a strict local sandbox, developers guarantee that secrets never touch the network:&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;// Pure In-Browser Client-Side Token Signature Verification&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;verifyTokenLocally&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawToken&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;publicKeyJwk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonWebKey&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="nx"&gt;parts&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="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="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="s2"&gt;Invalid JWT format&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="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;parts&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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="nf"&gt;atob&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="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="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="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="nb"&gt;window&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="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="s2"&gt;jwk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;publicKeyJwk&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="s2"&gt;RSASSA-PKCS1-v1_5&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="s2"&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="s2"&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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;window&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="nx"&gt;subtle&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RSASSA-PKCS1-v1_5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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;
  
  
  3. Interactive Verification Tool
&lt;/h2&gt;

&lt;p&gt;Developers can benchmark and inspect tokens with zero server exfiltration using our free, open-source utility:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;Test with JWT &amp;amp; Token Decoder&lt;/a&gt;&lt;/strong&gt; (Runs 100% in browser RAM).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Reference Standards: RFC 7519 (JSON Web Token), RFC 9309 (Robots Exclusion), W3C Web Cryptography API.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Technical Breakdown: Vitest Dev-Server Secret Leak (CVE-2026-112948) &amp; How to Protect Your .env</title>
      <dc:creator>Muhammad Umair | DevOmniTools</dc:creator>
      <pubDate>Mon, 26 Oct 2026 01:36:00 +0000</pubDate>
      <link>https://dev.to/devomnitools/technical-breakdown-vitest-dev-server-secret-leak-cve-2026-112948-how-to-protect-your-env-45ji</link>
      <guid>https://dev.to/devomnitools/technical-breakdown-vitest-dev-server-secret-leak-cve-2026-112948-how-to-protect-your-env-45ji</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Original Technical Advisory &amp;amp; Research published on &lt;a href="https://www.devomnitools.com/en/guides/vitest-dev-server-secret-leak-cve-2026-112948/" rel="noopener noreferrer"&gt;DevOmniTools Security Lab&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fimpmmf1y9q35ikyqxj05.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%2Fimpmmf1y9q35ikyqxj05.png" alt="Vitest dev server.fs.deny leak architecture diagram" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Executive Summary &amp;amp; Vulnerability Context
&lt;/h2&gt;

&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 vulnerability in &lt;strong&gt;Vitest 5 dev servers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When developers execute test suites using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The interactive UI (&lt;code&gt;vitest --ui&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Vitest Browser Mode (&lt;code&gt;@vitest/browser&lt;/code&gt;)- Or by exposing the remote API (&lt;code&gt;api: true&lt;/code&gt; / &lt;code&gt;--api&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vitest's underlying HTTP server inadvertently discarded Vite's default &lt;code&gt;server.fs.deny&lt;/code&gt; array. This allowed unauthenticated HTTP requests from local network peers or malicious browser scripts to read raw project files 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, and &lt;code&gt;.git/config&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Root Cause: The Array Override Bug
&lt;/h2&gt;

&lt;p&gt;Under standard Vite configurations, &lt;code&gt;server.fs.deny&lt;/code&gt; protects sensitive dotfiles by default:&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 default protection&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&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;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;.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;**/.git/**&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in Vitest 5, the test runner attempted to safeguard its internal token file by assigning an explicit array rather than merging it with Vite's existing 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;// Buggy assignment in Vitest 5 core&lt;/span&gt;
&lt;span class="nx"&gt;config&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deny&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;.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="c1"&gt;// Overwrote Vite's default ['.env', '.env.*', '**/.git/**']!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because Vite does not automatically deep-merge replaced configuration arrays, all default deny patterns were silently dropped. An attacker on the local network could fetch secrets with a single HTTP request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Exposed .env via Vitest dev server port&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:51204/@fs/workspace/project/.env"&lt;/span&gt;&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt;&lt;span class="c"&gt;## Incident Response &amp;amp; Secret Rotation Protocol&lt;/span&gt;

If your engineering team ran &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;vitest &lt;span class="nt"&gt;--ui&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; on shared &lt;span class="nb"&gt;local &lt;/span&gt;networks, public Wi-Fi, or during CI/CD &lt;span class="nb"&gt;test &lt;/span&gt;previews, follow this 3-step recovery protocol:

&lt;span class="c"&gt;### 1. Upgrade Vitest Immediately&lt;/span&gt;
Upgrade your project dependencies to Vitest &lt;span class="k"&gt;**&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; 5.0.2&lt;span class="k"&gt;**&lt;/span&gt; where &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;server.fs.deny&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt; is properly concatenated using array spreading.
&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="sb"&gt;```&lt;/span&gt;bash
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; vitest@latest&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;### 2. Force Explicit Deny Configuration&lt;/span&gt;
Explicitly guard your secret files &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;vite.config.ts&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;:
&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="sb"&gt;```&lt;/span&gt;ts
import &lt;span class="o"&gt;{&lt;/span&gt; defineConfig &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'vite'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;default defineConfig&lt;span class="o"&gt;({&lt;/span&gt;
  server: &lt;span class="o"&gt;{&lt;/span&gt;
    fs: &lt;span class="o"&gt;{&lt;/span&gt;
      deny: &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'.env'&lt;/span&gt;, &lt;span class="s1"&gt;'.env.*'&lt;/span&gt;, &lt;span class="s1"&gt;'**/.git/**'&lt;/span&gt;, &lt;span class="s1"&gt;'.vitest-secret-token'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Rotate Compromised Credentials Offline
&lt;/h3&gt;

&lt;p&gt;Never generate or paste replacement API keys into ad-supported cloud tools that store telemetry on third-party servers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For Cryptographic Tokens &amp;amp; API Keys:&lt;/strong&gt; Generate high-entropy 256-bit replacement keys directly in your browser using the &lt;strong&gt;&lt;a href="https://www.devomnitools.com/en/tools/token-generator/" rel="noopener noreferrer"&gt;DevOmniTools Offline CSPRNG Token Generator&lt;/a&gt;&lt;/strong&gt; (uses local &lt;code&gt;crypto.getRandomValues&lt;/code&gt; with zero network transmission).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For JWT Signing Secrets:&lt;/strong&gt; Verify token claims, expiration timestamps, and detached JWS signatures safely in client-side memory using the &lt;strong&gt;&lt;a href="https://www.devomnitools.com/en/tools/jwt-decoder/" rel="noopener noreferrer"&gt;DevOmniTools Offline JWT Inspector&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion &amp;amp; Architecture Lessons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dev server UI ports must always be strictly bound to &lt;code&gt;127.0.0.1&lt;/code&gt; and never exposed across shared LANs.&lt;/li&gt;
&lt;li&gt;When authoring Vite plugins, never assign arrays directly to &lt;code&gt;server.fs.deny&lt;/code&gt; - always concatenate existing entries.&lt;/li&gt;
&lt;li&gt;Audit your test runners and keep local &lt;code&gt;.env&lt;/code&gt; files out of public dev server scopes.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Published by Muhammad Umair &amp;amp; the DevOmniTools Research Team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <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>
  </channel>
</rss>
