SSD Nodes Learn 🎉 VPS $5.50/மாதம் முதல்

npm supply-chain attack தடுப்பது எப்படி?

npm packages மூலம் உங்கள் Node app-ல் ஊடுருவும் malicious scripts மற்றும் typosquats தாக்குதல்களைத் தடுக்கும் முறைகள். பாதுகாப்பான deploy செயல்முறைகளை இந்த வழிகாட்டி விளக்குகிறது.

npm supply-chain attack என்றால் என்ன

npm supply-chain attack என்பது நீங்கள் நிறுவும் ஒரு package மூலம் உங்கள் server-ஐ வந்தடைகிறது. இதில் open port-களோ அல்லது exploit படிநிலைகளோ கிடையாது. npm (node package manager) நிரல்களை நிறுவுகிறது; நிரலை நிறுவுவதே அந்த நிரலை இயக்குவதற்குச் சமம். எனவே, ஒரு சிறிய Node application-ஐ நிறுவும்போது, நீங்கள் இதுவரை பார்த்திராத பல நூறு packages-கள் பதிவிறக்கம் செய்யப்படுகின்றன. அவற்றில் ஏதேனும் ஒன்று, எந்த நேரத்திலும் புதிய version-ஐ வெளியிடலாம்.

உங்கள் deploy செயல்முறை ஒரு malicious version-ஐப் பதிவிறக்குகிறது, ஏனெனில் உங்கள் install command புதிய version-ஐத் தேடுகிறது. அந்த நிரல், install செய்த பயனரின் உரிமைகளுடன் (privileges) இயங்குகிறது. கீழே உள்ள அனைத்தும் இந்த இரண்டு வாக்கியங்களின் விளைவுகளே.

ஒரு VPS-ல் ஒரு Node app-ஐ deploy செய்யும் தனிநபர் மீது இவை எந்த அளவுக்குத் தாக்கத்தை ஏற்படுத்துகின்றன என்பதைப் பொறுத்து வரிசைப்படுத்தப்பட்டுள்ளன. பெரிய நிறுவனங்கள் internal registry, review team மற்றும் public registry-ன் mirror ஆகியவற்றைப் பயன்படுத்துவதால், இந்த வரிசை அவற்றுக்குப் பொருந்தாது. உங்களிடம் ஒரு deploy script மட்டுமே உள்ளது.

வடிவம் 1: ஒரு பராமரிப்பாளர் கணக்கு சமரசம் செய்யப்பட்டு, ஒரு patch வெளியிடப்படுதல்

npm registry ஏற்கனவே உள்ள ஒரு version-ன் உள்ளடக்கத்தை மாற்ற யாரையும் அனுமதிப்பதில்லை. ஒரு பராமரிப்பாளரை phish செய்யும் அல்லது publish token-ஐ திருடும் ஒரு தாக்குதலால் 4.18.2-ஐ மாற்றியமைக்க முடியாது. அவர்கள் 4.18.3-ஐ வெளியிடுகிறார்கள்.

உங்கள் package.json-ஐ கவனியுங்கள். "express": "^4.18.2" போன்ற ஒரு வரி, version 4.18.2-ஐ மட்டும் குறிக்காது. இதில் உள்ள caret குறியீடு "இந்த version அல்லது அதற்கு மேல் உள்ள எந்தவொரு 4.x version-ம்" என்று பொருள்படும், மேலும் ~4.18.2 என்பது "எந்தவொரு 4.18.x version-ம்" என்று பொருள்படும். npm install அந்த range-ஐ இயங்கும் நேரத்தில் தீர்மானிக்கிறது, எனவே ஒரே git commit-ஐ ஒரே நாளில் இருமுறை deploy செய்யும்போது, அது இரண்டு வெவ்வேறு code தொகுப்புகளை நிறுவக்கூடும். அந்த இடைவெளிதான் தாக்குதல் பரப்பு (attack surface). அது திறப்பதற்கு உங்கள் கணினியில் எதுவும் சமரசம் செய்யப்பட வேண்டிய அவசியமில்லை.

தீங்கிழைக்கும் releases பொதுவாகப் புகாரளிக்கப்பட்டு நீக்கப்படுகின்றன, ஆனால் அந்த நீக்கம் மக்கள் அவற்றை நிறுவிய பின்னரே நடக்கிறது. அந்த இடைவெளியில் யார் deploy செய்தார்களோ, அவர்களின் வட்டில் அந்த code இருக்கும். ஒவ்வொரு முறையும் range-களைத் தீர்மானிக்கும் ஒரு pipeline, தானாகவே அந்த இடைவெளிக்குள் நுழைகிறது; யாரும் தீர்மானிக்காமலேயே வாரத்திற்குப் பலமுறை இது நிகழ்கிறது.

வடிவம் 2: ஒரு install script, deploy செய்யும் பயனரின் உரிமைகளுடன் இயங்குதல்

ஒரு தொகுப்பின் package.json அதன் scripts தொகுதியில் preinstall, install, postinstall மற்றும் prepare ஆகியவற்றை அறிவிக்க முடியும். npm இவற்றை நிறுவலின் போது இயக்குகிறது. இவை sandboxed செய்யப்படுவதில்லை மற்றும் யாரும் இவற்றை ஆய்வு செய்வதில்லை. இவை install கட்டளையைத் தட்டச்சு செய்த பயனரின் உரிமைகளுடன், அந்த பயனரின் home directory-ல், அந்த பயனரின் network அணுகல் மற்றும் முழுமையான shell சூழலுடன் இயங்கும் shell கட்டளைகளாகும்.

எனவே, அந்தத் தொகுப்பால் என்ன செய்ய முடியும் என்பது முக்கியமல்ல; அந்தப் பயனரால் எதைப் படிக்க முடியும் என்பதே முக்கியமான கேள்வி. ஒரு சாதாரண deploy box-ல், இதற்கான பதிலில் registry token-ஐக் கொண்ட ~/.npmrc, SSH (secure shell)-க்கான deploy key-ஆகப் பயன்படுத்தப்படும் ~/.ssh/id_ed25519, ~/.aws/credentials, ~/.docker/config.json மற்றும் shell-ல் export செய்யப்பட்ட அனைத்து variable-களும் அடங்கும்; பொதுவாக DATABASE_URL இங்கேயேதான் இருக்கும்.

இது போன்ற ஒரு payload-க்கு persistence அல்லது privilege escalation தேவையில்லை. இது சில கோப்புகளைப் படித்து, அவற்றை HTTPS வழியாக ஒரு host-க்கு அனுப்பிவிட்டு, status 0-உடன் வெளியேறிவிடும். npm இயல்பாகவே install-script வெளியீட்டை மறைத்துவிடுவதால், உங்களுக்கு எதுவும் தெரியாது. அதை முடக்கிவிட்டு, உண்மையில் என்ன இயங்குகிறது என்பதைப் பாருங்கள்:

npm ci --foreground-scripts

foreground-scripts, npm process-உடன் standard input, output மற்றும் error-ஐப் பகிர்ந்து கொள்கிறது. எனவே, build scripts உங்கள் terminal-லேயே தகவல்களை அச்சிடும்; நிறுவல் வெற்றி பெற்றதும் npm அந்தத் தகவல்களை நீக்கிவிடாது.

வடிவம் 3: typosquats மற்றும் நீங்கள் தவறாகத் தட்டச்சு செய்த பெயர்

ஒரு typosquat என்பது பிரபலமான ஒரு தொகுப்பின் பெயருக்கு நெருக்கமான பெயரில் வெளியிடப்படும் தொகுப்பாகும். இது தவறான தட்டச்சு அல்லது தவறாக நகலெடுக்கப்பட்ட install கட்டளைக்காகக் காத்திருக்கும். இதன் செயல்முறை கட்டளையைச் சார்ந்தது, குறியீட்டை (code) அல்ல. எனவே, lockfile இங்கே உதவாது: நீங்கள் ஒருமுறை தவறான பெயரைச் சேர்த்தால், அதன் பிறகு lockfile அதைத் துல்லியமாகப் பதிந்துவிடும்.

தனிநபர்களை விட குழுக்களைப் பாதிக்கும் இதன் மாறுபட்ட வடிவம் dependency confusion ஆகும். உங்கள் உள்நாட்டுத் தொகுப்பு billing-utils என்று அழைக்கப்படுகிறது மற்றும் அது ஒரு private registry-ல் உள்ளது. பொதுவான registry-ல் billing-utils என்ற பெயரில் எதுவும் இல்லை என்றால், எவரும் அதை வெளியிட முடியும். npm, unscoped பெயர்களை இயல்புநிலை public registry-ல் தேடும், எனவே பொதுவான பதிப்பு வெற்றி பெறக்கூடும். இதற்கான தீர்வு, நீங்கள் வைத்திருக்கும் ஒரு scope மற்றும் அந்த scope-க்கான registry mapping ஆகும். இதை .npmrc-ல் அமைக்கலாம்:

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

இப்போது @yourorg/billing-utils அந்த host-லிருந்து மட்டுமே பெறப்படும், ஏனெனில் இயல்புநிலை registry-க்கு முன்பாகவே scope-to-registry mapping சரிபார்க்கப்படுகிறது. unscoped உள்நாட்டுப் பெயருக்கு mapping இல்லாததால், அதற்குப் பாதுகாப்பு இல்லை.

புதிய dependency எதையும் சேர்ப்பதற்கு முன், அதன் download badge-ஐ மட்டும் பார்க்காமல், அதைக் கூர்ந்து கவனியுங்கள்:

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

கடந்த மாதம் உருவாக்கப்பட்ட ஒரு தொகுப்பு, பொதுவான repository-உடன் தொடர்பில்லாத ஒரு கணக்கிலிருந்து வெளியிடப்பட்டிருந்தால், அது ஆறு வருட வரலாற்றைக் கொண்ட தொகுப்பிலிருந்து மாறுபட்ட ஆபத்தைக் கொண்டது. இதில் எந்த உண்மையும் ஆதாரமல்ல. ஆனால் இரண்டையும் சரிபார்ப்பது எளிது.

வடிவம் 4: உரிமையாளர் அமைதியாக மாறிய சார்புநிலை (dependency)

Maintainer-கள் தொகுப்புகளை (packages) மற்றவர்களிடம் ஒப்படைக்கிறார்கள். ஒருவர் பணியிலிருந்து விலகும்போது, ஒரு புதிய நபர் உதவ முன்வருகிறார், வெளியீட்டு உரிமைகள் (publish rights) மாறுகின்றன, ஆனால் அதைச் சார்ந்திருக்கும் திட்டங்களுக்கு எந்தவிதமான அறிவிப்பும் கிடைப்பதில்லை. இதில் எந்த சமரசமும் (compromise) நடக்கவில்லை. நீங்கள் 2021-ல் வைத்திருந்த நம்பிக்கை இப்போது வேறொரு நபரிடம் உள்ளது.

இதுவே மிக மெதுவான மற்றும் கண்டறிய கடினமான வடிவமாகும். இதை நேரடியாகக் கண்டறிய எந்தக் கட்டளையும் இல்லை. இரண்டு விஷயங்கள் இதைச் சுருக்க உதவுகின்றன. ஒரு தொகுப்பைத் தேர்ந்தெடுப்பதற்கு முன், மேலே உள்ள npm view வரியைப் பயன்படுத்தி யார் அதை வெளியிட முடியும் என்பதைச் சரிபார்க்கவும். நீங்கள் சார்ந்திருக்கும் ஒரு தொகுப்பு மாறும்போது, அதன் 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

முதல் வடிவம் மாற்றப்பட்ட கோப்புப் பெயர்களை மட்டுமே அச்சிடும். நீங்கள் முக்கியமாகக் கருதும் தொகுப்புகளை மேம்படுத்தும் (upgrade) ஒவ்வொரு முறையும் இதைச் செய்வது எளிது. ஒரு build script-ஐ மாற்றும், தொகுப்பின் root-ல் ஒரு கோப்பைச் சேர்க்கும் அல்லது scripts தொகுப்பைத் திருத்தும் ஒரு patch release-ஐ, அது உங்கள் server-க்கு வரும் முன்பே முழுமையாகப் படித்துப் பார்ப்பது நல்லது.

npm ci மூலம் கமிட் செய்யப்பட்ட lockfile-லிருந்து உருவாக்குதல்

package-lock.json ஆனது, tree-ல் உள்ள ஒவ்வொரு package-ன் துல்லியமான பதிப்பு, அது வந்த URL, ஒவ்வொரு tarball-ன் sha512 integrity hash மற்றும் அதை எந்த package கோரியது ஆகிய விவரங்களைப் பதிவு செய்கிறது. இதை கமிட் செய்யவும். நீங்கள் உண்மையில் எதைச் சோதித்தீர்கள் என்பதை உறுதிப்படுத்தும் ஒரே கோப்பு இதுதான்.

பிறகு, டெவலப்பரின் லேப்டாப் அல்லாத எந்தவொரு கணினியிலும் npm install-க்கு பதிலாக, எப்போதும் npm ci-ஐப் பயன்படுத்தி install செய்யவும்:

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

npm ci மற்றும் npm install ஆகியவற்றுக்கு இடையே உள்ள வேறுபாடுகள் இங்கே முக்கியமானவை. npm ci-க்கு ஒரு lockfile கட்டாயம் இருக்க வேண்டும். இது தொடங்குவதற்கு முன்பே ஏற்கனவே உள்ள node_modules-ஐ நீக்கிவிடும், எனவே முந்தைய deploy-ன் எஞ்சிய கோப்புகள் இதில் இருக்காது. இது package.json-லிலோ அல்லது lockfile-லிலோ எதையும் எழுதாது, எனவே ஒரு install தானாகவே உங்களை அடுத்த பதிப்பிற்கு நகர்த்தாது. lockfile மற்றும் package.json ஆகியவற்றுக்கு இடையே முரண்பாடு இருந்தால், அது வேறுபாடுகளைச் சரிசெய்யாமல் பிழையுடன் வெளியேறிவிடும்.

அந்தப் பிழை ஒரு தொந்தரவு அல்ல, அது ஒரு சிறப்பம்சம். ஒரு dependency மாற்றம் என்பது யாரோ ஒருவரால் சரிபார்க்கப்பட்ட கமிட் மூலம் மட்டுமே வர வேண்டும், மாறாக 02:00 மணிக்கு நடக்கும் ஒரு deploy-ன் பக்கவிளைவாக வரக்கூடாது என்பதே இதன் பொருள்.

ஒவ்வொரு fetch-ன் போதும் integrity hash சரிபார்க்கப்படும். ஒரு tarball-ன் bytes பதிவு செய்யப்பட்ட hash-உடன் பொருந்தவில்லை என்றால், அது unpack செய்யப்படாமல் code EINTEGRITY பிழையுடன் install தோல்வியடையும். இது உங்களுக்கு என்ன பலனைத் தருகிறது என்பதைத் துல்லியமாகப் புரிந்துகொள்ளுங்கள்: lockfile-ல் குறிப்பிடப்பட்ட அதே கோப்புதான் உங்களுக்குக் கிடைத்துள்ளது என்பதை இது உறுதிப்படுத்துகிறது. இது checksums மூலம் பதிவிறக்கங்களைச் சரிபார்த்தல் உங்களுக்கு வழங்கும் அதே உத்தரவாதமாகும், அதே வரம்புகளையும் கொண்டது. அந்தப் பதிப்பு வெளியிடப்பட்டபோது அது தீங்கிழைக்கும் வகையில் இருந்ததா இல்லையா என்பதைப் பற்றி இது எதையும் கூறாது.

--omit=dev பற்றிய ஒரு விவரம்: அந்தப் packages இன்னும் resolve செய்யப்பட்டு lockfile-ல் எழுதப்படுகின்றன. அவை வட்டில் (disk) மட்டுமே வைக்கப்படுவதில்லை. வட்டில் குறைவான packages இருப்பது என்பது, குறைவான install scripts மற்றும் runtime-ல் குறைவான code மட்டுமே ஏற்றப்படும் என்பதால், இதைச் செய்வது பயனுள்ளது. இது உங்கள் tree-லிருந்து ஒரு dependency-ஐ நீக்காது.

Install scripts-ஐ code-ஆகக் கருதி, அவற்றை நிராகரிக்கப் பழகவும்

Install scripts-ஐ நீங்கள் முடக்க முடியும். இதை project-ன் .npmrc-ல் சேர்த்து, lockfile-உடன் சேர்த்து commit செய்யவும்:

ignore-scripts=true
save-exact=true

ignore-scripts=true, dependencies-ல் குறிப்பிடப்பட்டுள்ள scripts-ஐ npm இயக்குவதைத் தடுக்கிறது. save-exact=true-ஐப் பயன்படுத்தினால், npm install some-lib ஆனது 1.4.2-ஐ ^1.4.2-க்கு பதிலாக package.json-ல் எழுதும். இதனால், எதிர்பாராத விதமாக ஒரு resolving range உங்கள் manifest-க்குள் நுழையாது.

இது சில செயல்பாடுகளைப் பாதிக்கும் என்பதால், இதைச் செயல்படுத்தும் முன் அதன் விளைவுகளைப் புரிந்துகொள்வது அவசியம். Native addon-ஐ compile செய்யும் அல்லது prebuilt binary-ஐ download செய்யும் packages, அந்த வேலைகளை install script-ல் தான் செய்கின்றன. Scripts முடக்கப்பட்டிருக்கும்போது, install செயல்முறை வெற்றிகரமாக முடிந்துவிடும், ஆனால் runtime-ல் அந்த module-ன் binding file-ஐ ஏற்ற முடியாமல் பிழை ஏற்படும். இதற்குத் தீர்வாக allowlist-ஐப் பயன்படுத்தலாம்:

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

npm rebuild <package> அந்த ஒரு குறிப்பிட்ட package-க்கான build scripts-ஐ மட்டும் இயக்கும். இப்போது நீங்கள் நூற்றுக்கணக்கான அறிமுகமில்லாத நபர்களுக்கு மொத்தமாக execute அனுமதி வழங்குவதற்குப் பதிலாக, ஒவ்வொரு package-க்கும் தனித்தனியாக முடிவெடுக்கிறீர்கள்.

தற்போது எவ்வளவு பெரிய அனுமதி வழங்கப்பட்டுள்ளது என்பதை அறிய, npm-மிடம் இவ்வாறு கேட்கவும்:

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

இது installed tree-ல் postinstall script-ஐக் கொண்டுள்ள அனைத்து packages-ஐயும் பட்டியலிடும். ஒரு பொதுவான application-ல், மக்கள் எதிர்பார்ப்பதை விட இந்த பட்டியல் சிறியதாகவே இருக்கும்; இதுவே allowlist-ஐ நடைமுறைக்குச் சாத்தியமாக்குகிறது.

Build செயல்முறையையும் traffic-ஐ கையாளும் செயல்முறையையும் பிரித்தல்

Deploy user-க்கு node_modules-ல் எழுதும் அனுமதி தேவை. HTTP கோரிக்கைகளுக்குப் பதிலளிக்கும் செயல்முறைக்கு இந்த அனுமதி தேவையில்லை. இவை இரண்டும் ஒரே கணக்கைப் பயன்படுத்தினால், install செய்யும்போது இயங்கும் code, பயனர்களுக்குச் சேவை செய்யும் code-ஐ மாற்றியமைக்க முடியும்; runtime-ல் இயங்கும் code-ம் அவ்வாறே செய்ய முடியும்.

இவற்றைத் தனித்தனியாகப் பிரிக்கவும். ஒரு பயனர் மூலம் build செய்யவும், மற்றொரு பயனர் மூலம் சேவை வழங்கவும். சேவை வழங்கும் கணக்கிற்கு, சேவை வழங்கப்படும் 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 ஆக மாற்றுகிறது; /dev, /proc, /sys மற்றும் ReadWritePaths-ல் நீங்கள் குறிப்பிடும் இடங்களைத் தவிர. எனவே, application node_modules-க்குள் எழுத முயற்சித்தால், அது EROFS: read-only file system பிழையுடன் தோல்வியடையும். இதை உங்கள் logs-ல் ஒரு நிமிடத்திற்குள் சரிபார்க்கலாம். NoExecPaths, எழுதக்கூடிய upload directory-ஐக் கையாள்கிறது: சேவை அங்கு கோப்புகளை எழுத முடியும், ஆனால் kernel அவற்றை execute செய்ய அனுமதிக்காது. இந்த வசதிக்கு systemd 249 அல்லது அதற்குப் பிந்தைய பதிப்பு தேவை; Ubuntu 24.04-ல் 255 பதிப்பு உள்ளது.

இந்த unit file-ல் இரண்டு சிக்கல்கள் உள்ளன. முதலாவதாக, MemoryDenyWriteExecute=yes-ஐச் சேர்க்க வேண்டாம். இது பெரும்பாலான systemd hardening பட்டியல்களில் இடம்பெறுகிறது, ஆனால் இது Node தொடங்குவதைத் தடுக்கிறது. ஏனெனில், V8 runtime-ல் JavaScript-ஐ machine code-ஆக மாற்றுகிறது, அதற்கு writable மற்றும் executable ஆகிய இரண்டு தன்மைகளும் கொண்ட memory pages தேவை. இரண்டாவதாக, command -v node-லிருந்து ExecStart பாதையை எடுக்கவும். Node ஒரு version manager மூலம் நிறுவப்பட்டிருந்தால், அது deploy user-ன் home directory-ல் இருக்கும். ProtectHome=yes அந்த directory-ஐச் சேவையிலிருந்து மறைத்துவிடும், இதனால் unit உடனடியாக status=203/EXEC பிழையுடன் தோல்வியடையும் மற்றும் executable-ஐக் கண்டறிய முடியவில்லை என்ற log செய்தி தோன்றும்.

கோப்பை மட்டும் நம்பாமல், முடிவைச் சரிபார்க்கவும்:

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 அமைப்பையும் அதன் exposure-உடன் பட்டியலிடுகிறது. இதன் மூலம் எவை இன்னும் default நிலையில் உள்ளன என்பதை அறியலாம். nodeapp-க்கு current-ன் கீழ் எந்த உரிமையும் இல்லாததால், touch கட்டளை Permission denied பிழையுடன் தோல்வியடைய வேண்டும். அது வெற்றி பெற்றால், உங்கள் file ownership தவறாக உள்ளது என்று அர்த்தம்; systemd அமைப்புகள் அதை மறைமுகமாகச் சரிசெய்கின்றன.

EnvironmentFile குறித்த ஒரு குறிப்பு: systemd, User=nodeapp பயனர் நிலைக்கு மாறுவதற்கு முன்பு root-ஆக அதை வாசிக்கும். எனவே, அந்த file 600 mode-ல் root:root ஆக இருக்கலாம். application அந்த variables-ஐப் பெற்றுக்கொள்ளும். nodeapp பயனர் கணக்கில் shell அணுகல் உள்ள எவரும் /proc/<pid>/environ மூலம் அவற்றைப் படிக்க முடியும். எனவே, இது சேமிப்பில் உள்ள secret-ஐப் பாதுகாக்குமே தவிர, இயங்கும் செயல்முறையைப் பாதுகாக்காது.

Deploy credentials-ஐ build environment-க்கு வெளியே வைத்திருங்கள்

Install scripts-கள் தற்போதைய environment-ஐ அப்படியே பெற்றுக்கொள்ளும். இந்த ஒரு காரணமே நீங்கள் எங்கே build செய்ய வேண்டும் என்பதைத் தீர்மானிக்க வேண்டும்.

மிகவும் பாதுகாப்பான வழி, production server அல்லாத வேறொரு இடத்தில் build செய்து, முடிக்கப்பட்ட directory-ஐ மட்டும் நகலெடுப்பதாகும். அந்த 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-களைப் பதிவிறக்க மட்டுமே முடியும், எதையும் publish செய்ய முடியாது. ஒருவேளை build environment-லிருந்து அது திருடப்பட்டாலும், பொதுவான packages-களைப் பதிவிறக்கும் திறன் மட்டுமே இழக்கப்படும்.

ஒருவேளை நீங்கள் server-லேயே build செய்ய வேண்டியிருந்தால், deploy user-ஆக மிகக் குறைந்த வசதிகள் கொண்ட environment-ல் build செய்யுங்கள். runtime secrets-களை /etc/nodeapp/env-ல் வையுங்கள்; இதை deploy-ஆல் படிக்க முடியாது. இதே தர்க்கம் நீங்கள் சொந்தமாக நடத்தும் build automation-க்கும் பொருந்தும்: self-hosted GitHub Actions runner என்பது tokens-களை வைத்திருப்பதோடு, ஒவ்வொரு job-இன் போதும் வெளியிடப்பட்ட ஏதேனும் ஒரு code-ஐ இயக்குகிறது. இது ஒரு சிறிய deployment-ல் மிக முக்கியமான machine-ஆக மாறுகிறது. உங்கள் முழு environment-ஐயும் அணுகக்கூடிய, நீங்கள் எழுதாத எந்தவொரு நிரலும் இதே பிரிவில் தான் வரும். இதனால்தான் AI agent-ன் environment-க்குள் secrets-களை வைக்காமல் இருப்பது என்பது, இடையில் வேறொரு நிரல் இருக்கும் இதே சிக்கலையே குறிக்கிறது.

தணிக்கை செய்ய முடியாதவற்றை Pin செய்யவும் அல்லது Vendor செய்யவும்

Pin செய்யப்பட்ட dependency என்பது, ஒரு commit இன்றி அதன் version மாறாத ஒன்றைக் குறிக்கும். ஏற்கனவே உள்ள lockfile, முழு tree-க்கும் இதைச் செய்கிறது. ஆனால், இரண்டு சூழல்களுக்கு கூடுதல் கவனம் தேவை.

முதலாவது, transitive dependencies. உங்கள் dependencies எதைச் சார்ந்திருக்கின்றன என்பதை உங்களால் கட்டுப்படுத்த முடியாது. overrides-ஐ package.json-ல் பயன்படுத்துவது, tree-ல் எந்த இடத்திலும் ஒரு குறிப்பிட்ட version-ஐ கட்டாயப்படுத்தும்:

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

இதைச் சேர்த்த பிறகு, lockfile-ல் முடிவுகளைப் பதிவு செய்ய ஒருமுறை npm install-ஐ இயக்கவும், பின் இரண்டு கோப்புகளையும் commit செய்யவும்.

இரண்டாவது, உங்களால் தணிக்கை செய்ய முடியாத மற்றும் நீக்க முடியாத ஒரு package. அதை vendor செய்யவும். npm pack, registry வழங்கும் அதே tarball-ஐத் தரவிறக்கம் செய்யும், மேலும் ஒரு file: dependency உங்கள் நகலிலிருந்து நிறுவும்:

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-ல் உள்ளது, அது உங்கள் அனுமதியின்றி மாறாது. அதன் புதுப்பிப்புகளை இனி நீங்களே கவனித்துக்கொள்ள வேண்டும் என்பதால், கைவிடப்பட்ட சிறிய package-களுக்கு மட்டும் இதைப் பயன்படுத்தவும்; உங்கள் web framework-க்கு இதைப் பயன்படுத்த வேண்டாம்.

கூடுதலாக, எந்தச் செலவும் இல்லாத ஒரு cooling-off காலமும் உள்ளது:

npm install --before=2026-08-01

before விருப்பத்தேர்வு, அந்தத் தேதி அல்லது அதற்கு முன் வெளியிடப்பட்ட version-களை மட்டுமே பயன்படுத்தி tree-ஐ மீண்டும் உருவாக்கும். dependencies-ஐப் புதுப்பிக்கும்போது, ஒரு வாரம் அல்லது இரண்டு வாரங்களுக்கு முந்தைய தேதியை அமைக்கவும். இது, ஒரு தவறான release வெளியிடப்பட்டு, இன்னும் கண்டறியப்படாத கால இடைவெளியைத் தவிர்க்க உதவும். இது ஒரு முரட்டுத்தனமான கருவி, ஏனெனில் இது முக்கியமான பாதுகாப்புத் திருத்தங்களையும் (security fixes) தடுத்துவிடும். ranges-ஐத் தீர்க்க இதைப் பயன்படுத்தவும், மாற்றங்களைப் படிக்கவும், பிறகு lockfile-ஐ commit செய்யவும்.

நான் எந்த version-ஐ வெளியிட்டேன் என்பதை எப்படி அறிவது?

Git-ல் உள்ள lockfile எதை நிறுவியிருக்க வேண்டும் என்று குறிப்பிடுகிறது. வட்டில் (disk) உள்ளதே உண்மையில் நிறுவப்பட்டது. இரண்டாவது மட்டுமே உண்மையான ஆதாரமாகும்.

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

npm ls ஆனது node_modules-ஐ வாசிக்கிறது, எனவே இது lockfile-ல் குறிப்பிடப்பட்டதை விட, வட்டில் தற்போது என்ன உள்ளதோ அதையே காட்டுகிறது. node -e வரியானது நிறுவப்பட்ட manifest-ஐ அதன் path மூலம் வாசிக்கிறது. இது exports field மூலம் subpath imports தடுக்கப்பட்ட packages-க்கும் வேலை செய்யும், மேலும் எந்தவிதமான tree வரைபடமும் இன்றி ஒரு version-ஐ மட்டும் அச்சிடும்.

ஒப்பீட்டின் மற்ற பாதிக்கு, git-ஐ வாசிக்கவும்:

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

Deploy layout-ல் commit-ஐச் சேர்ப்பதன் மூலம் இரண்டிற்கும் இடையிலான தொடர்பை நிரந்தரமாக்குங்கள். /srv/nodeapp/releases/<short commit sha>-க்குள் release செய்து, /srv/nodeapp/current-ஐ ஒரு symlink மூலம் அதற்குச் சுட்டிக்காட்டுங்கள். "தற்போது என்ன இயங்குகிறது" என்ற கேள்விக்கான பதில் readlink /srv/nodeapp/current என்று அமையும், மேலும் இதை deploy செய்யாத ஒருவர் கூட 03:00 மணிக்குச் சரிபார்க்க முடியும்.

இறுதியாக, registry எதை உறுதிப்படுத்துகிறது என்று பாருங்கள்:

npm audit signatures

இது உங்கள் installed tree-ல் உள்ள packages-ன் registry signatures-ஐச் சரிபார்க்கிறது, மேலும் provenance attestations கொண்ட packages-ஐயும் சரிபார்க்கிறது. Provenance என்பது வெளியிடப்பட்ட tarball-ஐ அதை உருவாக்கிய பொதுவான continuous integration (CI) build-உடன் இணைக்கிறது. எனவே, சரிபார்க்கப்பட்ட attestation என்பது, குறியீட்டை (code) ஒரு தெரியாத laptop-க்கு பதிலாக, ஒரு குறிப்பிட்ட commit-க்கு உங்களால் பின்னோக்கித் தேட முடியும் என்பதைக் குறிக்கிறது. அனைத்து packages-க்கும் இது பொருந்தாது என்பதால், attestation இல்லை என்றால் அதை "தகவல் இல்லை" என்று எடுத்துக்கொள்ள வேண்டுமே தவிர, "தவறான package" என்று கருதக்கூடாது.

தவறான release உங்கள் server-ஐ அடைந்த பிறகு செய்ய வேண்டியவை

எது இயங்கியது மற்றும் எந்த user-ஆக இயங்கியது என்பதிலிருந்து உங்கள் ஆய்வைத் தொடங்குங்கள்.

நிறுவலின் போது code இயங்கியிருந்தால், build user-ஆல் படிக்கக்கூடிய அனைத்தும் சமரசத்திற்கு உள்ளானதாகக் கருதுங்கள். registry token, அந்த home directory-ல் உள்ள SSH keys, cloud credentials மற்றும் அந்த shell-ல் export செய்யப்பட்ட அனைத்து secret-களையும் மாற்றிவிடுங்கள் (rotate). ஒரு கோப்பு படிக்கப்படவில்லை என்பதை உங்களால் நிரூபிக்க முடியாது என்பதால், அவற்றை மாற்றுவதே சரியான தீர்வாகும்.

ஒரு locked-down service account-ன் கீழ் runtime-ல் code இயங்கியிருந்தால், பாதிப்புக்குள்ளான பரப்பு மிகக் குறைவு: application-ன் environment variables மற்றும் அதன் network access மூலம் எட்டக்கூடியவை மட்டுமே. VPS-ல் unprivileged users-ஆக services-ஐ இயக்குவதற்கான முழுமையான வாதமும் இதுவே. இது பாதிப்பைத் தடுக்காது. மாறாக, அந்த பாதிப்பு machine-ன் எவ்வளவு பகுதியைச் சென்றடையும் என்பதையும், restart செய்த பிறகும் அது நீடிக்குமா என்பதையும் இது தீர்மானிக்கிறது.

அதன்பிறகு, கோப்புகளைச் சுத்தம் செய்வதற்குப் பதிலாக, மீண்டும் உருவாக்குங்கள் (rebuild). node_modules-ஐ நீக்குங்கள், பாதிக்கப்பட்ட package-ஐ package.json-ல் தவறான version-க்குக் கீழே pin செய்யுங்கள், lockfile-ஐப் புதுப்பிக்க npm install-ஐ ஒருமுறை இயக்குங்கள், அதை commit செய்யுங்கள், பிறகு npm ci மூலம் deploy செய்யுங்கள். ஏற்கனவே உள்ள கோப்பு அமைப்பை (tree) அங்கேயே வைத்துச் சரிசெய்யாதீர்கள். ஒரு install script எதையெல்லாம் மாற்றியது என்பதை உங்களால் துல்லியமாகக் கணக்கிட முடியாது.

பாதிப்பு நிகழ்ந்த கால அளவையும் குறித்துக்கொள்ளுங்கள்: அந்த version-ஐப் பதிவிறக்கியிருக்கக்கூடிய முதல் deploy மற்றும் அதை நீக்கிய deploy. அந்த இடைவெளிதான் எந்த logs-ஐப் படிக்க வேண்டும் என்பதை உங்களுக்குத் தெரிவிக்கும். உங்கள் releases-க்கு commit-களின் பெயர்களை வைத்தால் மட்டுமே இந்தத் தகவலை உங்களால் பெற முடியும்.

இவை எதையும் சரிசெய்யாதவை

Lockfile ஒரு dependency-ஐ பாதுகாப்பானதாக மாற்றாது. நீங்கள் ஒரு dependency-ஐ ஏற்றுக்கொண்ட தருணத்தை, deploy-ன் பக்கவிளைவாக இல்லாமல், காலத்தால் முத்திரை குத்தப்பட்ட மற்றும் ஆய்வு செய்யப்பட்ட முடிவாக இது மாற்றுகிறது. மேலே உள்ள ஒவ்வொரு நடைமுறையும் தற்செயலான நிகழ்வை ஒரு தேர்வாக மாற்றும் அதே பணியையே செய்கிறது.

npm audit இதற்கு ஒரு தற்காப்பு அல்ல. இது உங்கள் tree-ஐ ஏற்கனவே கண்டறியப்பட்ட vulnerabilities தரவுத்தளத்துடன் ஒப்பிடுகிறது; எனவே, ஏற்கனவே வெளியிடப்பட்டு பெயரிடப்பட்ட சிக்கல்களை மட்டுமே இது கண்டறியும். ஒரு supply-chain attack அதன் பயனுள்ள காலம் முழுவதும் பெயரிடப்படாமலேயே இருக்கும். பழைய அறியப்பட்ட பிழைகளுக்கு npm audit-ஐ இயக்கவும், ஆனால் நான்கு மணி நேரத்திற்கு முன்பு வெளியான ஒரு release குறித்து இதிலிருந்து எந்தப் பலனையும் எதிர்பார்க்க வேண்டாம்.

இந்த வழிகாட்டியில் உள்ள எந்தவொரு கருவியை விடவும், உங்கள் dependency எண்ணிக்கையைக் குறைப்பது அதிக பலன் தரும்; ஆனால் இதுவே எவரும் சொல்ல விரும்பாத ஆலோசனையாகும். நீங்கள் சேர்க்காத ஒவ்வொரு package-ம், உங்கள் சார்பாக phish செய்ய முடியாத ஒரு publisher-ஐயும், உங்கள் deploy user-ஆக ஒருபோதும் இயங்காத ஒரு install script-ஐயும் குறைக்கிறது.

இவை எதுவும் npm-க்கு மட்டும் உரியவை அல்ல. இதே நான்கு வடிவங்கள் PyPI, RubyGems, container images மற்றும் உங்கள் distribution-ன் package manager ஆகியவற்றுக்கும் பொருந்தும். npm-ல் இவை அதிகமாகத் தெரிவதற்குக் காரணம், அங்கு tree-கள் மிகவும் ஆழமானவை மற்றும் install scripts இயல்பாகவே இயங்குவதுதான். உங்களைச் சுற்றியுள்ள machine-ல் எவ்வளவு பகுதியை நீங்கள் பாதுகாக்க வேண்டும் என்பது அது எங்கு இயங்குகிறது என்பதைப் பொறுத்தது; இது VPS hosting பாதுகாப்பானதா என்ற பரந்த கேள்வியின் ஒரு பகுதியாகும்.

FAQ

npm ci ஒரு compromised npm package-லிருந்து என்னைப் பாதுகாக்குமா?

உங்களுக்குத் தெரியாமல் version மாறுவதிலிருந்து இது உங்களைப் பாதுகாக்கும். package-lock.json எதைப் பதிவு செய்துள்ளதோ, அதை மட்டுமே npm ci நிறுவும். ஒவ்வொரு tarball-ஐயும் அதன் sha512 integrity hash உடன் சரிபார்க்கும். package.json மற்றும் lockfile-ல் உள்ள தகவல்கள் முரண்பட்டால், அவற்றைச் சரிசெய்ய முயற்சிப்பதற்குப் பதிலாக, இது பிழையைக் காட்டி நின்றுவிடும். ஆனால், அந்த pinned version பாதுகாப்பானதுதானா என்பதை இது உறுதிப்படுத்தாது. ஒருவேளை நீங்கள் தீங்கிழைக்கும் (malicious) version-ஐக் கொண்ட lockfile-ஐ commit செய்தால், npm ci அதை உங்கள் அனைத்து server-களிலும் ஒவ்வொரு முறையும் அப்படியே நிறுவும்.

நான் எல்லாவற்றிற்கும் ignore-scripts=true-ஐ அமைக்க வேண்டுமா?

முதலில் இதை அமைத்துவிட்டு, பிறகு allowlist-ஐப் பயன்படுத்தவும். ஒரு project-ன் .npmrc-ல் ignore-scripts=true-ஐ அமைப்பது, dependency install scripts இயங்குவதைத் தடுக்கும். இது ஒரு மோசமான package-லிருந்து உங்கள் deploy user-ன் credentials-ஐத் திருடும் நேரடி வழியை அடைக்கும். Native addon-ஐ compile செய்யும் அல்லது prebuilt binary-ஐப் பதிவிறக்கும் package-களுக்கு scripts தேவைப்படும். scripts முடக்கப்பட்டிருக்கும்போது, அவை install நேரத்தில் இயங்காமல், runtime-ல் missing binding file பிழையைக் காட்டும். npm ci --ignore-scripts-ஐ இயக்கி, நீங்கள் நம்பும் சில package-களுக்கு மட்டும் npm rebuild <package>-ஐப் பயன்படுத்தவும். உண்மையில் எத்தனை package-கள் உள்ளன என்பதை npm query ":attr(scripts, [postinstall])" காட்டும்.

எனது server-ல் எந்த version-ஆன package நிறுவப்பட்டுள்ளது என்பதை எப்படிக் கண்டறிவது?

Lockfile-ஐப் பார்க்காமல், disk-ஐப் பார்க்கவும். node_modules-ல் என்ன உள்ளது என்பதை npm ls <package> தெரிவிக்கும், node -e "console.log(require('./node_modules/<package>/package.json').version)" அந்த version string-ஐ மட்டும் அச்சிடும். Git-ல் உள்ள lockfile, எதை நிறுவியிருக்க வேண்டும் என்ற கேள்விக்கு விடையளிக்கும்; இரண்டையும் ஒப்பிட்டுப் பார்ப்பதே இதன் நோக்கம். Git commit-ன் பெயரிலேயே ஒரு directory-ஐ உருவாக்கி அதில் deploy செய்வதன் மூலம், பல மாதங்களுக்குப் பிறகும் இந்த இரண்டு தகவல்களையும் உங்களால் சரிபார்க்க முடியும்.

npm audit supply-chain தாக்குதல்களைக் கண்டறியுமா?

இல்லை. npm audit உங்கள் dependency tree-ஐ ஏற்கனவே அறிவிக்கப்பட்ட vulnerabilities தரவுத்தளத்துடன் ஒப்பிடும். எனவே, ஏற்கனவே வெளியிடப்பட்டு அடையாள எண் வழங்கப்பட்ட சிக்கல்களை மட்டுமே இது கண்டறியும். ஒரு தீங்கிழைக்கும் release, அது நிறுவப்படும் முக்கியமான நேரங்களில் அறிவிக்கப்படாமல் இருக்கலாம். npm audit signatures அதிக பயனுள்ளது: இது உங்கள் installed tree-ல் உள்ள registry signatures-ஐச் சரிபார்க்கும் மற்றும் publisher வழங்கிய provenance attestations-ஐ உறுதிப்படுத்தும். இதன் மூலம், ஒரு tarball தெரியாத கணினியிலிருந்து வராமல், பொதுவான build முறையிலிருந்து வந்ததா என்பதை அறியலாம்.

Install நேரத்திலேயே தாக்குதல் நடக்கும்போது, unprivileged user-ஆக app-ஐ இயக்குவது ஏன் முக்கியம்?

ஏனெனில், இந்த இரண்டு தோல்விகளும் வெவ்வேறு பாதிப்புகளை ஏற்படுத்தும், நீங்கள் இரண்டிலிருந்தும் உங்களைப் பாதுகாத்துக் கொள்ள வேண்டும். Install-time code, deploy user-ஆக இயங்கி அந்த user-ன் SSH keys, registry tokens மற்றும் cloud credentials-ஐ வாசிக்க முடியும். Runtime code, service account-ஆக இயங்கும். User=nodeapp, ProtectSystem=strict மற்றும் disk-ல் credentials இல்லாத நிலையில், அதன் பாதிப்பு application-ன் environment மற்றும் database-உடன் நின்றுவிடும். கணக்குகளைப் பிரிப்பதன் மூலம், traffic-ஐக் கையாளும் process-ஆல் node_modules-ஐ மாற்ற முடியாது. இதனால், runtime-ல் நடக்கும் ஒரு compromise, அடுத்த restart-ல் நீக்கப்பட்டுவிடும், அது நிரந்தரமாகாது.