<?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: VEILVM</title>
    <description>The latest articles on DEV Community by VEILVM (@veilvm).</description>
    <link>https://dev.to/veilvm</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%2F4224109%2F9db58b8d-6838-49a4-8789-bfbb70b0f8b0.png</url>
      <title>DEV Community: VEILVM</title>
      <link>https://dev.to/veilvm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/veilvm"/>
    <language>en</language>
    <item>
      <title>How to stop people from stealing your HTML5 game's JavaScript (without tanking FPS)</title>
      <dc:creator>VEILVM</dc:creator>
      <pubDate>Tue, 03 Nov 2026 12:50:01 +0000</pubDate>
      <link>https://dev.to/veilvm/how-to-stop-people-from-stealing-your-html5-games-javascript-without-tanking-fps-24pj</link>
      <guid>https://dev.to/veilvm/how-to-stop-people-from-stealing-your-html5-games-javascript-without-tanking-fps-24pj</guid>
      <description>&lt;p&gt;If you ship a Phaser, PixiJS, Cocos Creator (web), or Construct game, players already have your scripts. Opening DevTools → Sources is enough to read economy formulas, unlock flags, and anti-cheat stubs. Reskins on portals are not theoretical — they happen.&lt;/p&gt;

&lt;p&gt;This post is a practical playbook: &lt;strong&gt;what to protect, what to leave alone&lt;/strong&gt;, why VM-based protection has a real performance cost, and how to wire a selective pipeline. I'll use &lt;a href="https://vm.ldvmp.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=html5_protect" rel="noopener noreferrer"&gt;VEILVM&lt;/a&gt; (a browser JSVMP workspace I built) as the example tool, but the selective-protection idea applies even if you use something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The threat model (be honest)
&lt;/h2&gt;

&lt;p&gt;Anything that runs on the player's device can eventually be reversed. Obfuscation and VM protection &lt;strong&gt;raise time and cost&lt;/strong&gt;; they do not make code unbreakable. For competitive integrity you still want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-authoritative scores / inventory when cheating matters&lt;/li&gt;
&lt;li&gt;Purchase validation on your backend&lt;/li&gt;
&lt;li&gt;Not shipping secrets (API keys, private matchmaking tokens) in the client at all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Client-side protection is for the other problem: &lt;strong&gt;clone and reskin&lt;/strong&gt;. Someone who wants your drop tables, soft-currency curve, or paid unlock logic should have to work for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just minify" and classic obfuscators fall short
&lt;/h2&gt;

&lt;p&gt;Minification shrinks identifiers. Classic obfuscators (javascript-obfuscator and friends) add string encoding, control-flow flattening, dead code. The output is still &lt;strong&gt;ordinary JavaScript&lt;/strong&gt;. Beautifiers and public undo scripts exist for many of those patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSVMP&lt;/strong&gt; (JavaScript Virtual Machine Protection) takes a different route:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse selected source into an AST
&lt;/li&gt;
&lt;li&gt;Compile it to a &lt;strong&gt;custom bytecode&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Ship a small &lt;strong&gt;interpreter&lt;/strong&gt; that runs that bytecode
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Attackers usually have to reverse or instrument the VM before they recover the original logic. Higher reverse-engineering cost — and higher size/CPU cost for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that saves your frame budget
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do not put the whole game into the VM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;VM-protected code runs &lt;strong&gt;noticeably slower&lt;/strong&gt; than native JS. If you wrap your render loop, physics step, or per-frame update, you will drop frames and players will blame "the game," not "the obfuscator."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protect (small, high-value modules):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purchase / entitlement checks
&lt;/li&gt;
&lt;li&gt;Save-game integrity / anti-tamper stubs
&lt;/li&gt;
&lt;li&gt;Level unlock gates
&lt;/li&gt;
&lt;li&gt;Economy, drop tables, damage formulas you don't want mirrored
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Leave alone:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phaser / Pixi / Cocos / engine runtime
&lt;/li&gt;
&lt;li&gt;Scene wiring and UI glue
&lt;/li&gt;
&lt;li&gt;Anything that runs every frame
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat JSVMP like a scalpel, not a spray can.&lt;/p&gt;

&lt;h2&gt;
  
  
  A selective workflow that fits a bundler
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  economy/
    dropTable.js      ← protect this
    entitlement.js    ← protect this
  game/
    mainLoop.js       ← never protect
    render.js         ← never protect
dist/
  economy.protected.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build sketch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Develop and playtest with &lt;strong&gt;plain&lt;/strong&gt; modules.
&lt;/li&gt;
&lt;li&gt;Before production bundle, run only the critical files through a JSVMP step.
&lt;/li&gt;
&lt;li&gt;Import the protected outputs from the rest of the game as usual.
&lt;/li&gt;
&lt;li&gt;Measure FPS and load time on a mid-tier laptop and a phone — not only on your gaming PC.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Example: protect one helper, not the scene
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/economy/dropTable.js  (protect this file only)&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;calcDropRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enemyLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;luck&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;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;enemyLevel&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;luck&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.002&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/game/battleScene.js  (leave readable / lightly minified)&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;calcDropRate&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;../economy/dropTable.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or the protected build path&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;onEnemyDefeated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enemy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;calcDropRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enemy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;luck&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;grantLoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;enemy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lootTable&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;In CI you can call a hosted API instead of clicking the browser UI. VEILVM Pro exposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://vm.ldvmp.com/api/v1/obfuscate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$VEILVM_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"code":"&amp;lt;contents of dropTable.js&amp;gt;","mode":"performance"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Max &lt;strong&gt;2 MB&lt;/strong&gt; source per API request
&lt;/li&gt;
&lt;li&gt;Modes: &lt;code&gt;performance&lt;/code&gt; | &lt;code&gt;dynamic&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://vm.ldvmp.com/docs/api?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=html5_protect" rel="noopener noreferrer"&gt;https://vm.ldvmp.com/docs/api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the API key in CI secrets. Never commit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "good" looks like after protection
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pass criteria&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Game still boots&lt;/td&gt;
&lt;td&gt;No missing exports from protected modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FPS on target device&lt;/td&gt;
&lt;td&gt;Within ~your usual budget on hot paths (those paths should &lt;strong&gt;not&lt;/strong&gt; be VM'd)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bundle size&lt;/td&gt;
&lt;td&gt;Only the protected files grew a lot; engine untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevTools glance&lt;/td&gt;
&lt;td&gt;Economy / entitlement no longer readable as normal JS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If FPS tanks, you protected too much — peel the VM off the frame loop first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engine notes (JavaScript only)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Fit&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Phaser / PixiJS / Babylon.js (web)&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Protect logic modules before Vite/webpack pack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cocos Creator (web / JS build)&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Same idea on exported JS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Construct HTML5&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Focus on custom JS you care about&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPG Maker MV / MZ plugins&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Plugins are JS; protect core logic, keep &lt;code&gt;@param&lt;/code&gt; usable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unity / Roblox / Lua FiveM scripts&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Not JavaScript — wrong tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also skip channels that &lt;strong&gt;ban&lt;/strong&gt; obfuscated submissions (browser extension stores, Greasy Fork). Shipping &lt;em&gt;your own&lt;/em&gt; game build is a different story from uploading an obfuscated userscript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try before you rebuild the pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://vm.ldvmp.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=html5_protect" rel="noopener noreferrer"&gt;VEILVM&lt;/a&gt; runs in the browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 free runs a day, no sign-up&lt;/strong&gt;, up to 5 KB per file (resets 00:00 UTC)
&lt;/li&gt;
&lt;li&gt;Free account: 20 runs/month, 30 KB/file
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro&lt;/strong&gt;: $9/mo or $79/year — 1,000 runs/month, no plan-level file-size cap, 2 concurrent jobs, HTTP API
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste one entitlement or drop-table module first. If size and behavior look fine in your target browser, wire the same step into CI.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Client protection raises clone cost; it does not replace server checks.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective protection&lt;/strong&gt; — billing, saves, levels, numeric cores — not the render/physics loop.
&lt;/li&gt;
&lt;li&gt;Expect &lt;strong&gt;size and CPU cost&lt;/strong&gt; on whatever you put in the VM; measure on real devices.
&lt;/li&gt;
&lt;li&gt;Automate only the critical files in CI so every release stays consistent.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you try this on a Phaser or Cocos web build, I'd love to hear what you protected and what you deliberately left alone — that trade-off is the whole game.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>gamedev</category>
      <category>security</category>
    </item>
    <item>
      <title>Pro API for JSVMP: call VEILVM's JS VM obfuscator from CI</title>
      <dc:creator>VEILVM</dc:creator>
      <pubDate>Tue, 03 Nov 2026 11:59:13 +0000</pubDate>
      <link>https://dev.to/veilvm/pro-api-for-jsvmp-call-veilvms-js-vm-obfuscator-from-ci-538h</link>
      <guid>https://dev.to/veilvm/pro-api-for-jsvmp-call-veilvms-js-vm-obfuscator-from-ci-538h</guid>
      <description>&lt;p&gt;I built &lt;a href="https://vm.ldvmp.com/en/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pro_api" rel="noopener noreferrer"&gt;VEILVM&lt;/a&gt; as a browser workspace that compiles JavaScript into a custom VM (JSVMP) — high-performance or dynamic mode — so small critical fragments are harder to reverse than with classic string/control-flow obfuscators.&lt;/p&gt;

&lt;p&gt;Pro plans can now hit the &lt;strong&gt;same engines over HTTP&lt;/strong&gt;, which is what you want for CI, batch jobs, or a private build step. API runs share the monthly quota with the web UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick facts (from the live docs)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST https://vm.ldvmp.com/api/v1/obfuscate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Header: &lt;code&gt;Authorization: Bearer veil_live_…&lt;/code&gt; (key shown once; up to 3 active keys per account)&lt;/li&gt;
&lt;li&gt;Body: &lt;code&gt;{ "code": "&amp;lt;js&amp;gt;", "mode": "performance" | "dynamic" }&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Max 2 MB source per request (UTF-8)&lt;/li&gt;
&lt;li&gt;Success &lt;code&gt;200&lt;/code&gt;: &lt;code&gt;{ ok, output, inputBytes, outputBytes, ms, engine, engineVersion }&lt;/code&gt; plus &lt;code&gt;X-Quota-Limit&lt;/code&gt; / &lt;code&gt;X-Quota-Remaining&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Plans: Try free 3 times a day, no sign-up; 20 runs a month after free signup; Pro $9/month or $79/year → 1,000 runs/month + API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full reference: &lt;a href="https://vm.ldvmp.com/docs/api?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pro_api" rel="noopener noreferrer"&gt;API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  curl
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://vm.ldvmp.com/api/v1/obfuscate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$VEILVM_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"code":"console.log(\"hello\")","mode":"performance"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Node.js 18+ with Retry-After on 503
&lt;/h2&gt;

&lt;p&gt;Cold starts or engine timeouts return &lt;strong&gt;503&lt;/strong&gt; with &lt;code&gt;ENGINE_UNAVAILABLE&lt;/code&gt; or &lt;code&gt;TIMEOUT&lt;/code&gt;. Those runs are &lt;strong&gt;not charged&lt;/strong&gt;. Wait for the &lt;code&gt;Retry-After&lt;/code&gt; header (seconds), then retry. &lt;code&gt;500 INTERNAL_ERROR&lt;/code&gt; is also refunded — retry later. &lt;code&gt;422 COMPILE_FAILED&lt;/code&gt; is refunded (bad source, not a transient fault).&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="c1"&gt;// Node.js 18+ — keep the key in an env var, never in the repo&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;readFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writeFile&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;node:fs/promises&lt;/span&gt;&lt;span class="dl"&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;obfuscate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;performance&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;maxAttempts&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="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&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="k"&gt;while &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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://vm.ldvmp.com/api/v1/obfuscate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;VEILVM_API_KEY&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&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;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&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;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="k"&gt;if &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;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;ok&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;data&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;retriable&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;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;503&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;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;500&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;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ENGINE_UNAVAILABLE&lt;/span&gt;&lt;span class="dl"&gt;"&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;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TIMEOUT&lt;/span&gt;&lt;span class="dl"&gt;"&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;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INTERNAL_ERROR&lt;/span&gt;&lt;span class="dl"&gt;"&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;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNAVAILABLE&lt;/span&gt;&lt;span class="dl"&gt;"&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;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUOTA_UNAVAILABLE&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;retriable&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryAfterHeader&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;headers&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;Retry-After&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;waitSec&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;retryAfterHeader&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;delayMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitSec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;waitSec&lt;/span&gt; &lt;span class="o"&gt;&amp;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;waitSec&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delayMs&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&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;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input.js&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;utf8&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;await&lt;/span&gt; &lt;span class="nf"&gt;obfuscate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output.js&lt;/span&gt;&lt;span class="dl"&gt;"&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;output&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="s2"&gt;`&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;engine&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputBytes&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputBytes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes in &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;ms&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;th&gt;&lt;code&gt;error&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;401&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INVALID_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing, malformed, revoked, or unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PLAN_REQUIRED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Account is no longer Pro — keys die immediately on downgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;422&lt;/td&gt;
&lt;td&gt;&lt;code&gt;COMPILE_FAILED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source would not compile — &lt;strong&gt;run refunded&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;429&lt;/td&gt;
&lt;td&gt;&lt;code&gt;QUOTA_EXCEEDED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Body includes &lt;code&gt;limit&lt;/code&gt; / &lt;code&gt;used&lt;/code&gt; / &lt;code&gt;remaining&lt;/code&gt; / &lt;code&gt;resetAt&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;503&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ENGINE_UNAVAILABLE&lt;/code&gt; / &lt;code&gt;TIMEOUT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Wait &lt;code&gt;Retry-After&lt;/code&gt; seconds — &lt;strong&gt;run refunded&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INTERNAL_ERROR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retry later — &lt;strong&gt;run refunded&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;VM output is larger and slower; protect small high-value functions, not your whole bundle.&lt;/li&gt;
&lt;li&gt;No client-side obfuscation is unbreakable.&lt;/li&gt;
&lt;li&gt;Keep keys server-side; revoke from the Account page if one leaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it, I’d love feedback on CI integration vs a future CLI. Links: &lt;a href="https://vm.ldvmp.com/en/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pro_api" rel="noopener noreferrer"&gt;workspace&lt;/a&gt; · &lt;a href="https://vm.ldvmp.com/pricing?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pro_api" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; · &lt;a href="https://vm.ldvmp.com/docs/api?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=pro_api" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>VEILVM: Browser-based JSVMP — compile JS into a custom VM, now with a Pro API</title>
      <dc:creator>VEILVM</dc:creator>
      <pubDate>Tue, 03 Nov 2026 11:08:25 +0000</pubDate>
      <link>https://dev.to/veilvm/veilvm-browser-based-jsvmp-compile-js-into-a-custom-vm-now-with-a-pro-api-1j9g</link>
      <guid>https://dev.to/veilvm/veilvm-browser-based-jsvmp-compile-js-into-a-custom-vm-now-with-a-pro-api-1j9g</guid>
      <description>&lt;p&gt;I built &lt;strong&gt;VEILVM&lt;/strong&gt;, an online JSVMP (JavaScript Virtual Machine Protection) workbench. You paste JavaScript (or drop a &lt;code&gt;.js&lt;/code&gt; / &lt;code&gt;.mjs&lt;/code&gt; / &lt;code&gt;.cjs&lt;/code&gt; file), pick an engine, and get an interpreter-plus-bytecode output you can copy or download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a VM?
&lt;/h2&gt;

&lt;p&gt;Minifiers and renamers leave your control flow as plain JavaScript. A beautifier plus a debugger often gets most of it back. VM-based protection maps the logic to custom instructions run by an embedded interpreter, which raises the cost of reverse engineering. The trade-off is size and speed, so it's best for small critical snippets, not your whole bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://vm.ldvmp.com/en/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;https://vm.ldvmp.com/en/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=launch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Try free 3 times a day with no sign-up (5 KB per file). Click "Load example" and run it; desktop shows source and output side by side.&lt;/li&gt;
&lt;li&gt;Two engines: High-performance VM and Dynamic VM.&lt;/li&gt;
&lt;li&gt;After free email signup: 20 runs a month, 30 KB per file.&lt;/li&gt;
&lt;li&gt;Pro: $9/month (or $79/year) for 1,000 runs, billed via Paddle, 14-day refund on a first purchase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example from my own run: a 219-byte license check → about 10 KB in 228 ms on the High-performance VM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pro HTTP API
&lt;/h2&gt;

&lt;p&gt;Pro (and above) can call obfuscation over HTTP for CI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST https://vm.ldvmp.com/api/v1/obfuscate&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Authorization: Bearer veil_live_…&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://vm.ldvmp.com/docs/api" rel="noopener noreferrer"&gt;https://vm.ldvmp.com/docs/api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keys are hashed at rest, capped at 3 per account, and stop working if the plan drops to free. Quota is shared with the web UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;No obfuscation is unbreakable. Uploaded files are deleted after 24 hours. Don't use it to obfuscate malware.&lt;/p&gt;

&lt;p&gt;I'd love feedback on output size/speed, and on what you'd want next (CLI, Vite/Webpack plugin, or GitHub Action).&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
