Userscripts और extensions को अपने web app को patch करने से रोकें

एक userscript, Tampermonkey patch या extension को आपके JavaScript में किसी नाम, string या पहचाने जा सकने वाले आकार पर टिकना पड़ता है। संरचनात्मक obfuscation और build का नया आकार उन anchors के साथ क्या करते हैं।

एक userscript, community patch या extension आपके ship किए JavaScript में text match करके और उसे फिर से लिखकर आपके app को बदलता है। हर release पर match करने लायक चीज़ बदल दें, और patch लागू होना बंद हो जाता है।

Patch किस पर टिकता है

Patch लिखने वाले को एक स्थिर anchor चाहिए: किसी function का नाम, एक literal string, या कोई पहचाना जा सकने वाला आकार जिसे वह अगली download की गई फ़ाइल में फिर से ढूँढ सके। Minification तीनों को अपनी जगह छोड़ देता है। Minified bundle में अब भी function names होते हैं, छोटे किए हुए लेकिन हर build में एक जैसे, वही string literals, और वही समग्र संरचना, इसलिए एक release के लिए लिखा patch अगली release पर भी match करता रहता है।

इसीलिए एक अच्छा patch महीनों टिका रहता है। उसका लेखक उसे आपकी मौजूदा build पर test करता है, अपने users तक पहुँचाता है, और वह हर उस release में काम करता रहता है जो उसके anchors को नहीं छूती।

संरचनात्मक बदलाव वह आकार हटा देता है जिसे patch match करता है

सिर्फ़ नाम बदलने से anchor बस अपनी जगह से हटता। medium और ऊपर, AfterPack program को ही फिर से लिखता है: control flow flatten और reorder होता है, values को store करने के बजाय हर उपयोग पर फिर से compute किया जाता है, और जो code स्वतंत्र था वह entangled होकर निकलता है। जो ship होता है वह आपके लिखे code का functional समकक्ष है, एक अलग program जो वैसा ही व्यवहार करता है, जिसे उस build का seed चुनता है।

इसलिए जो patch पहले checkAccess में एक लाइन जोड़ता था, उसे पहले वह code ढूँढना होगा जो वही करता है जो checkAccess करता था। अब वह उस नाम का function नहीं रहा, और न ही उस आकार का function। AfterPack कैसे काम करता है इस mechanism को समझाता है।

जिन strings के लिए patcher grep करता है, वे मौजूद ही नहीं रहतीं

किसी भी positive complexity target पर strings.encode चालू रहता है, इसलिए आपके JavaScript के string literals artifact में text के रूप में नहीं होते। Feature का नाम, message type, API path, entitlement string, gate बंद होने पर दिखने वाला text: ship हुई फ़ाइल पर grep इनमें से कुछ भी नहीं लौटाता, और न ही इनमें से किसी पर लिखा गया find-and-replace कुछ पाता है।

ये literals सबसे सस्ता anchor हैं जिसका patch उपयोग कर सकता है, क्योंकि ये minification के बाद भी जस के तस रहते हैं और अपने आसपास का code क्या करता है, इसके documentation की तरह पढ़े जाते हैं। Build का encoding वाला हिस्सा ही इन्हें हटाता है।

हर release anchors को फिर से खिसका देती है

जब तक आप seed को pin न करें, वह हर build पर नया होता है, इसलिए नाम, encodings और आकार सब आपकी पिछली release से अलग होते हैं। तब patch उसे maintain करने वाले के लिए एक बार-बार की लागत बन जाता है: आपकी ship की हर build के लिए काम फिर से करना पड़ता है, और users तक पहले से पहुँची script उसी पल काम करना बंद कर देती है जब आप deploy करते हैं।

जहाँ patcher काम करता है, वहाँ सुरक्षा बढ़ाएँ

medium पूरी build में सुरक्षा बढ़ाता है:

{
  "preset": "medium"
}

जिस code को patch असल में निशाना बनाता है, जैसे entitlement check, उसके लिए hard पर जाएँ:

$ npx afterpack@latest dist/ --preset=hard

Production को random seed पर ही रहने दें। Constant seed ही वह चीज़ है जिसकी वजह से एक patch release दर release match करता रहता है; Seed strategy उन स्थितियों को कवर करती है जहाँ उसे pin करना सुरक्षित है। अगर आप कम ही ship करते हैं, तो schedule पर rebuild करें ताकि आकार फिर भी बदलता रहे; देखें Schedule पर rotate करें।

पक्का करें कि anchors हट गए हैं

जिस artifact को आप deploy करने वाले हैं, उसमें उन नामों और strings को खोजें जिन्हें कोई patch निशाना बनाता। कुछ भी वापस नहीं आना चाहिए।

Protection Map खोलें, जो protectionMap.enabled चालू होने पर लिखा जाता है, और पक्का करें कि जिस code के पीछे patch जाएगा वह उस complexity पर कवर है जिसकी आप अपेक्षा करते हैं। फिर deploy से पहले आख़िरी कदम के रूप में verify चलाएँ: build अपने output में एक protection receipt लिखती है, और verify receipt में दर्ज हर फ़ाइल को फिर से hash करता है और किसी के मेल न खाने पर विफल हो जाता है।

यह क्या नहीं रोकता

Patching जारी रहती है। नई build पढ़ने वाला कोई भी उसके लिए नया patch लिख सकता है, और build में ऐसा कुछ नहीं है जो किसी extension को उसके मालिक के नियंत्रण वाले browser में आपका page बदलने से रोके।

जो userscript आपके JavaScript के बजाय DOM पर टिका है, यानी CSS classes, element संरचना या दिखने वाले text को match करता है, उस पर AfterPack के bundle में किए बदलावों का कोई असर नहीं होता। बदलता है हर वह patch जिसे code ढूँढना पड़ता है: उसके लेखक को हर release पर वह काम फिर से करना पड़ता है, और उसके users तब तक टूटी हुई script चलाते हैं जब तक उन्हें update नहीं मिलता।

आगे

  • Build & CI: schedule पर rebuild करना और production में random seed रखना।
  • Best practices: पूरी seed strategy, जिसमें यह भी शामिल है कि उसे pin करना कब सुरक्षित है।
  • Paywall check को protect करें: वही mechanics एक entitlement gate पर लागू।