SSD Nodes Learn 🎉 VPS $5.50/महिन्यापासून
मार्गदर्शक Matt Connorद्वारे Matt Connor

तुमच्या सर्व्हरवरील npm supply-chain attack कसा होतो

Node app च्या VPS deploy मध्ये malicious patch release, postinstall script आणि typosquat कसे पोहोचतात ते जाणून घ्या. योग्य deploy पद्धतीने धोका थांबवा.

तुमच्या सर्व्हरवरील npm supply-chain attack म्हणजे काय

npm supply-chain attack तुमच्या सर्व्हरपर्यंत तुम्ही install करण्याचे निवडलेल्या package मार्फत पोहोचतो. यात कोणताही open port किंवा exploit step नसतो. npm (node package manager) code install करतो आणि code install करताना तो code run होतो. त्यामुळे छोटा Node application तुम्ही कधीही तपासलेली नसलेली शेकडो packages आणतो. त्यांपैकी कोणतीही package एका तासानंतर नवीन version publish करू शकते.

तुमच्या install command ने जुळणारी सर्वात नवीन version मागितल्यामुळे deploy malicious version fetch करतो. त्यानंतर तो code install चालवणाऱ्या व्यक्तीच्या privileges सह run होतो. पुढील सर्व माहिती या दोन वाक्यांवर आधारित आहे.

खालील प्रकार एका Node app ला एका VPS वर deploy करणाऱ्या व्यक्तीवर किती वेळा परिणाम करतात, त्यानुसार क्रमाने दिले आहेत. मोठी कंपनी हाच क्रम वापरणार नाही. कारण तिच्याकडे internal registry, review team आणि public registry चा mirror असतो. तुमच्याकडे deploy script आहे.

आकार 1: maintainer account तडजोडीत येतो आणि patch प्रकाशित करतो

npm registry आधीपासून अस्तित्वात असलेल्या version मधील contents कोणालाही बदलू देत नाही. एखादा attacker maintainer कडून phishing करतो किंवा publish token चोरतो, तरी तो 4.18.2 पुन्हा लिहू शकत नाही. तो 4.18.3 प्रकाशित करतो.

तुमचे package.json तपासा. "express": "^4.18.2" सारख्या line चा अर्थ version 4.18.2 असा होत नाही. caret म्हणजे “या version इतकी किंवा त्यापेक्षा जास्त असलेली कोणतीही 4.x version” असा अर्थ होतो, आणि ~4.18.2 म्हणजे “कोणतीही 4.18.x version”. npm install चालते त्या क्षणी ती range resolve करते. त्यामुळे त्याच git commit ला त्याच दुपारी दोनदा deploy केल्यास code चे दोन वेगवेगळे संच install होऊ शकतात. हीच तफावत attack surface असते. ती उघडण्यासाठी तुमच्या machine वरील कोणत्याही गोष्टीशी तडजोड झालेली असणे आवश्यक नाही.

Malicious releases ची सामान्यतः नोंद केली जाते आणि त्या registry मधून काढल्या जातात. परंतु हे काढणे लोकांनी त्या install केल्यानंतर होते. त्या कालावधीत deploy करणाऱ्या कोणाच्याही disk वर तो code उपलब्ध असतो. प्रत्येक run वेळी ranges resolve करणारी pipeline हा कालावधी आपोआप स्वीकारते. हे कोणाचाही निर्णय नसताना आठवड्यात अनेक वेळा घडते.

आकृती 2: deploy करणाऱ्या user म्हणून install script चालते

एखाद्या package चा package.json त्याच्या scripts block मध्ये preinstall, install, postinstall आणि prepare घोषित करू शकतो. npm installation दरम्यान ती scripts चालवते. त्या sandbox मध्ये चालत नाहीत आणि त्यांचे कोणी पुनरावलोकन करत नाही. install command टाइप करणाऱ्या user म्हणून, त्या user च्या home directory मध्ये, त्या user च्या network access सह आणि त्या shell च्या संपूर्ण environment सह shell commands चालतात.

म्हणून उपयुक्त प्रश्न package काय करू शकते हा नाही. तो user काय वाचू शकतो हा आहे. सामान्य deploy box वर यामध्ये registry token असलेली ~/.npmrc, SSH (secure shell) साठी deploy key म्हणून वापरली जाणारी ~/.ssh/id_ed25519, ~/.aws/credentials, ~/.docker/config.json आणि shell मधील प्रत्येक exported variable यांचा समावेश होतो. DATABASE_URL सहसा याच ठिकाणी असते.

अशा payload ला persistence किंवा privilege escalation ची गरज नसते. तो काही files वाचतो, त्या HTTPS द्वारे host कडे पाठवतो आणि status 0 सह बंद होतो. तुम्हाला काहीही दिसत नाही, कारण npm install-script output default ने लपवते. ते बंद करून प्रत्यक्षात काय चालते ते पहा:

npm ci --foreground-scripts

foreground-scripts standard input, output आणि error npm process सोबत share करते. त्यामुळे build scripts npm install यशस्वी झाल्यावर discard करत असलेल्या buffer मध्ये न जाता तुमच्या terminal मध्ये print होतात.

आकार 3: typosquats आणि तुम्ही पूर्णपणे टाइप न केलेले नाव

typosquat म्हणजे लोकप्रिय नावासारख्या नावाने प्रकाशित केलेले पॅकेज. चुकीच्या पद्धतीने टाइप केलेल्या किंवा चुकीच्या पद्धतीने paste केलेल्या install command ची ते वाट पाहते. ही पद्धत code पेक्षा command वर अवलंबून असते. त्यामुळे lockfile येथे उपयोगी पडत नाही: तुम्ही एकदा चुकीचे नाव जोडता आणि त्यानंतर lockfile ते नाव अचूकपणे pin करते.

व्यक्तींऐवजी टीम्सना अडचणीत आणणारा प्रकार म्हणजे dependency confusion. तुमचे अंतर्गत package billing-utils नावाचे आहे आणि ते private registry मध्ये आहे. public registry वर billing-utils नावाचे काहीही अस्तित्वात नसेल, तर कोणीही त्या नावाने package प्रकाशित करू शकतो. npm default public registry विरुद्ध unscoped नावे resolve करते. त्यामुळे public copy निवडली जाऊ शकते. यावरील उपाय म्हणजे तुमच्या नियंत्रणाखालील scope आणि त्या scope साठी registry mapping. हे mapping .npmrc मध्ये द्या:

@yourorg:registry=https://npm.yourorg.example/
//npm.yourorg.example/:_authToken=${NPM_TOKEN}

आता @yourorg/billing-utils फक्त त्या host कडूनच fetch केले जाते, कारण default registry वापरण्यापूर्वी scope-to-registry mapping तपासले जाते. Unscoped अंतर्गत नावासाठी कोणतेही mapping नसते. त्यामुळे त्याला संरक्षण मिळत नाही.

कोणतेही नवीन dependency जोडण्यापूर्वी त्याच्या download badge ऐवजी package कडे पाहा:

npm view some-lib repository.url maintainers time.created time.modified

गेल्या महिन्यात तयार केलेले आणि ज्याचा publisher तुम्ही कोणत्याही public repository शी जोडू शकत नाही असे package, सहा वर्षांचा इतिहास असलेल्या package पेक्षा वेगळा धोका निर्माण करते. यापैकी कोणतीही बाब पुरावा नाही. मात्र दोन्ही बाबी तपासणे सोपे आहे.

आकार 4: ज्याचा मालक शांतपणे बदलला ती dependency

Maintainers पॅकेजची जबाबदारी दुसऱ्यांकडे सोपवतात. एखादी व्यक्ती थकून काम सोडते, अनोळखी व्यक्ती मदतीची ऑफर देते, publish अधिकार दुसऱ्याकडे जातात आणि यावर अवलंबून असलेल्या projects पर्यंत कोणतीही notification पोहोचत नाही. काहीही compromised झालेले नसते. तुम्ही 2021 मध्ये दिलेला trust आता वेगळ्या व्यक्तीकडे असतो.

हा सर्वात धीमा आकार आहे आणि तो शोधणे सर्वात कठीण आहे. कोणताही command याचे थेट उत्तर देत नाही. दोन गोष्टी शोधक्षेत्र कमी करतात. एखादे package स्वीकारण्यापूर्वी कोण publish करू शकते ते वरील npm view line वापरून तपासा. त्यानंतर तुम्ही प्रत्यक्षात वापरत असलेले package बदलल्यास diff वाचा:

npm diff --diff-name-only --diff=some-lib@1.4.2 --diff=some-lib@1.4.3
npm diff --diff=some-lib@1.4.2 --diff=some-lib@1.4.3

पहिला प्रकार फक्त बदललेल्या filenames दाखवतो. त्यामुळे तुम्हाला महत्त्वाच्या package च्या प्रत्येक upgrade वेळी तो चालवणे पुरेसे जलद आहे. build script मध्ये बदल करणारा, package root मध्ये file जोडणारा किंवा scripts block संपादित करणारा patch release तुमच्या server वर पोहोचण्यापूर्वी पूर्णपणे वाचणे योग्य आहे.

प्रतिबद्ध lockfile वरून npm ci ने build करा

package-lock.json tree मधील प्रत्येक package ची अचूक version, तो package ज्या URL वरून मिळाला तो URL, प्रत्येक tarball चा sha512 integrity hash आणि कोणत्या package ला त्याची आवश्यकता होती हे नोंदवते. ही file commit करा. तुम्ही प्रत्यक्षात काय test केले आहे हे सांगणारी हीच एकमेव file आहे.

Developer laptop नसलेल्या कोणत्याही machine वर installation साठी npm ci वापरा; npm install कधीही वापरू नका:

npm ci --omit=dev --ignore-scripts

npm ci आणि npm install यांच्यातील फरक येथे महत्त्वाचे आहेत. npm ci साठी lockfile अस्तित्वात असणे आवश्यक आहे. सुरू होण्यापूर्वी ते विद्यमान node_modules काढून टाकते. त्यामुळे आधीच्या deploy मधील उरलेल्या files या deploy मध्ये टिकून राहत नाहीत. ते package.json किंवा lockfile मध्ये कधीही लिहित नाही. त्यामुळे installation मुळे version गुपचूप पुढे जाऊ शकत नाही. lockfile आणि package.json यांच्यात विसंगती असल्यास ते फरक सोडवण्याऐवजी error देऊन थांबते.

हा error त्रास नसून याच प्रक्रियेचा उद्देश आहे. Dependency मधील बदल कोणीतरी review केलेल्या commit द्वारेच यायला हवा. तो 02:00 वाजता झालेल्या deploy चा अनपेक्षित परिणाम नसावा.

प्रत्येक fetch वेळी integrity hash तपासला जातो. ज्यातील bytes नोंदवलेल्या hash शी जुळत नाहीत असा tarball unpack करण्याऐवजी code EINTEGRITY सह installation अयशस्वी करतो. यामुळे नेमके काय सुनिश्चित होते हे स्पष्टपणे समजा: तुम्हाला मिळालेली file ही lockfile ने pin केलेलीच file आहे, हे सिद्ध होते. हीच हमी checksums वापरून downloads पडताळणे देते आणि तिच्याही मर्यादा याच आहेत. Pin केलेली version प्रकाशित होताना malicious होती की नाही, याबद्दल ती काहीही सांगत नाही.

--omit=dev बाबत एक तपशील लक्षात घ्या: ही packages अद्याप resolve केली जातात आणि lockfile मध्ये लिहिली जातात. मात्र ती disk वर ठेवली जात नाहीत. Disk वर कमी packages असल्यामुळे install scripts कमी चालतात आणि runtime वेळी कमी code load होतो. त्यामुळे हे करणे उपयुक्त आहे. मात्र यामुळे तुमच्या tree मधून dependency काढली जात नाही.

Treat install scripts as code, and know how to refuse them

You can turn install scripts off. Put this in the project's .npmrc and commit it next to the lockfile:

ignore-scripts=true
save-exact=true

ignore-scripts=true stops npm running the scripts declared in dependencies. save-exact=true makes npm install some-lib write 1.4.2 into package.json instead of ^1.4.2, so a resolving range never enters your manifest by accident.

This breaks things, and you should know how before you enable it. Packages that compile a native addon or download a prebuilt binary do that work in an install script. With scripts off, the install itself succeeds and the failure appears later, at runtime, as a module that cannot load its binding file. The answer is an allowlist:

npm ci --ignore-scripts
npm rebuild better-sqlite3

npm rebuild <package> runs the build scripts for that one package. You have now made a decision per package, instead of granting blanket execute permission to a few hundred strangers you will never meet.

To see how large that grant currently is, ask npm:

npm query ":attr(scripts, [postinstall])"

That prints every package in the installed tree that carries a postinstall script. On a typical application the list is shorter than people expect, which is exactly what makes the allowlist practical.

बिल्ड आणि ट्रॅफिक हाताळणारी प्रक्रिया वेगळी ठेवा

deploy वापरकर्त्याला node_modules मध्ये लिहिण्याची आवश्यकता आहे. HTTP विनंत्यांना प्रतिसाद देणाऱ्या प्रक्रियेला ही आवश्यकता नाही. दोन्हींसाठी एकच account वापरल्यास, install दरम्यान चालणारा code तुमच्या वापरकर्त्यांना सेवा देणारा code पुन्हा लिहू शकतो. Runtime दरम्यान चालणारा code देखील तेच करू शकतो.

दोन्ही वेगळे ठेवा. एका वापरकर्त्याच्या account मधून build करा, दुसऱ्या account मधून सेवा द्या आणि serving account साठी सेवा दिली जाणारी directory read-only करा:

sudo useradd --system --home-dir /srv/nodeapp --shell /usr/sbin/nologin nodeapp
sudo chown -R deploy:nodeapp /srv/nodeapp
sudo chmod -R o-rwx /srv/nodeapp

यानंतर systemd कडून हे लागू करून घ्या. /etc/systemd/system/nodeapp.service लिहा:

[Unit]
Description=Node application
After=network-online.target

[Service]
User=nodeapp
Group=nodeapp
WorkingDirectory=/srv/nodeapp/current
EnvironmentFile=/etc/nodeapp/env
ExecStart=/usr/bin/node server.js
Restart=on-failure

NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/srv/nodeapp/shared
NoExecPaths=/srv/nodeapp/shared
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

[Install]
WantedBy=multi-user.target

ProtectSystem=strict या सेवेच्या दृष्टीने संपूर्ण file system read-only mount करते. मात्र /dev, /proc, /sys आणि ReadWritePaths मध्ये तुम्ही नमूद केलेल्या इतर path याला अपवाद असतात. त्यामुळे application ने node_modules मध्ये लिहिण्याचा प्रयत्न केल्यास तो EROFS: read-only file system सह अपयशी ठरतो. तुम्ही हे स्वतःच्या logs मध्ये जाऊन सुमारे एका मिनिटात पुन्हा पडताळू शकता. NoExecPaths writable upload directory साठी आहे. सेवा तेथे files लिहू शकते, पण kernel त्यांना execute करण्यास नकार देतो. या पर्यायासाठी systemd 249 किंवा त्यानंतरची आवृत्ती आवश्यक आहे. Ubuntu 24.04 मध्ये 255 आवृत्ती येते.

या unit file मध्ये दोन गोष्टींची काळजी घ्या. पहिली, MemoryDenyWriteExecute=yes जोडू नका. हे बहुतेक systemd hardening lists मध्ये दिसते. मात्र यामुळे Node सुरू होत नाही, कारण V8 runtime दरम्यान JavaScript चे machine code मध्ये compilation करते आणि writable तसेच executable असलेली pages आवश्यक असतात. दुसरी, ExecStart path command -v node मधून घ्या. Node version manager वापरून install केले असल्यास ते deploy वापरकर्त्याच्या home directory खाली असते. ProtectHome=yes नंतर ती directory सेवेपासून लपवते आणि unit status=203/EXEC सह लगेच अपयशी ठरते. Log मध्ये executable सापडले नाही अशी नोंद दिसते.

File वर विश्वास ठेवण्याऐवजी परिणाम तपासा:

sudo systemctl daemon-reload
sudo systemctl enable --now nodeapp
sudo systemd-analyze security nodeapp.service
sudo -u nodeapp touch /srv/nodeapp/current/probe

systemd-analyze security प्रत्येक hardening setting आणि तिचे exposure दाखवते. त्यामुळे कोणत्या settings अजून default वर आहेत ते पाहता येते. touch Permission denied सह अपयशी ठरले पाहिजे, कारण nodeapp कडे current अंतर्गत काहीही मालकीचे नाही. ते यशस्वी झाल्यास file ownership चुकीचे आहे. अशा वेळी systemd settings प्रत्यक्ष समस्या लपवत आहेत.

EnvironmentFile बद्दल एक गोष्ट लक्षात ठेवा: systemd User=nodeapp वर switch होण्यापूर्वी ते root म्हणून वाचते. त्यामुळे त्या file ला mode 600 सह root:root ठेवता येते. Application ला variables तरीही मिळतात. nodeapp म्हणून shell access असलेली कोणतीही व्यक्ती ती variables /proc/<pid>/environ मधून वाचू शकते. त्यामुळे हे secret at rest चे संरक्षण करते; चालू process चे नाही.

बिल्ड environment मधून deploy credentials बाहेर ठेवा

Install scripts ला environment वारसा म्हणून मिळते. त्यामुळे बिल्ड कुठे करायचे हे ठरवताना हा एक तथ्य पुरेसा आहे.

सर्वात सुरक्षित पद्धत म्हणजे production server नसलेल्या ठिकाणी बिल्ड करणे आणि पूर्ण झालेली directory server वर कॉपी करणे. त्यामुळे build machine वर फक्त read-only registry token असतो. SSH deploy key, cloud access key, database password किंवा container registry login यापैकी काहीही तेथे ठेवलेले नसते.

npm token create --read-only

Read-only token packages fetch करू शकतो, पण publish करू शकत नाही. तो build environment मधून चोरीला गेला, तरी नुकसान public packages download करण्यापुरतेच मर्यादित राहते.

जर server वरच बिल्ड करणे आवश्यक असेल, तर deploy user म्हणून, जाणीवपूर्वक मर्यादित environment मध्ये बिल्ड करा. Runtime secrets /etc/nodeapp/env मध्ये ठेवा; deploy त्यांना वाचू शकत नाही. तुम्ही स्वतः host करत असलेल्या build automation साठीही हेच तर्क लागू होतात: self-hosted GitHub Actions runner मध्ये tokens असतात आणि प्रत्येक job मध्ये प्रकाशित केलेला arbitrary code execute केला जातो. त्यामुळे छोट्या deployment मध्ये हे सर्वाधिक मौल्यवान machine ठरते. तुम्ही लिहिलेला नसलेला आणि तुमचे संपूर्ण environment मिळवणारा कोणताही program याच श्रेणीत येतो. म्हणूनच AI agent च्या environment मधून secrets बाहेर ठेवणे हीच समस्या वेगळ्या program ला मध्यस्थीला ठेवून सोडवण्याची पद्धत आहे.

तुम्ही audit करू शकत नसलेल्या dependency ला pin करा किंवा vendor करा

Pinned dependency ची आवृत्ती commit केल्याशिवाय बदलता येत नाही. संपूर्ण dependency tree साठी committed lockfile आधीच हे करते. मात्र दोन प्रकरणांसाठी अतिरिक्त उपाय आवश्यक आहेत.

पहिले प्रकरण म्हणजे transitive dependencies. तुमच्या dependencies कोणत्या dependencies वर अवलंबून आहेत, यावर तुमचे नियंत्रण नसते. overrides मधील package.json वापरल्यास संपूर्ण tree मध्ये कुठेही एक आवृत्ती सक्तीने लागू होते:

{
  "overrides": {
    "some-transitive-lib": "1.4.2"
  }
}

हे जोडल्यावर एकदा npm install चालवा, जेणेकरून lockfile मध्ये परिणाम नोंदवला जाईल. त्यानंतर दोन्ही files commit करा.

दुसरे प्रकरण म्हणजे अशी package जिचे तुम्ही audit करू शकत नाही आणि जी काढूनही टाकता येत नाही. अशा package ला vendor करा. npm pack registry कडून मिळणारा अचूक tarball डाउनलोड करते. file: dependency तुमच्या local copy मधून install करते:

npm pack some-lib@1.4.2
mkdir -p vendor && mv some-lib-1.4.2.tgz vendor/
{
  "dependencies": {
    "some-lib": "file:vendor/some-lib-1.4.2.tgz"
  }
}

आता tarball तुमच्या repository मध्ये आहे आणि तुमच्या नकळत बदलू शकत नाही. मात्र त्याच्या updates ची जबाबदारी कायमची तुमच्यावर येते. त्यामुळे हा उपाय तुम्ही वापरू न शकणाऱ्या, लहान आणि abandoned package साठी वापरा; web framework साठी नाही.

Cooling-off period हाही एक विनामूल्य उपाय आहे:

npm install --before=2026-08-01

before option त्या तारखेला किंवा त्यापूर्वी publish झालेल्या versions वापरून tree पुन्हा तयार करते. Dependencies refresh करताना तारीख एक किंवा दोन आठवडे मागे ठेवा. त्यामुळे एखादा bad release live झाल्यानंतर आणि त्याची नोंद होण्यापूर्वीचा कालावधी टाळता येतो. हा blunt tool आहे, कारण यामुळे वास्तविक security fixes देखील पुढे ढकलले जातात. Ranges resolve करण्यासाठी तो वापरा, झालेला बदल तपासा आणि नंतर lockfile commit करा.

प्रत्यक्षात कोणती आवृत्ती deploy केली हे कसे कळेल?

git मधील lockfile मध्ये कोणती आवृत्ती install व्हायला हवी होती हे नमूद असते. डिस्कवर प्रत्यक्षात काय install आहे ते दिसते. यापैकी फक्त दुसरे पुरावा मानता येते.

npm ls some-lib
node -e "console.log(require('./node_modules/some-lib/package.json').version)"

npm ls हे node_modules वाचते. त्यामुळे lockfile मध्ये अपेक्षित असलेली आवृत्ती नव्हे, तर प्रत्यक्ष उपलब्ध असलेली आवृत्ती दाखवली जाते. node -e ही ओळ path द्वारे installed manifest वाचते. त्यामुळे एखाद्या package मधील exports field मुळे subpath imports प्रतिबंधित असले तरी ती कार्य करते. तसेच tree drawing न दाखवता एकच आवृत्ती छापते.

तुलनेसाठी दुसरी बाजू git मधून वाचा:

git log --oneline -- package-lock.json
git show <commit>:package-lock.json | grep -A3 '"node_modules/some-lib"'

Commit deploy layout मध्ये समाविष्ट करून या दोन्ही गोष्टींमधील संबंध कायमचा ठेवा. /srv/nodeapp/releases/<short commit sha> मध्ये release ठेवा आणि symlink द्वारे /srv/nodeapp/current ला त्याकडे निर्देशित करा. “सध्या काय चालू आहे” या प्रश्नाचे उत्तर readlink /srv/nodeapp/current असेल. तसेच deploy करणारी व्यक्ती नसलेल्या व्यक्तीलाही ते 03:00 वाजता उपलब्ध असेल.

शेवटी, registry कोणत्या गोष्टीची खात्री देईल ते तपासा:

npm audit signatures

यामुळे तुमच्या installed tree मधील packages वरील registry signatures पडताळल्या जातात. ज्यांच्याकडे provenance attestations आहेत, त्या packages साठी त्या attestations देखील पडताळल्या जातात. Provenance मुळे published tarball तो तयार करणाऱ्या सार्वजनिक continuous integration (CI) build शी जोडला जातो. त्यामुळे verified attestation असल्यास code अज्ञात laptop ऐवजी एखाद्या commit पर्यंत trace करता येतो. ही coverage सर्वत्र उपलब्ध नसते. त्यामुळे attestation नसल्याचा अर्थ “माहिती उपलब्ध नाही” असा घ्या; “package खराब आहे” असा नाही.

सर्व्हरवर सदोष release पोहोचल्यानंतर काय करावे

काय चालले आणि कोणत्या user अंतर्गत चालले, यापासून तपासणी सुरू करा.

कोड install दरम्यान चालला असल्यास, build user ला वाचता येणारे सर्व काही उघड झाले आहे असे गृहीत धरा. registry token, त्या home directory मधील SSH keys, cloud credentials आणि त्या shell मध्ये exported केलेले कोणतेही secret बदला. बदल करणे हाच योग्य प्रतिसाद आहे, कारण एखादी file वाचली गेली नाही हे तुम्ही सिद्ध करू शकत नाही.

कोड runtime वेळी locked-down service account अंतर्गत चालला असल्यास, त्याला पोहोचता येणारा संच खूपच लहान असतो: अॅप्लिकेशनचे स्वतःचे environment variables आणि त्याच्या network access द्वारे पोहोचता येणाऱ्या गोष्टी. VPS वर unprivileged users म्हणून services चालवण्याचा हा संपूर्ण आधार आहे. यामुळे compromise थांबत नाही. त्या compromise मुळे मशीनचा किती भाग धोक्यात येतो आणि reboot नंतर तो टिकून राहतो का, हे यावर ठरते.

त्यानंतर साफसफाई करण्याऐवजी पुन्हा build करा. node_modules delete करा, प्रभावित package ची version सदोष version पेक्षा कमी असलेली pin करा: package.json, lockfile update करण्यासाठी npm install एकदाच चालवा, ते commit करा आणि npm ci वापरून deploy करा. चालू tree मध्ये थेट दुरुस्ती करू नका. install script ने कोणत्या गोष्टींवर परिणाम केला हे तुम्ही पूर्णपणे मोजू शकत नाही.

ही कालमर्यादाही नोंदवा: ती version आणू शकणारा पहिला deploy आणि ती version काढून टाकणारा deploy. या कालावधीत तुमचे कोणते logs वाचायचे हे ठरते. तुमच्या releases ची नावे commits वर आधारित असल्यासच या प्रश्नाचे उत्तर देता येते.

यापैकी कोणतीही गोष्ट काय दुरुस्त करत नाही

lockfile मुळे dependency सुरक्षित होत नाही. ती dependency स्वीकारल्याचा क्षण नोंदवून, deploy च्या अनपेक्षित परिणामाऐवजी त्याला दिनांकित आणि पुनरावलोकन केलेला निर्णय बनवते. वरील प्रत्येक पद्धत हाच बदल घडवते: अपघाताचे निवडीमध्ये रूपांतर.

npm audit हे येथे संरक्षण देत नाही. ते तुमच्या tree ची नोंदवलेल्या vulnerabilities च्या database शी तुलना करते. त्यामुळे आधीच प्रकाशित आणि नाव दिलेल्या समस्या सापडतात. supply-chain attack त्याच्या संपूर्ण उपयुक्त कालावधीत अनामित असतो. जुन्या, ज्ञात bugs साठी npm audit चालवा; मात्र चार तासांपूर्वी release झालेल्या आवृत्तीबद्दल त्याच्याकडून काहीही अपेक्षित ठेवू नका.

या मार्गदर्शिकेतील कोणत्याही tool पेक्षा dependency ची संख्या कमी करणे अधिक उपयुक्त ठरते. मात्र हा सल्ला सर्वांत कमी दिला जातो. तुम्ही न जोडलेले प्रत्येक package म्हणजे तुमच्या वतीने फिशिंग करता न येणारा आणखी एक publisher आणि deploy user म्हणून कधीही न चालणारी आणखी एक install script.

यापैकी कोणतीही गोष्ट फक्त npm पुरती मर्यादित नाही. हेच चार नमुने PyPI, RubyGems, container images आणि तुमच्या distribution च्या package manager ला लागू होतात. npm मध्ये ही समस्या सर्वाधिक दिसते, कारण त्याचे trees सर्वांत खोल असतात आणि install scripts default ने चालतात. तुमच्या संरक्षणाची जबाबदारी असलेल्या आसपासच्या machine चा विस्तार ती कुठे चालते यावर अवलंबून असतो. हा VPS hosting सुरक्षित आहे का या व्यापक प्रश्नाचा एक भाग आहे.

FAQ

npm ci मला breached npm package पासून संरक्षण देते का?

हे माझ्या नकळत version बदलला जाण्यापासून संरक्षण देते. npm ci नेमके तेच install करते जे package-lock.json नोंदवते, प्रत्येक tarball चा sha512 integrity hash शी मेळ तपासते आणि package.json व lockfile यांच्यात विसंगती असल्यास फरक स्वतः सोडवण्याऐवजी error सह थांबते. Pinned version सुरक्षित आहे की नाही, याबद्दल ते काहीही सांगत नाही. तुम्ही malicious version pin करणारे lockfile commit केले, तर npm ci तुमच्या प्रत्येक server वर प्रत्येक वेळी तोच version अचूकपणे install करेल.

प्रत्येक गोष्टीसाठी ignore-scripts=true सेट करावे का?

ते सेट करा आणि नंतर allowlist तयार करा. Project च्या .npmrc मधील ignore-scripts=true dependency install scripts चालू देत नाही. त्यामुळे चुकीच्या package पासून deploy user च्या credentials पर्यंत पोहोचण्याचा सर्वात थेट मार्ग बंद होतो. Native addon compile करणाऱ्या किंवा prebuilt binary fetch करणाऱ्या packages ना त्यांचे scripts खरोखर आवश्यक असतात. Scripts बंद असल्यास ती packages install वेळी न थांबता नंतर runtime वेळी missing binding file मुळे fail होतात. npm ci --ignore-scripts चालवा आणि ज्यांच्यावर विश्वास ठेवण्याचे तुम्ही ठरवले आहे त्या मोजक्या packages साठी npm rebuild <package> चालवा. प्रत्यक्षात अशी packages किती आहेत हे npm query ":attr(scripts, [postinstall])" दाखवते.

माझ्या server ने प्रत्यक्षात कोणता package version install केला हे कसे शोधावे?

Lockfile नव्हे, disk वाचा. npm ls <package> मध्ये node_modules सध्या काय उपलब्ध आहे ते दाखवते आणि node -e "console.log(require('./node_modules/<package>/package.json').version)" फक्त version string print करते. Git मधील lockfile वेगळ्या प्रश्नाचे उत्तर देते: कोणता version install व्हायला हवा होता. या दोन्हींची तुलना करणे हाच उद्देश आहे. Git commit च्या नावावर आधारित directory मध्ये deploy केल्यास अनेक महिन्यांनंतरही दोन्ही उत्तरे उपलब्ध राहतात.

npm audit supply-chain attacks शोधते का?

नाही. npm audit तुमच्या tree ची नोंदवलेल्या vulnerabilities च्या database शी तुलना करते. त्यामुळे आधीच प्रकाशित होऊन identifier दिलेल्या issuesच ती शोधू शकते. Malicious release install करणे महत्त्वाचे असते त्या काही तासांत किंवा दिवसांत त्याची नोंद झालेली नसू शकते. npm audit signatures ही अधिक उपयुक्त command आहे. ती तुमच्या installed tree मधील registry signatures पडताळते आणि publisher ने उपलब्ध करून दिल्यास provenance attestations तपासते. त्यामुळे tarball public build मधून आला आहे की अज्ञात machine मधून, हे समजते.

Attack install वेळी होत असेल, तर app unprivileged user म्हणून चालवणे का महत्त्वाचे आहे?

कारण या दोन्ही failures ची पोहोच वेगळी आहे आणि तुम्ही दोन्हींपासून संरक्षण करत आहात. Install-time code deploy user म्हणून चालतो आणि त्या user च्या SSH keys, registry tokens व cloud credentials वाचू शकतो. Runtime code service account म्हणून चालतो. User=nodeapp, ProtectSystem=strict आणि disk वर credentials नसल्यास त्याची पोहोच application's own environment आणि database पुरतीच मर्यादित राहते. Accounts वेगळे ठेवल्याने traffic serve करणारा process node_modules पुन्हा लिहू शकत नाही. त्यामुळे runtime compromise कायमस्वरूपी होण्याऐवजी पुढील restart वेळी नाहीसा होतो.