# Cloudflare Workers में हर रिक्वेस्ट पर JavaScript ओबफस्केट करें

Cloudflare Worker में हर रिक्वेस्ट पर JavaScript ओबफस्केट करने के लिए AfterPack के @afterpack/wasm को रैंडम seed के साथ कॉल करें, या हर टाइम विंडो का एक वर्ज़न कैश करें।

Source: https://www.afterpack.dev/hi/blog/obfuscate-javascript-cloudflare-workers

Published: 2026-09-28 · Author: Nikita Savchenko · Tags: guide, security

Cloudflare Worker में हर रिक्वेस्ट पर JavaScript ओबफस्केट (obfuscate) करने के लिए `@afterpack/wasm` इंस्टॉल करें और अपने `fetch` हैंडलर में `obfuscate()` को रैंडम `seed` के साथ कॉल करें: हर रिस्पॉन्स संरचना में अलग स्क्रिप्ट होती है जिसका व्यवहार एक जैसा रहता है। यह पैकेज AfterPack (afterpack.dev), यानी JavaScript obfuscator, का इंजन है जो WebAssembly में कंपाइल किया गया है; यह मुफ़्त है और आपके Worker के अंदर चलता है। असली bundle फ़्री प्लान की 10 ms CPU सीमा से ज़्यादा लेते हैं, इसलिए Workers Paid इस्तेमाल करें और हर टाइम विंडो का एक ओबफस्केटेड वर्ज़न कैश करें।

नीचे की हर चीज़ मैंने `wrangler dev` के साथ चलाई: बिल्कुल वही Worker, curl आउटपुट जिसमें एक ही URL के लिए दो अलग बॉडी दिखती हैं, और एक कैश किया हुआ वेरिएंट जो हर रिक्वेस्ट के बजाय हर दस मिनट में बदलता है। हर रिक्वेस्ट पर कोड ओबफस्केशन के लिए एक npm पैकेज और बीस से कम लाइनों का Worker कोड काफ़ी है।

AI ने शिप किए गए JavaScript को पढ़ना सस्ता कर दिया है: एक AI एजेंट को ओबफस्केट किए गए डेमो वापस साफ़ सोर्स में बदलने में [बस कुछ मिनट लगे](https://www.afterpack.dev/blog/ai-deobfuscates-javascript)। [AfterPack CLI](https://www.afterpack.dev/docs/cli) और framework plugins इसका जवाब हर रिलीज़ पर देते हैं: हर बिल्ड पर नया seed ([लॉन्च पोस्ट](https://www.afterpack.dev/blog/announcing-afterpack-javascript-obfuscator))। आपके अपने Worker के अंदर वही इंजन यह रफ़्तार हर रिक्वेस्ट तक ले जाता है, इसलिए किसी स्क्रिप्ट की एक कॉपी के ख़िलाफ़ बनाई गई कोई भी ऑटोमेशन अगली कॉपी के लिए दोबारा बनानी पड़ती है।

## हर रिक्वेस्ट पर कोड ओबफस्केशन क्या रोकता है?

एक रिस्पॉन्स के ख़िलाफ़ बनाए गए hook, patch और deobfuscation स्क्रिप्ट अगले रिस्पॉन्स पर काम करना बंद कर देते हैं, क्योंकि identifiers, offsets और decoder की पोज़िशन हर बार बदलती हैं। यह उन स्क्रिप्ट के लिए मायने रखता है जिन पर ऑटोमेशन बार-बार लौटता है: anti-bot और challenge स्क्रिप्ट, क्लाइंट-साइड integrity checks, और वे स्क्रिप्ट जिन्हें scrapers बायपास करने के लिए patch या hook करते हैं।

हर रिस्पॉन्स एक पूरा AfterPack बिल्ड भी होता है: algorithms दोबारा लिखे जाते हैं, control flow फ़्लैट किया जाता है और identities मिला दी जाती हैं, इसलिए रिवर्स की गई कॉपी भी आपके लिखे कोड से अलग होती है। फिर भी हर रिस्पॉन्स वही प्रोग्राम है, और [कोई भी obfuscator, AfterPack समेत, रिवर्सल को असंभव नहीं बनाता](https://www.afterpack.dev/docs/threat-model#what-obfuscation-protects)।

## Cloudflare Worker में JavaScript obfuscator कैसे इंस्टॉल करें

`wrangler` के साथ `@afterpack/wasm` इंस्टॉल करें और उससे `obfuscate` इम्पोर्ट करें। कोई `init` कॉल नहीं, कोई `nodejs_compat` फ़्लैग नहीं और `.wasm` फ़ाइल के लिए कोई wrangler rule नहीं।

2026-09-27 को मैंने ये वर्ज़न इस्तेमाल किए: `@afterpack/wasm` 0.1.0, `wrangler` 4.141.0, Node 24.21.0, Apple M2 Max पर।

```bash
npm i @afterpack/wasm wrangler
```

`@afterpack/wasm` वही Rust इंजन है जो CLI और plugins चलाते हैं, WebAssembly में कंपाइल किया हुआ; पैकेज का `exports` मैप wrangler को Worker के लिए बनी एंट्री देता है ([डॉक्स](https://www.afterpack.dev/docs/workers))। यही import Node में भी काम करता है, जहाँ एक दूसरी एंट्री बाइनरी को डिस्क से पढ़ती है, हालाँकि सामान्य Node बिल्ड में नेटिव [`@afterpack/core`](https://www.afterpack.dev/docs/frameworks) ज़्यादा तेज़ है।

डेमो स्क्रिप्ट एक छोटा क्लाइंट-साइड प्राइस कैलकुलेटर है। यह किसी challenge या integrity स्क्रिप्ट की जगह खड़ी है, और इतनी छोटी है कि आउटपुट को हाथ से जाँचा जा सके:

**src/pricing.client.js — जो सोर्स सर्व किया जा रहा है**

```js
const TIERS = [
  { min: 1, max: 9, unit: 12 },
  { min: 10, max: 49, unit: 10 },
  { min: 50, max: Infinity, unit: 8 },
];

function unitPrice(quantity) {
  const tier = TIERS.find((t) => quantity >= t.min && quantity <= t.max);
  return tier ? tier.unit : TIERS[0].unit;
}

export function quote(quantity, coupon) {
  let total = unitPrice(quantity) * quantity;
  if (coupon === "LAUNCH20" && quantity >= 10) total *= 0.8;
  return Math.round(total * 100) / 100;
}
```

Worker इस फ़ाइल को string के रूप में इम्पोर्ट करता है, जिसके लिए सोर्स फ़ाइल का एक wrangler rule चाहिए (इंजन को कोई नहीं चाहिए)। `wrangler.jsonc`:

```jsonc
{
  "name": "per-request-obfuscation",
  "main": "src/worker.js",
  "compatibility_date": "2026-09-25",
  "vars": { "WINDOW_SECONDS": "600" },
  "rules": [{ "type": "Text", "globs": ["**/*.client.js"], "fallthrough": true }]
}
```

## हर रिक्वेस्ट पर JavaScript कैसे ओबफस्केट करें

`fetch` हैंडलर में एक रैंडम seed बनाएँ, उसे `obfuscate()` को दें, और नतीजा `cache-control: no-store` के साथ लौटाएँ। तब हर रिस्पॉन्स अपने ही seed से बनता है।

`src/worker.js`:

```js
import { obfuscate } from "@afterpack/wasm";
import source from "./pricing.client.js";

export default {
  async fetch(request) {
    if (new URL(request.url).pathname !== "/pricing.js") {
      return new Response("Not found", { status: 404 });
    }
    const seed = crypto.getRandomValues(new Uint32Array(1))[0];
    const result = await obfuscate({ path: "pricing.js", source }, { preset: "hard", seed });
    return new Response(result.bytes, {
      headers: {
        "content-type": "application/javascript",
        "cache-control": "no-store",
      },
    });
  },
};
```

Worker में [`seed`](https://www.afterpack.dev/docs/config#seed) हमेशा ख़ुद पास करें: `crypto.getRandomValues` से आया रैंडम seed ही हर रिस्पॉन्स को अलग बनाता है। `cache-control: no-store` इसे ऐसा ही रखता है, क्योंकि वरना ब्राउज़र, आपके डोमेन के आगे कोई दूसरा CDN या कॉर्पोरेट प्रॉक्सी पहला वर्ज़न रख सकता है, और हर रिक्वेस्ट का काम बेकार जाता। किसी रिस्पॉन्स को बाद में दोबारा बनाने के लिए उसका seed लॉग करें या रिस्पॉन्स हेडर में लौटाएँ; seed कोई सीक्रेट नहीं है।

`npx wrangler dev` चलाएँ, फिर एक ही URL दो बार फ़ेच करें:

```bash
$ for i in 1 2; do curl -s localhost:8787/pricing.js | shasum -a 256; done
0265ae6f5cf5a0839ee52a9a08bab6326503849c66296ae581c2c21923d2d676  -
a5684abbb2ba13be3c182840d30b1f2a634b241013e112be70c6de22ac1233b4  -

$ for i in 1 2; do curl -s localhost:8787/pricing.js | head -c 100; echo; done
var DA=($d,wS,Uv)=>((tD*(tD+1)&1)===0?(((tD*tD*tD-tD)%3|0)===0?$d:tD^32)[((tD*tD-tD&1)===0?e:tD&739)
var Sg=(H5,xS,nF)=>((Ye*(Ye+1)*(Ye+5)%6|0)===0?H5:Ye+124)[((Ye*(Ye+3)%2|0)!==0?Ye+100:B)](((Ye*(Ye+1

$ for i in 1 2; do curl -s localhost:8787/pricing.js | wc -c; done
    3823
    3545
```

हैश, identifiers और लंबाई सब अलग हैं, और किसी भी बॉडी में `TIERS` या `unitPrice` नहीं है। मैंने दो रिस्पॉन्स को मॉड्यूल की तरह सेव किया और छह quantity/coupon जोड़ों के लिए हर एक पर, मूल कोड के साथ, `quote()` कॉल किया; तीनों ने एक जैसे टोटल लौटाए। coupon string अब भी अंदर है, रनटाइम पर एन्कोड और डिकोड होती हुई, और यह ठीक है: ऑर्डर लेने वाला सर्वर क़ीमत दोबारा गिनता है और coupon जाँचता है, और यह क्लाइंट कॉपी सिर्फ़ quote दिखाती है।

## ओबफस्केटेड JavaScript को कैश करके हर 10 मिनट में कैसे बदलें

रैंडम seed बनाने के बजाय उसे मौजूदा टाइम विंडो से निकालें, और नतीजे को ऐसी key के तहत कैश करें जिसमें विंडो शामिल हो। तब हर लोकेशन हर विंडो के लिए एक ओबफस्केटेड वर्ज़न बनाती है और विंडो ख़त्म होने तक वही सर्व करती है, इसलिए विज़िटरों को अपने-अपने नए बिल्ड का CPU नहीं देना पड़ता:

**src/rotating.js — हर विंडो के लिए एक वर्ज़न, कैश किया हुआ**

```js
import { obfuscate } from "@afterpack/wasm";
import source from "./pricing.client.js";

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    if (url.pathname !== "/pricing.js") {
      return new Response("Not found", { status: 404 });
    }

    const windowSeconds = Number(env.WINDOW_SECONDS);
    const now = Math.floor(Date.now() / 1000);
    const slot = Math.floor(now / windowSeconds);
    const secondsLeft = (slot + 1) * windowSeconds - now;
    const cacheKey = new Request(`${url.origin}/pricing.js?slot=${slot}`);

    let response = await caches.default.match(cacheKey);
    if (!response) {
      const result = await obfuscate(
        { path: "pricing.js", source },
        { preset: "hard", seed: `pricing.js:${slot}` },
      );
      response = new Response(result.bytes, {
        headers: {
          "content-type": "application/javascript",
          "cache-control": `public, max-age=${secondsLeft}`,
        },
      });
      ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
    }

    response = new Response(response.body, response);
    response.headers.set("cache-control", `public, max-age=${secondsLeft}`);
    return response;
  },
};
```

ऊपर का कॉन्फ़िग 600 सेकंड की विंडो सेट करता है। दस मिनट इंतज़ार किए बिना इसे बदलते देखने के लिए मैंने इसे 15 सेकंड कर दिया:

```bash
npx wrangler dev src/rotating.js --port 8788 --var WINDOW_SECONDS:15
```

```bash
$ for i in 1 2 3 4 5; do
    date +%T
    curl -s -D headers.txt localhost:8788/pricing.js | shasum -a 256 | cut -c1-16
    grep -i cache-control headers.txt
    sleep 4
  done
15:03:46
951fdcf8a3cf73ef
Cache-Control: public, max-age=14
15:03:50
951fdcf8a3cf73ef
Cache-Control: public, max-age=10
15:03:54
951fdcf8a3cf73ef
Cache-Control: public, max-age=6
15:03:58
951fdcf8a3cf73ef
Cache-Control: public, max-age=2
15:04:03
4120ce5292b9cf92
Cache-Control: public, max-age=12
```

विंडो के अंदर वही बाइट्स, उसके बाद नए बाइट्स, और `max-age` सीमा तक उल्टी गिनती करता है।

तीन बातें इसे काम करने लायक बनाती हैं:

- **एक ही seed से एक ही बाइट्स।** मेरे टेस्ट में एक ही seed string, सोर्स और पैकेज वर्ज़न ने बार-बार कॉल करने पर एक जैसा आउटपुट दिया। Cloudflare का Cache API उस डेटा सेंटर के बाहर रेप्लिकेट नहीं होता जिसने एंट्री सेव की ([Cloudflare डॉक्स](https://developers.cloudflare.com/workers/runtime-apis/cache/)), इसलिए हर लोकेशन अपने पहले miss पर अपनी कॉपी बनाती है; चूँकि seed विंडो से आता है, एक ही डिप्लॉय किया गया वर्ज़न चला रही हर लोकेशन को उस विंडो में वही बाइट्स सर्व करने चाहिए। seed [कोई सीक्रेट नहीं है](https://www.afterpack.dev/docs/builds#random-seed-by-default-pin-for-reproducibility): इससे बस आपके सोर्स से आउटपुट दोबारा बनाया जा सकता है, और आपका सोर्स किसी और के पास नहीं है।
- **ब्राउज़र को बचा हुआ समय मिलता है, सेव किया हुआ नहीं।** कैश hit उन्हीं हेडरों के साथ लौटता है जिनके साथ वह सेव हुआ था, इसलिए Worker बाहर जाते समय `cache-control` दोबारा लिखता है। इसके बिना, विंडो के आख़िर में hit करने वाला ब्राउज़र पुराना वर्ज़न एक और विंडो तक रख सकता था। slot कैश key में है, इसलिए नई विंडो कभी पुरानी एंट्री नहीं पढ़ती।
- **कैशिंग लोकल या अपने डोमेन पर टेस्ट करें।** Cache API का डैशबोर्ड एडिटर या Playground प्रीव्यू में कोई असर नहीं होता, इसलिए `wrangler dev` या डिप्लॉय किया गया route इस्तेमाल करें।

## Worker में ओबफस्केशन पर कितना CPU लगता है?

इस 16 लाइन की फ़ाइल के लिए, `wrangler dev` में वॉर्म होने के बाद 4–12 ms का रिक्वेस्ट टाइम। असली bundle हर बिल्ड पर `light` में सैकड़ों मिलीसेकंड और `hard` में कई सेकंड लेता है, जो Workers फ़्री प्लान की 10 ms CPU सीमा से कहीं ज़्यादा है।

मेरे M2 Max पर `wrangler dev` के पाँच कोल्ड स्टार्ट में पहली रिक्वेस्ट ने 59–338 ms लिए (इंजन स्टार्ट-अप समेत) और बाद वाली 4–12 ms पर टिक गईं। ये लोकल workerd प्रोसेस के रिक्वेस्ट टाइम हैं, edge isolate का CPU टाइम नहीं, इसलिए कुछ भी तय करने से पहले Cloudflare पर मापें। पैमाने के लिए, डॉक्स ने AfterPack 0.2.0 के साथ Node में 185 KB के bundle के लिए `light` पर 271 ms और `medium` पर 1,558 ms [मापे](https://www.afterpack.dev/docs/performance#the-wasm-build); यह डेमो `hard` चलाता है, जो [complexity](https://www.afterpack.dev/docs/complexity) 12 है ([presets](https://www.afterpack.dev/docs/presets))।

इंजन दोनों प्लान पर Worker की साइज़ सीमा में फ़िट होता है। किसी छोटी-सी स्क्रिप्ट से बड़ी चीज़ के लिए फ़्री प्लान को उसकी CPU सीमा बाहर कर देती है ([डॉक्स](https://www.afterpack.dev/docs/workers))।

## हर रिक्वेस्ट पर, हर विंडो पर या हर बिल्ड पर?

बड़े bundle और पेज-लोड पाथ की हर चीज़ हर बिल्ड पर ओबफस्केट करें, जिन स्क्रिप्ट को सस्ते में बदलना है उन्हें हर विंडो पर, और हर रिक्वेस्ट पर सिर्फ़ वहाँ जहाँ कोई आपकी स्क्रिप्ट के ख़िलाफ़ बार-बार वही ऑटोमेशन चलाता है।

| रफ़्तार | कहाँ चलता है | बिल्ड | कैशिंग | किसके लिए |
|---|---|---|---|---|
| हर बिल्ड पर | [CLI](https://www.afterpack.dev/docs/cli) या plugin, CI में | हर डिप्लॉय पर एक | आपका CDN, किसी भी फ़ाइल की तरह | बड़े bundle, पेज-लोड का hot path |
| हर विंडो पर | आपका Worker, टाइम slot से seed | हर विंडो हर लोकेशन पर एक | Cache API, `max-age` = बचे हुए सेकंड | वे स्क्रिप्ट जिन्हें हर विज़िटर का ख़र्च दिए बिना बदलना है |
| हर रिक्वेस्ट पर | आपका Worker, रैंडम seed | हर रिक्वेस्ट पर एक | `no-store` | Anti-bot और challenge स्क्रिप्ट, integrity checks, वे स्क्रिप्ट जिन्हें scrapers patch करते हैं |

## Worker से Pro ओबफस्केशन कैसे चलाएँ

एक [`key`](https://www.afterpack.dev/docs/config#key) पास करें, जैसे Worker secret से `{ key: env.AFTERPACK_KEY }`, और वही `obfuscate()` कॉल AfterPack के क्लाउड में [Pro](https://www.afterpack.dev/docs/pro) चलाती है। आप जो पैकेज इंस्टॉल करते हैं वह अपने आप Free सुरक्षा देता है।

यह कॉल rotation Worker में होनी चाहिए, हर रिक्वेस्ट वाले Worker में नहीं, जहाँ हर विज़िटर को क्लाउड बिल्ड के राउंड ट्रिप का इंतज़ार करना पड़ता। विंडो की लंबाई पर भी नज़र रखें: Pro का बिल [प्रोसेस किए गए MB के हिसाब से](https://www.afterpack.dev/docs/pro#what-pro-costs) बनता है, और हर लोकेशन हर विंडो में अपनी कॉपी बनाती है। 20 KB की स्क्रिप्ट, 10 मिनट की विंडो और 30 लोकेशन पर रोज़ लगभग 86 MB बनती है, महीने में 2.6 GB, जो Indie के 500 MB से कहीं ज़्यादा है। 6 घंटे की विंडो पर वही स्क्रिप्ट महीने में लगभग 72 MB बनती है।

हर क्लाउड बिल्ड एक रिकॉर्ड किया गया बिल्ड भी है जिसकी अपनी [Protection Map](https://www.afterpack.dev/docs/protection-map) होती है, और एक प्रोजेक्ट सिर्फ़ आख़िरी 500 रखता है, इसलिए जिस प्रोजेक्ट के तहत आपका rotation Worker बिल्ड करता है उसके लिए [map स्टोरेज बंद कर दें](https://www.afterpack.dev/docs/protection-map#turning-it-off)। इस कॉल के लिए आपका सोर्स Worker से बाहर जाता है, मेमोरी में प्रोसेस होता है और हटा दिया जाता है ([प्राइवेसी](https://www.afterpack.dev/docs/privacy))। अगर क्लाउड तक नहीं पहुँचा जा सकता, तो कॉल [fail closed होती है](https://www.afterpack.dev/docs/diagnostics#fail-closed-no-exceptions): वह throw करती है और कुछ भी असुरक्षित शिप नहीं होता। उस throw को पकड़ें और किसी पिछली विंडो से रखा वर्ज़न सर्व करें, वरना विज़िटर को एरर मिलेगा।

## आज़माकर देखें

ऊपर की तीन फ़ाइलें (`wrangler.jsonc`, `src/worker.js`, `src/pricing.client.js`) कॉपी करें, `npm i @afterpack/wasm wrangler` और `npx wrangler dev` चलाएँ, फिर `/pricing.js` को दो बार curl करें। पैकेज का रेफ़रेंस [Cloudflare Worker के अंदर JavaScript ओबफस्केट करें](https://www.afterpack.dev/docs/workers) है; इसे अपनाते समय इस पोस्ट का explicit `seed` बनाए रखें।

## अक्सर पूछे जाने वाले सवाल

### क्या मैं Cloudflare Workers फ़्री प्लान पर JavaScript ओबफस्केट कर सकता हूँ?

सिर्फ़ बहुत छोटी स्क्रिप्ट के लिए, और तब भी हर isolate की पहली रिक्वेस्ट (`wrangler dev` में 59–338 ms का रिक्वेस्ट टाइम, इंजन स्टार्ट-अप समेत) फ़्री प्लान की 10 ms CPU सीमा पार कर सकती है। Workers Paid इस्तेमाल करें, बेहतर हो तो ऊपर वाले हर-विंडो कैश के साथ।

### क्या हर रिक्वेस्ट पर ओबफस्केशन मेरी साइट को धीमा करता है?

इस 16 लाइन की स्क्रिप्ट के लिए, `wrangler dev` में वॉर्म रिक्वेस्ट ने 4–12 ms लिए, और हर isolate की पहली रिक्वेस्ट ने 59–338 ms। हर-विंडो कैश के साथ सिर्फ़ हर लोकेशन पर हर विंडो की पहली रिक्वेस्ट बिल्ड करती है; ज़्यादातर कैश hit 2–3 ms में लौटे। हर बिल्ड पर ओबफस्केशन रिक्वेस्ट टाइम में कुछ नहीं जोड़ता।

### क्या हर रिक्वेस्ट पर ओबफस्केशन वेब स्क्रेपिंग और बॉट्स को रोक सकता है?

यह एक रिस्पॉन्स के ख़िलाफ़ लिखे गए hooks, patches और scrapers को तोड़ देता है, क्योंकि अगला रिस्पॉन्स एक अलग स्क्रिप्ट है। यह किसी इंसान को रिस्पॉन्स पढ़ने से नहीं रोकता, और अपने आप बॉट्स का पता नहीं लगाता; यह उन स्क्रिप्ट के ख़िलाफ़ ऑटोमेशन की लागत बढ़ाता है जिन पर आपके bot checks टिके हैं।

### क्या हर रिक्वेस्ट पर JavaScript ओबफस्केट करने से API keys छिप जाती हैं?

नहीं। ब्राउज़र को जो भी चाहिए, ब्राउज़र उसे पढ़ सकता है, चाहे वह किसी भी रूप में आए। सीक्रेट्स और अंतिम क़ीमत या coupon की वैधता जैसे फ़ैसले सर्वर पर रखें, जैसा डेमो करता है। [JavaScript सोर्स कोड कैसे सुरक्षित करें](https://www.afterpack.dev/blog/protect-javascript-source-code) बताता है कि आपकी साइट पहले से क्या शिप कर रही है, यह कैसे जाँचें।

### क्या AfterPack मेरे अपने Worker के बिना हर रिक्वेस्ट पर ओबफस्केशन दे सकता है?

नहीं। ऊपर वाला Worker, आपके अपने account में, ही इंटीग्रेशन है। Pro के लिए, अपनी key उसी `obfuscate()` कॉल में डालें: बिल्ड AfterPack के cloud में Pro engine पर चलता है, और Worker के लिए बहुत बड़ी फ़ाइल अपने आप container में चली जाती है। असली कठिन सवाल यह है कि आपकी कौन-सी स्क्रिप्ट के ख़िलाफ़ सच में कोई हर दिन वही ऑटोमेशन चला रहा है।
