कोडची एक ओळ तुमचे संपूर्ण ॲक्सेस कंट्रोल मॉडेल उद्ध्वस्त करू शकते. जर तुम्ही रिक्वेस्ट बॉडी थेट डेटाबेस अपडेटमध्ये वापरली, तर तुम्ही क्लायंटला तुमचा स्कीमा पुन्हा लिहिण्यासाठी पेन सोपवल्यासारखे आहे. यालाच 'मास असाइनमेंट' (Mass Assignment) म्हणतात. ही एखादी दुर्मिळ त्रुटी किंवा किरकोळ केस नाही. ही एक डिझाइनमधील चूक आहे, जी तेव्हाच घडते जेव्हा एखादे API पेलोडला (payload) स्वतःच्या अपडेट पॉलिसीप्रमाणे वागवते.

await db.users.update(req.params.id, { ...req.body });

हे दिसायला स्वच्छ वाटते. यामुळे टायपिंग वाचते. पण कीजवर (keys) क्लायंटचे नियंत्रण असते. एखादा अटॅकर सामान्य प्रोफाइल अपडेटमध्ये "role": "admin", "accountId": "someone_else", किंवा "credit": 99999" जोडू शकतो. तुमचे व्हॅलिडेशन लेअर (validation layer) कदाचित ती मूल्ये स्ट्रिंग आहेत की नंबर आहेत हे तपासेल आणि ती योग्य आहेत असे म्हणेल. मात्र, व्हॅलिडिटी (validity) म्हणजे ऑथोरायझेशन (authorization) नव्हे. एखाद्या वापरकर्त्याला त्या विशिष्ट रेकॉर्डची मालकी असू शकते, पण याचा अर्थ असा नाही की त्याला त्यातील प्रत्येक फील्ड एडिट करण्याचा अधिकार असावा.

मास असाइनमेंट (Mass Assignment) प्रत्यक्षात कसे दिसते

धोका सोयीमध्ये दडलेला असतो. फ्रेमवर्क्स आणि ORMs मुळे JSON कीज थेट डेटाबेस कॉलमवर मॅप करणे अत्यंत सोपे होते. जेव्हा तुम्ही असे करता, तेव्हा तुम्ही डेटाबेसला केवळ कसे बदलावे हे सांगत नाही, तर काय बदलावे याबद्दल क्लायंटवर विश्वास ठेवण्यास सांगत असता.

प्रोफाइल अपडेट करणारा वापरकर्ता displayName आणि bio साठी वैध डेटा पाठवू शकतो, परंतु त्यासोबत role किंवा balance देखील चोरून पाठवू शकतो. जर तुमच्या कंट्रोलरने (controller) फक्त तो ऑब्जेक्ट पुढे पाठवला, तर डेटाबेस ते सर्व लिहून घेतो. व्हॅलिडेशन चुकीची मूल्ये पकडते, पण ते सहसा घातक कीज (malicious keys) पकडत नाही. "या वापरकर्त्याला त्यांचे प्रोफाइल अपडेट करण्याची परवानगी आहे" हा बिझनेस रूल (business rule) त्या रोमधील प्रत्येक कॉलमसाठी अनियंत्रित परवानगी बनतो.

उपाय म्हणजे अधिक व्हॅलिडेशन करणे नाही, तर अधिक कडक आर्किटेक्चर (architecture) तयार करणे आहे.

तीन प्रवेशद्वारे (The Three Gates)

एक सुरक्षित म्युटेशन (mutation) स्टोरेजला स्पर्श करण्यापूर्वी तीन वेगवेगळ्या तपासण्यांमधून जाते.

स्वीकारलेले फील्ड्स (Allowlist)

तुम्ही नेमक्या कोणत्या कीज पाहणार आहात हे ठरवून सुरुवात करा. जर एखादे फील्ड अलाऊलिस्टमध्ये (allowlist) नसेल, तर रिक्वेस्ट नाकारा किंवा ती की काढून टाका. यामुळे डीफॉल्ट पोश्चर बदलते: जोपर्यंत डेव्हलपर स्पष्टपणे एखादे फील्ड उघडत नाही, तोपर्यंत डेटाबेसचे नवीन कॉलम्स 'नॉन-राईटेबल' (non-writable) राहतात. स्कीमा कालांतराने वाढतात. एखादा सहकारी stripeCustomerId, departmentBudget, किंवा isVerified फ्लॅग जोडतो. अलाऊलिस्टमुळे, हे नवीन कॉलम्स क्लायंटच्या राइट्सपासून आपोआप सुरक्षित राहतात. अलाऊलिस्टशिवाय, प्रत्येक नवीन कॉलम अनवधानाने API चा भाग बनतो.

वैध मूल्ये (Valid Values)

एकदा तुम्हाला कोणते फील्ड्स अनुमत आहेत हे समजले की, मूल्ये अर्थपूर्ण आहेत का ते तपासा. टाइमझोन स्ट्रिंग खरोखर ओळखला जाणारा टाइमझोन आहे का? ईमेल फॉरमॅट ईमेलसारखा आहे का? संख्या योग्य मर्यादेत आहे का? ही एक स्वच्छता प्रक्रिया (hygiene) आहे. यामुळे तुमच्या सिस्टममध्ये कचरा जाण्यापासून रोखले जाते, परंतु हे गैरवापर थांबवत नाही. चुकीच्या व्यक्तीने role फील्डमध्ये अगदी वैध "admin" स्ट्रिंग पाठवली तरी ती धोकादायकच ठरते.

अधिकृत बदल (Authorized Transitions)

हे असे प्रवेशद्वार आहे जे बहुतेक टीम्स वगळतात आणि खऱ्या संरक्षणाचे केंद्र हेच आहे. एक सूक्ष्म प्रश्न विचारा: या विशिष्ट व्यक्तीला या विशिष्ट रेकॉर्डवरील हे विशिष्ट फील्ड बदलण्याची परवानगी आहे का? "वापरकर्ता ॲडमिन आहे का?" किंवा "वापरकर्त्याकडे write:users स्कोप आहे का?" असे विचारू नका. त्याऐवजी, "या वापरकर्त्याला स्वतःचे displayName बदलण्याची परवानगी आहे, पण accountId बदलण्याची नाही?" असे विचारा. प्रति-फील्ड ऑथोरायझेशनमुळे "Editor" किंवा "User" सारखी व्यापक परवानगी रोमधील प्रत्येक प्रॉपर्टीची मास्टर की बनण्यापासून रोखली जाते.

पॅच फंक्शन (Patch Function) तयार करणे

या तीन प्रवेशद्वारांना एका सिंगल पाईपलाईनमध्ये बांधा. जेव्हा पॅच रिक्वेस्ट येते, तेव्हा ती क्रमाने या टप्प्यांमधून चालवा.

प्रथम, तुमच्या अलाऊलिस्टच्या विरुद्ध इनपुट फिल्टर करा. जर role हे या एंडपॉइंटसाठी अनुमत फील्ड नसेल, तर तिथेच थांबवा. तुम्हाला कधीही प्राप्त न होणारी व्हॅल्यू व्हॅलिडेट किंवा ऑथोराईज करण्याची गरज नाही.

दुसरे म्हणजे, अनुमत मूल्यांची पडताळणी (validate) करा. प्रकार (types), फॉरमॅट आणि बिझनेस रूल्स तपासा. लोकेशन फील्ड हे स्ट्रिंग असावे जे खऱ्या टाइमझोनमध्ये रूपांतरित होईल. अवतार URL हे एका विशिष्ट लांबीच्या मर्यादेतील वैध URI असावे.

तिसरे म्हणजे, कृतीला अधिकृत (authorize) करा. संबंधित व्यक्तीकडे त्या रेकॉर्डची मालकी आहे की या फील्डसाठी आवश्यक असलेली नेमकी परवानगी आहे याची खात्री करा. वैयक्तिक डेटासाठी 'मालकी' (ownership) हा एक चांगला डीफॉल्ट पर्याय आहे, परंतु काही फील्ड्सना अजूनही अतिरिक्त गेट्सची गरज असते. एखाद्या वापरकर्त्याची स्वतःची प्रोफाइल असू शकते, तरीही फक्त बिलिंग ॲडमिनलाच taxRegion बदलण्याचा अधिकार असावा.

चौथे म्हणजे, डेटा नॉर्मलाईज (normalize) करा. व्हाईटस्पेस काढून टाका, वारंवार येणाऱ्या स्पेस एकत्र करा, ईमेल लोअरकेस करा किंवा कंट्रोल कॅरेक्टर्स काढून टाका. हे व्हॅलिडेशननंतर पण स्टोरेजपूर्वी करा, जेणेकरून ऑथोरायझेशन चेक दरम्यान तुम्हाला अशुद्ध स्ट्रिंग्सची (dirty strings) तुलना करावी लागणार नाही.

जर इनपुट कोणत्याही गेटवर (gate) अपयशी ठरले, तर संपूर्ण mutation नाकारून टाका. सुरक्षित फील्ड्स अंशतः लागू करू नका आणि खराब फील्ड्स शांतपणे सोडून देऊ नका. मिश्र प्रतिसाद (mixed response) क्लायंट्सना त्यांच्या कल्पनेतील प्रत्येक की (key) वापरून पाहण्यासाठी आणि त्यातून काय यशस्वी होते ते पाहण्यासाठी प्रवृत्त करतो. स्पष्टपणे अपयशी (Fail explicitly) घोषित करा.

खरोखर महत्त्वाचे असलेले एज केसेस (Edge Cases)

मास असाइनमेंट (Mass assignment) संरक्षण हे अशा तपशिलांवर अवलंबून असते जे युनिट टेस्ट्स (unit tests) अनेकदा चुकवतात.

ड्युप्लिकेट JSON कीज (Duplicate JSON keys). अटॅकर {"role": "user", "role": "admin"} सारखे पेलोड्स (payloads) पाठवू शकतात. तुमच्या HTTP पार्सर (parser) आणि फ्रेमवर्कवर (framework) अवलंबून, तुमच्या ॲप्लिकेशन कोडला ऑब्जेक्ट दिसण्यापूर्वीच दुसरे मूल्य पहिल्या मूल्याला ओव्हरराईट (overwrite) करू शकते. हे वर्तन पार्सर लेव्हलवर तपासा. जर तुमचे फ्रेमवर्क शांतपणे शेवटची की स्वीकारत असेल, तर तुमचे अलाऊलिस्ट (allowlist) "user" कडे पाहत असू शकते, तर डेटाबेसमध्ये "admin" प्राप्त होऊ शकते.

नेस्टेड ऑब्जेक्ट्स, नल्स आणि ॲरे (Nested objects, nulls, and arrays). पेलोड सपाट (flat) असेल असे गृहीत धरू नका. क्लायंट प्रतिबंधित फील्ड { "profile": { "role": "admin" } } सारख्या नेस्टेड ऑब्जेक्टमध्ये गुंडाळू शकतो. जर तुमच्या स्कीमामध्ये (schema) रिकर्शन (recursion) असेल, तर तुमच्या अलाऊलिस्टमध्येही ते असणे आवश्यक आहे. त्याचप्रमाणे, तुम्ही null कसे हाताळता हे ठरवा. याचा अर्थ "हे फील्ड दुर्लक्षित करा" असा आहे की "हे फील्ड हटवा" असा आहे? आणि जर ॲरेची (array) अपेक्षा असेल, तर तुमचा व्हॅलिडेटर (validator) अनपेक्षित स्ट्रक्चर्स नाकारतो की एखाद्या सिंगल ऑब्जेक्टला ॲरेमध्ये रूपांतरित करून पुढे जाऊ देतो?

युनिकोड नॉर्मलायझेशन (Unicode normalization). दोन स्ट्रिंग्स मानवी दृष्टीला सारख्या दिसू शकतात, परंतु त्यांचे बाइट्सचे अनुक्रम (sequences of bytes) वेगळे असू शकतात. वापरकर्ता प्रीकंपोज्ड (precomposed) é किंवा डीकंपोज्ड (decomposed) e आणि कॉम्बाइनिंग ॲक्सेंट (combining accent) पाठवू शकतो. जर तुमची ऑथोरायझेशन चेक (authorization check) एकदा नॉर्मलाईज करते पण तुमची स्टोरेज लेयर (storage layer) वेगळ्या पद्धतीने नॉर्मलाईज करते, तर तुमच्याकडे विसंगत डेटा असू शकतो किंवा त्याहून वाईट म्हणजे, युजरनेम कोलिजन (username collision) तुमच्या लॉजिकला चकवा देऊ शकतो. लवकर नॉर्मलाईज करा आणि सातत्याने नॉर्मलाईज करा.

रेस कंडिशन्स (Race conditions). ऑथोरायझेशनचे निर्णय हे स्थिर नसतात. ते एका विशिष्ट वेळेत घडतात. दोन विनंत्या (requests) एकच रेकॉर्ड वाचू शकतात, दोन्हीला असे दिसू शकते की ॲक्टरला लिहिण्याची परवानगी आहे, आणि दोन्ही अपडेट्स जारी करू शकतात. या दरम्यान, स्थिती (state) किंवा ॲक्टरच्या परवानग्या बदलल्या असू शकतात. नेहमी व्हर्जन नंबर (version number) किंवा स्टेट मशीन व्हॅल्यूवर (state machine value) कंडिशनसह डेटाबेस अपडेट्स लागू करा. UPDATE users SET ... WHERE id = ? AND version = 5 सारख्या कमांडचा वापर करा. जर तुम्ही ते वाचल्यापासून रो (row) बदलली असेल, तर राइट (write) ऑपरेशन अयशस्वी होईल. पुन्हा प्रयत्न करून किंवा नाकारून या अपयशाचे व्यवस्थापन करा. यामुळे जुन्या (stale) ऑथोरायझेशन चेकमुळे तुमचा डेटा खराब होणार नाही.

महत्त्वाच्या गोष्टींवर लक्ष ठेवा (Monitor)

तुम्ही ज्या गोष्टी पाहू शकत नाही, तिला सुरक्षित करू शकत नाही. तुमचे ऑडिट लॉगिंग (audit logging) केवळ कृतीवर (action) नाही, तर निर्णयावर (decision) आधारित असावे.

ॲक्टर आयडी (actor ID) आणि टार्गेट आयडी (target ID) लॉग करा. स्वीकारलेली नेमकी फील्ड नावे आणि नाकारलेली फील्ड नावे लॉग करा. निर्णय घेणारी पॉलिसी व्हर्जन (policy version) आणि अंतिम निकाल लॉग करा. जर एखाद्या वापरकर्त्याचे प्रोफाइल अपडेट करताना अचानक role नाकारले जाऊ लागले, तर तुम्हाला ते त्वरित समजले पाहिजे.

बेअरर टोकन्स (bearer tokens) कधीही लॉग करू नका. संपूर्ण रिक्वेस्ट बॉडीज (request bodies) तुमच्या लॉग्समध्ये टाकू नका. ऑडिट ट्रेलने (audit trail) तुम्हाला गैरवापर तपासण्यास मदत केली पाहिजे, ते क्रेडेंशियल्स (credentials) आणि वैयक्तिक डेटाचे भांडार बनू नये.

तुम्हाला आवश्यक असलेला एकमेव नियम

रिक्वेस्ट बॉडी डेटा प्रस्तावित करते. ती स्वतःचा अधिकार कधीही ठरवत नाही. क्लायंट काहीही मागू शकतो. तुमच्या सर्व्हरला ठरवावे लागते की, फील्डनुसार आणि रो (row) नुसार, कायमस्वरूपी स्टोरेजमध्ये काय साठवण्यास परवानगी आहे. हे पृथक्करण लक्षात घेऊन तुमचे पॅचेस (patches) तयार करा, आणि मास असाइनमेंट ही अशी समस्या बनेल जी तुमच्या ऑथोरायझेशन लेयरपर्यंत पोहोचण्यापूर्वीच तुम्ही थांबवली असेल.