AfterPack ब्लॉग
securityguideAI

प्रोडक्शन में JavaScript सोर्स कोड कैसे सुरक्षित करें

द्वारा Nikita Savchenko11 मिनट पढ़ने का समय

JavaScript सोर्स कोड को सुरक्षित करने के लिए source maps सर्व करना बंद करें, secrets और access checks सर्वर पर रखें, और जो कोड ब्राउज़र में जाना ही है उसे ओबफस्केट करें। हर विज़िटर आपका बंडल डाउनलोड करता है, और minification सिर्फ़ लोकल नाम छोटे करता है: strings, API routes, feature flags और प्राइसिंग के नियम पढ़ने लायक बने रहते हैं। AfterPack (afterpack.dev), JavaScript ओबफस्केटर, हर बिल्ड पर आपके बंडल को संरचना के हिसाब से एक अलग प्रोग्राम में बदल देता है। इसका ओपन-सोर्स CLI npx afterpack से मुफ़्त और लोकली चलता है; Pro में हर हिस्से के लिए अलग directives और anti-tamper जुड़ते हैं।

यह दिखाने के लिए कि असल में यह कैसा दिखता है, मैंने एक छोटा डेमो ऐप बनाया: चार TypeScript फ़ाइलें, जिनमें सीट के हिसाब से प्राइसिंग, प्रोमो कोड, feature flags, फ़्री प्लान पर एक्सपोर्ट की लिमिट और दो API कॉल हैं। उसे मैंने 2026-09-27 को esbuild 0.28.1 (--bundle --minify --sourcemap) से बंडल किया। नीचे जो कुछ भी है, वह उसी बिल्ड का असली आउटपुट है, और यही जाँचें आप अपनी प्रोडक्शन साइट पर लगभग पाँच मिनट में कर सकते हैं: DevTools, एक curl, और मुफ़्त security scanner।

क्या यूज़र्स आपका JavaScript कोड देख सकते हैं?

हाँ। आपका पेज जो भी स्क्रिप्ट चलाता है, वह विज़िटर के ब्राउज़र में डाउनलोड होती है, और DevTools उन सबकी लिस्ट दिखाता है।

View Page Source (Ctrl+U, macOS पर Cmd+Option+U) सिर्फ़ HTML और उसके script tags दिखाता है; DevTools ख़ुद स्क्रिप्ट्स दिखाता है। React, Next.js और Vue ऐप भी इसी तरह शिप होते हैं: आपके components JavaScript फ़ाइलों के रूप में पहुँचते हैं, जिन्हें कोई भी खोल सकता है।

अपनी प्रोडक्शन साइट खोलें, localhost नहीं। Chrome में DevTools खोलें (macOS पर Cmd+Option+I, बाक़ी जगह F12) और Sources टैब पर जाएँ। आपके बंडल आपके डोमेन के नीचे दिखेंगे। किसी एक पर क्लिक करें। अगर वह पहले से pretty-print नहीं है, तो नीचे {} बटन दबाएँ।

अब सभी लोड हुई फ़ाइलों में Cmd+Option+F (Windows और Linux पर Ctrl+Shift+F) से सर्च करें। /api, /v1, internal, admin, free, pro, trial, promo आज़माएँ, और किसी भी ऐसे फ़ीचर का नाम जिसकी आपने अभी घोषणा नहीं की है।

Minified JavaScript क्या-क्या उजागर करता है?

लोकल नामों के अलावा सब कुछ। मेरे डेमो का पूरा minified बंडल 995 बाइट का है:

(()=>{var f={LAUNCH40:.4,PARTNER_FRIENDS:.5};function i(e,t,o){let r=e*1200;e>=25&&(r*=.9),t&&(r*=1-.2);let p=o?f[o.toUpperCase()]??0:0;return r*=1-p,Math.round(r)}function a(e,t){return e==="free"?t<3:!0}var l={newCheckout:!0,aiSummaries:!1,teamSeatsBeta:!0};function s(e,t){return e==="aiSummaries"?t==="pro"||t==="team":l[e]}var u="https://api.example.com";async function c(e){return(await fetch(`${u}/v2/billing/invoices`,{headers:{Authorization:`Bearer ${e}`}})).json()}async function m(e){return fetch(`${u}/internal/admin/trials/${e}/extend`,{method:"POST"})}var n={id:"u_1",plan:"free",exportsThisMonth:2};document.querySelector("#price").textContent=String(i(10,!0,"launch40")/100);document.querySelector("#export").addEventListener("click",()=>{if(!a(n.plan,n.exportsThisMonth)){alert("Upgrade to Pro to export more than 3 files a month");return}s("aiSummaries",n.plan)&&console.log("summaries on")});window.__debug={fetchInvoices:c,grantTrial:m};})();
//# sourceMappingURL=app.js.map

लोकल नाम ग़ायब हैं; बाक़ी सब वहीं है, उन नामों समेत जो मैंने जानबूझकर window पर रखे थे, जैसे grantTrial:

सोर्स में क्या थाMinification के बाद पढ़ने लायक?बंडल में कहाँ है
लोकल फ़ंक्शन और वेरिएबल के नामनहीं, छोटे हो गएquote() बन गया i()
प्रोमो कोड और उनकी छूटहाँLAUNCH40:.4,PARTNER_FRIENDS:.5
सेंट्स में प्रति-सीट क़ीमत और 25 सीटों वाली वॉल्यूम छूटहाँe*1200, e>=25&&(r*=.9)
फ़्री प्लान की तीन एक्सपोर्ट वाली लिमिटहाँe==="free"?t<3:!0
तीनों feature flagsहाँnewCheckout:!0,aiSummaries:!1,teamSeatsBeta:!0
API होस्ट और routes, एक admin route समेतहाँ/v2/billing/invoices, /internal/admin/trials/${e}/extend
window पर रखे गए नामहाँwindow.__debug={fetchInvoices:c,grantTrial:m}
मूल फ़ाइलें, types और constants के नामसिर्फ़ तब, जब source map पब्लिक होsourceMappingURL वाली लाइन

Minification फ़ाइलों को छोटा करता है; इसका मक़सद कभी कुछ छिपाना था ही नहीं।

क्या प्रोडक्शन में source maps सुरक्षा के लिए ख़तरा हैं?

हाँ, जब map पब्लिक हो। Source map में आमतौर पर आपकी मूल फ़ाइलें होती हैं, इसलिए उसे सर्व करने का मतलब है अपना सोर्स पब्लिश करना।

हर बंडल की आख़िरी लाइन देखें। //# sourceMappingURL=app.js.map जैसा कमेंट ब्राउज़र को बताता है कि map कहाँ रखा है। कुछ सेटअप इसे SourceMap response header के रूप में भेजते हैं, इसलिए Network टैब भी देख लें।

फिर map को ख़ुद रिक्वेस्ट करें और status code नहीं, body जाँचें। कई होस्ट किसी भी अनजान path पर 200 और आपका index.html लौटा देते हैं। अगर यह 3 प्रिंट करता है, तो आप source map शिप कर रहे हैं:

curl -s https://example.com/assets/app.js.map | jq .version
# 3 (my demo, served locally; an HTML page gives a jq parse error instead)

Source map में आमतौर पर एक sourcesContent array होता है, जो source map फ़ॉर्मैट का एक वैकल्पिक फ़ील्ड है: आपकी मूल फ़ाइलें, ठीक वैसी जैसी आपने लिखी थीं। दो jq कॉल उनकी लिस्ट बनाते हैं और एक को प्रिंट करते हैं:

1curl -s https://example.com/assets/app.js.map | jq -r '.sources[]'
2# ../src/pricing.ts
3# ../src/flags.ts
4# ../src/api.ts
5# ../src/app.ts
6
7curl -s https://example.com/assets/app.js.map | jq -r '.sourcesContent[0]' | head -8
8# const SEAT_PRICE_CENTS = 1200;
9# const ANNUAL_DISCOUNT = 0.2;
10# const PROMO_CODES: Record<string, number> = {
11# LAUNCH40: 0.4,
12# PARTNER_FRIENDS: 0.5,
13# };
14#
15# export function quote(seats: number, annual: boolean, promo?: string) {

यह मेरा TypeScript है, अपने types और constants के नामों के साथ, सीधे मेरी डिस्क वाली फ़ाइल से। DevTools यही काम चुपचाप करता है: map लोड होते ही Sources टैब बंडल के बगल में आपका मूल फ़ोल्डर ट्री दिखा देता है। एक पब्लिश किया हुआ map ही Claude Code npm पैकेज को ख़बर बना गया, जबकि उसका बंडल पहले से ही पढ़ने लायक था।

क्या AI minified या ओबफस्केट किया हुआ JavaScript पढ़ सकता है?

हाँ। आज का मॉडल एक ही प्रॉम्प्ट में minified बंडल समझा देता है, प्राइसिंग फ़ॉर्मूले और खुले पड़े admin hook तक, और AI एजेंट ओबफस्केट किया हुआ कोड भी पढ़ लेते हैं: पिछले एक टेस्ट में एक एजेंट ने दो ओबफस्केटरों के पब्लिश किए हुए डेमो को 10 और 20 मिनट में साफ़ सोर्स में वापस बदल दिया।

2026-09-27 को मैंने ऊपर वाला minified डेमो बंडल (map के बिना) Claude Code 2.1.283 के ज़रिए Claude Sonnet 5 को दिया, बिना किसी टूल के, इस प्रॉम्प्ट के साथ, और उसके बाद बंडल एक js code fence में:

This is a minified JavaScript file I downloaded from a website. Explain what it does in plain English: the pricing rules, the feature flags, the API endpoints it calls, and anything a competitor could copy or abuse. Be concise.

उसने पूरा प्राइसिंग फ़ॉर्मूला निकाल लिया, यहाँ तक कि LAUNCH40 के साथ 10 सीटों वाले सालाना प्लान की क़ीमत भी: $57.60। उसने वह हिस्सा भी पकड़ लिया जो मैंने छिपाकर रखा था और जिसकी ओर कोई इशारा नहीं किया था:

window.__debug.grantTrial / fetchInvoices expose the admin trial-extension and invoice-fetch functions directly in the global scope of production JS. Anyone can open devtools and call __debug.grantTrial('<any user id>') from the browser console.

पूरा जवाब, बिना एडिट किए
1## Pricing logic (`i()`)
2- Base rate: **$12/seat/month** (`e*1200` in cents).
3- **25+ seats** → extra 10% off (stacks).
4- Annual billing (`t` truthy) → 20% off.
5- Promo codes (case-insensitive): `LAUNCH40` = 40% off, `PARTNER_FRIENDS` = 50% off. Discounts stack multiplicatively (seat discount × annual × promo).
6- Example in the code: 10 seats, annual, `launch40` → $120 → $96 (annual) → $57.60 (promo), shown as `57.6` on `#price`.
7
8## Feature gating
9- Export limit: free plan capped at **<3 exports/month**; every paid plan is unlimited (`a()`).
10- Flags: `newCheckout: true`, `aiSummaries: false`, `teamSeatsBeta: true` (`l`).
11- `aiSummaries` is force-enabled for `pro`/`team` plans regardless of the global flag — so it's effectively live for paying users even though the flag reads "off" (likely a soft-launch/A-B mechanism).
12
13## API endpoints
14- `GET /v2/billing/invoices` — bearer-token authenticated, returns invoice list.
15- `POST /internal/admin/trials/{userId}/extend` — an **internal/admin** endpoint shipped in a public client bundle, with no auth header attached in this call at all.
16
17## What a competitor/attacker could copy or abuse
18- **Hardcoded discount codes** (`LAUNCH40`, `PARTNER_FRIENDS`) are fully visible client-side, including their exact percentages — trivial to discover and use even if not publicly advertised.
19- **Exact free-tier limit** (3 exports/month) and pricing formula are exposed, handy for competitive benchmarking/undercutting.
20- **`window.__debug.grantTrial` / `fetchInvoices`** expose the admin trial-extension and invoice-fetch functions directly in the global scope of production JS. Anyone can open devtools and call `__debug.grantTrial('<any user id>')` from the browser console. If the server doesn't independently re-verify admin privileges (and the client sends no auth token here), this is a **self-service trial/privilege-extension exploit** — the single biggest risk in this file.
21- Naming leaks internal API structure (`/internal/admin/...`), which helps an attacker guess/probe other admin routes.
22- `teamSeatsBeta` flag reveals an unreleased feature (competitive roadmap intel).
23
24**Bottom line:** the real vulnerability isn't the pricing math, it's that debug/admin hooks (`grantTrial`) were left reachable from `window` in production — that should be stripped from the shipped bundle and the endpoint should require server-side admin auth regardless.

मैंने इसे सोर्स से मिलाकर देखा: इसमें हर तथ्य सही है। बस एक ढीला वाक्यांश है, <3 exports/month, जिसे आगे चलकर वह सही तरह से 3 बताता है। इसमें एक प्रॉम्प्ट लगा, और आपके प्रोडक्ट के बारे में जिज्ञासु कोई भी व्यक्ति इसे भेज सकता है।

अपनी साइट को खुले सोर्स कोड और source maps के लिए कैसे स्कैन करें

अपनी प्रोडक्शन URL AfterPack के मुफ़्त security scanner में पेस्ट करें; इसके लिए अकाउंट की ज़रूरत नहीं। यह पेज को एक असली ब्राउज़र में रेंडर करता है, उसकी लोड की हुई हर स्क्रिप्ट को इकट्ठा करता है, और बताता है कि कौन-से secrets खुले पड़े हैं, कौन-से source maps पब्लिक हैं, और हर बंडल कितना सुरक्षित है। यही स्कैनर टर्मिनल से भी चलता है:

npx afterpack@latest audit https://example.com

लाइव साइट का ऑडिट में लिखा है कि यह क्या-क्या जाँचता है और CI के लिए exit codes क्या हैं।

JavaScript सोर्स कोड कैसे सुरक्षित करें

पहले वह हटाएँ जिसे ओबफस्केशन नहीं बचा सकता, फिर बाक़ी को ओबफस्केट करें: पब्लिक source map या बंडल में रखी key कोड को उजागर कर देती है, बंडल चाहे जैसे भी लिखा हो।

क़दमकिससे बचाता हैकैसे
1. Source maps सर्व न करेंमूल फ़ाइलें, types और कमेंट्स पब्लिक होने सेHidden maps, निजी तौर पर अपलोड; .map फ़ाइलें deploy से हटाई गईं
2. Secrets और अधिकार सर्वर पर ले जाएँKeys पढ़े जाने से, लिमिट्स और admin routes को बायपास किए जाने सेसर्वर-साइड जाँच; क्लाइंट वाली जाँच सिर्फ़ UI के लिए
3. जो लॉजिक शिप होना ही है, उसे ओबफस्केट करेंStrings की सर्च से, लॉजिक पढ़े या पैच किए जाने सेnpx afterpack@latest dist/ --preset=hard, या कोई framework plugin

प्रोडक्शन में source maps कैसे बंद करें

उन्हें सर्व न करें। अगर error tracking के लिए उनकी ज़रूरत है, तो उन्हें sourceMappingURL कमेंट के बिना जनरेट करें:

Bundler या frameworkसेटिंगआपको क्या मिलता है
esbuild--sourcemap=externalएक .map फ़ाइल, बंडल में कोई कमेंट नहीं
Vitebuild.sourcemap: "hidden"एक .map फ़ाइल, बंडल में कोई कमेंट नहीं
webpackdevtool: "hidden-source-map"एक .map फ़ाइल, बंडल में कोई कमेंट नहीं
Rollupoutput.sourcemap: "hidden"एक .map फ़ाइल, बंडल में कोई कमेंट नहीं
Next.jsproductionBrowserSourceMapsडिफ़ॉल्ट रूप से बंद; जाँच लें कि किसी ने चालू तो नहीं किया
Create React AppGENERATE_SOURCEMAP=falseकोई map नहीं (डिफ़ॉल्ट रूप से ये चालू रहते हैं)

Maps को निजी तौर पर अपने error tracker पर अपलोड करें, फिर deploy डायरेक्टरी से .map फ़ाइलें हटा दें। सर्वर पर छूटा हुआ hidden map भी बस एक अंदाज़ा लगाए गए URL की दूरी पर है। Map पब्लिश किए बिना प्रोडक्शन traces डीबग करें में उस स्थिति के लिए एक edge rule है, जब कोई map फिर भी निकल जाए।

Secrets और जाँचें सर्वर पर रखें

बंडल में रखी API key हर उस व्यक्ति के लिए पढ़ने लायक है जो पेज चलाता है, फ़ाइल के साथ आप चाहे कुछ भी करें। फ़ैसलों के साथ भी यही बात है: सिर्फ़ ब्राउज़र में जाँची गई फ़्री-प्लान लिमिट बस एक सुझाव है, प्रोमो कोड सर्वर-साइड lookup में होने चाहिए, और मेरे डेमो का admin route उतना ही सुरक्षित है जितनी सर्वर की अपनी जाँच। क्लाइंट वाली जाँच UI के लिए रखें। लागू उसे सर्वर पर करें।

जो JavaScript शिप होना ही है, उसे ओबफस्केट करें

कुछ कोड सिर्फ़ ब्राउज़र में ही काम करता है, जैसे लाइव प्राइस कैलकुलेटर या इन-ब्राउज़र एडिटर। ऐसे कोड के लिए बिल्ड आउटपुट पर AfterPack चलाएँ:

npx afterpack@latest dist/ --preset=hard

--preset न देने पर आपको light मिलता है, जो string literals को एनकोड करता है। इससे आपके routes और flags की टेक्स्ट सर्च रुक जाती है; लेकिन प्राइसिंग का गणित समझना मुश्किल नहीं होता। जिस कोड को कोई प्रतिस्पर्धी चाहेगा, जैसे प्राइसिंग और प्लान गेटिंग, उसके लिए hard इस्तेमाल करें, या पहले किसी फ़ंक्शन को playground में पेस्ट करके देखें कि हर लेवल उसके साथ क्या करता है। Presets में लिखा है कि हर लेवल क्या जोड़ता है। hard के बाद भी मेरा डेमो वही $57.60 निकालता है (AfterPack 0.2.0, 2026-09-29), और अगर आप चाहते हैं कि यह काम आपका सामान्य बिल्ड ही कर दे, तो framework plugins मौजूद हैं।

रूप उतना ही मायने रखता है जितना preset। एक स्थिर बंडल निशाना होता है: कोई मॉडल या स्क्रिप्ट उसे एक बार रिवर्स इंजीनियर कर लेती है, और नतीजा आपकी अगली रिलीज़ तक काम करता है। AfterPack हर बिल्ड पर एक नया seed लेता है, इसलिए हर रिलीज़ संरचना के हिसाब से एक अलग प्रोग्राम होती है, और पिछले हफ़्ते के बंडल पर बनी deobfuscation स्क्रिप्ट या एनोटेट किया हुआ map इस हफ़्ते वाले पर लागू नहीं होता। आपके अपने Cloudflare Worker के अंदर @afterpack/wasm के साथ, कोड हर रिक्वेस्ट पर अपना रूप बदल सकता है; Workers ट्यूटोरियल में पूरा सेटअप है, और लॉन्च पोस्ट एक फ़ंक्शन को obfuscation से पहले और बाद में दिखाती है।

एक सर्च से शुरुआत करें: अपनी प्रोडक्शन साइट पर DevTools खोलें और internal खोजें। जो भी मिलता है, वह हर विज़िटर के पास पहले से है।

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

क्या JavaScript को यूज़र्स से पूरी तरह छिपाया जा सकता है?

नहीं। कोड चलाने के लिए ब्राउज़र को उसे डाउनलोड करना ही पड़ता है, इसलिए जो उसे मिलता है, उसे कोई भी पढ़ सकता है। आप secrets और अधिकार सर्वर पर रख सकते हैं, source maps पब्लिश करना बंद कर सकते हैं, और ओबफस्केशन से शिप होने वाले कोड को पढ़ना महँगा बना सकते हैं।

लोगों को मेरा JavaScript कॉपी या रिवर्स इंजीनियर करने से कैसे रोकूँ?

डाउनलोड आप नहीं रोक सकते; लेकिन कॉपी को पढ़ना महँगा और उसे जल्दी पुराना बना सकते हैं। --preset=hard से ओबफस्केट करें, हर बिल्ड पर (या अपने Worker से हर रिक्वेस्ट पर) नया रूप शिप करें, और अधिकार सर्वर पर रखें, ताकि कॉपी किया हुआ क्लाइंट मूल से ज़्यादा कुछ न कर सके। Pro का anti-tamper बदली हुई कॉपी को चलने की बजाय टूटने देता है।

क्या ओबफस्केशन API keys छिपाता है?

नहीं। बंडल में रखी key हर उस व्यक्ति के लिए पढ़ने लायक है जो पेज चलाता है, चाहे वह ओबफस्केट हो या नहीं। Keys सर्वर पर रखें और API को वहीं से कॉल करें; threat model बताता है कि ओबफस्केशन किससे बचाता है और किससे नहीं।

क्या ओबफस्केट किया हुआ JavaScript पहले जैसा ही काम करता है?

उसे करना चाहिए, और जब नहीं कर सकता, तो आपको बिल्ड के समय ही पता चल जाता है: जिस फ़ाइल को इंजन ओबफस्केट नहीं कर पाता, वह बिल्ड को फ़ेल कर देती है और फ़ाइल का नाम बताती है, इसलिए टूटा हुआ बंडल कभी चुपचाप शिप नहीं होता। अपने integration tests ओबफस्केट किए गए बिल्ड पर चलाएँ, सादे बिल्ड पर नहीं: शिप वही होता है।

क्या AfterPack मुफ़्त है?

लोकल इंजन हर preset के साथ मुफ़्त है, और CLI और framework plugins ओपन सोर्स हैं। Pro में हर हिस्से के लिए अलग directives, anti-tamper और मेगाबाइट के हिसाब से बिल होने वाले क्लाउड बिल्ड जुड़ते हैं; आँकड़े plans पर हैं।

— Nikita

अपडेट रहें

रिलीज़ नोट्स और लेखों के लिए AfterPack को फॉलो करें।