Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 1.1 Sarlavhani kim bajaradi
- 1.2 API'ni ham brauzer ochadi
- 2. helmet() sukuti: nima bor
- 2.1 27-qism oxiridagi sarlavhalar
- 2.2 Har biri nima uchun
- 2.3 Sukut sahifa uchun yozilgan
- 3. CSP: API uchun default-src 'none'
- 3.1 Qaysi direktiva qayerga "qaytadi"
- 3.2 Bu CSP nimani himoya qiladi
- 3.3 Kanon topilmasi: CSP o'z sahifasini ham to'sdi
- 4. Clickjacking: frame-ancestors va X-Frame-Options
- 4.1 Hujum
- 4.2 Ikki sarlavha
- 5. HSTS: faqat HTTPS orqasida
- 5.1 Nima qiladi
- 5.2 Nega kanon uni sukutda o'chirdi
- 6. Qolgan uchtasi: CORP, Referrer va Permissions-Policy
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): to'g'ri yoki noto'g'ri
- 2-mashq (o'rta): sarlavha tekshiruvchisi
- 3-mashq (qiyin): CSP yig'uvchi
- 10. Loyiha qadami: API uchun qat'iy sarlavhalar
- 10.1 src/ilova.ts
- 10.2 Test
- 10.3 Jonli tekshiruv
- 11. Real ishda
- Xulosa
- Manbalar
Helmet va xavfsizlik sarlavhalari: API uchun qat'iy CSP, HSTS, clickjacking va Permissions-Policy
Qisqacha: Xavfsizlik sarlavhalari (security headers) — server brauzerga beradigan ko'rsatmalar: "meni iframe'ga qo'yma", "Content-Type'ni taxmin qilma", "faqat HTTPS orqali kel". Ularni server emas, brauzer bajaradi.
helmet()sukuti brauzer sahifasi uchun mo'ljallangan; JSON qaytaradigan API uchun undan ham qattiqroq siyosat kerak:default-src 'none',frame-ancestors 'none',X-Frame-Options: DENY. Bu darsdavazifalar-apisarlavhalarini API uchun toraytiramiz va har birini test bilan qotiramiz.
Bu darsda
helmet()sukutda qo'yadigan sarlavhalarni o'qiysiz va qaysi biri kimga kerakligini ajratasiz.- CSP direktivalari qachon
default-srcga "qaytishini", qachon qaytmasligini ko'rasiz. - Clickjacking'dan
frame-ancestorsvaX-Frame-Optionsbilan himoyalanasiz. - HSTS'ni qachon yoqish va qachon yoqmaslikni tushuntirasiz.
vazifalar-apiga API uchun qat'iy sarlavhalarni va ularning testini qo'shasiz.
Oldin bilishingiz kerak: Mashhur middleware'lar (helmet va cors birinchi marta), Brauzer xavfsizlik modeli (CSP, COOP, Permissions-Policy), XSS va DOM xavfsizligi, HTTPS, TLS va HTTP/2, OWASP Top 10:2025 xaritasi.
25-qismda vazifalar-api ga bitta qator qo'shgan edik: app.use(helmet()). Sarlavhalar paydo bo'ldi va biz shu bilan qoldik. 13-qismda esa CSP'ni sahifa nuqtai nazaridan o'rgandik. Bugun savol boshqacha: JSON qaytaradigan API uchun helmet sukuti to'g'rimi? Javob: yo'q, u API uchun keragidan yumshoq.
1. Nega bu kerak?
1.1 Sarlavhani kim bajaradi
Xavfsizlik sarlavhasi — eshikka yopishtirilgan yozuvga o'xshaydi: "Kirish faqat xodimlar uchun". Yozuvning o'zi hech kimni to'xtatmaydi. Uni hurmat qiladigan qorovul — brauzer. curl, Postman yoki hujumchining skripti bu sarlavhalarga umuman qaramaydi.
Demak sarlavhalar serverni emas, foydalanuvchini himoya qiladi: uning brauzerida sizning javobingiz yomon maqsadda ishlatilmasin. Bu himoya qatlamlaridan biri (Xavfsizlik tamoyillari — chuqur himoya). Uning o'rnini parametrli so'rov yoki ruxsat tekshiruvi bosa olmaydi va aksincha.
1.2 API'ni ham brauzer ochadi
"Bizda sahifa yo'q, faqat JSON" — keng tarqalgan fikr. Lekin brauzer API javobini bir necha yo'l bilan olishi mumkin:
- hujumchi foydalanuvchiga
https://api…/v1/vazifalar?q=<script>…havolasini yuboradi va brauzer javobni sahifa sifatida ochadi; - boshqa sayt API manzilini
<iframe>,<img>yoki<script>ichiga qo'yadi; - noto'g'ri
Content-Typebilan kelgan javobni brauzer "taxmin qilib" HTML deb o'qiydi.
Har holatda sarlavha brauzerga aniq javob beradi: "bu sahifa emas, hech narsa yuklama, hech qayerga qo'yma".
flowchart LR
H[Hujumchi sahifasi] -->|iframe| A[API javobi]
H -->|img yoki script| A
F[Foydalanuvchi] -->|havola orqali ochadi| A
A --> B{Brauzer sarlavhalarni o'qiydi}
B -->|frame-ancestors none| X1[iframe bloklandi]
B -->|CORP same-site| X2[begona sayt o'qiy olmaydi]
B -->|default-src none| X3[hech narsa yuklanmaydi]Diagrammaning o'ng tomoniga qarang: uchala yo'lni ham brauzer to'xtatadi, server faqat ko'rsatma beradi. OWASP Top 10:2025 da bunday sozlamalar A02 Security Misconfiguration toifasiga kiradi.
2. helmet() sukuti: nima bor
2.1 27-qism oxiridagi sarlavhalar
28-qism boshida kanon vazifalar-api (27-qism yakuni) ni hujumchi ko'zi bilan tekshirgan edi. Har javobda shu sarlavhalar bor edi (kanon tajribasi, curl -i, sarlavhalarning bir qismi). Birinchi qatorga — CSP uzunligiga va X-Frame-Options qiymatiga qarang:
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0Bu — helmet 8.3.0 sukuti. Strict-Transport-Security (HSTS) yo'q, chunki 25-qismda uni ataylab HSTS sozlamasiga bog'lagan edik — lokal http da yoqilmaydi. X-Powered-By: Express ham yo'q — uni app.disable("x-powered-by") olib tashlaydi.
2.2 Har biri nima uchun
| Sarlavha | Brauzerga ko'rsatma | API uchun |
|---|---|---|
Content-Security-Policy |
qayerdan resurs yuklash mumkin | toraytiramiz |
Cross-Origin-Opener-Policy |
begona oyna window.opener orqali tegmasin |
qoladi |
Cross-Origin-Resource-Policy |
qaysi saytlar <img>/<script> bilan ola oladi |
same-site ga |
Origin-Agent-Cluster |
sahifani alohida jarayon guruhiga ajratish | qoladi |
Referrer-Policy |
havola bosilganda manzil yuborilmasin | qoladi |
X-Content-Type-Options |
Content-Type ni taxmin qilma |
eng muhimlaridan |
X-DNS-Prefetch-Control |
DNS'ni oldindan so'rama | qoladi |
X-Download-Options |
eski IE uchun | zararsiz |
X-Frame-Options |
iframe'ga qo'yish | DENY ga |
X-Permitted-Cross-Domain-Policies |
eski Flash/PDF siyosati | zararsiz |
X-XSS-Protection: 0 |
eski XSS filtrini o'chir | qoladi |
Ko'pi oldingi darslardan tanish: nosniff va X-XSS-Protection: 0 — Mashhur middleware'lar darsida, COOP va Permissions-Policy — Brauzer xavfsizlik modeli darsida. Bugun uchtasiga chuqur kiramiz: CSP, frame-ancestors va HSTS.
2.3 Sukut sahifa uchun yozilgan
Sukutdagi CSP'ni o'qing: style-src 'self' https: 'unsafe-inline', img-src 'self' data:, font-src 'self' https: data:. Bu — oddiy veb-sahifa uchun murosa: o'z skriptlari, Google Fonts, inline stil. Helmet hujjati ham buni aytadi: sukut — boshlang'ich nuqta, uni ilovangizga moslashtirish kerak.
JSON API esa hech qanday rasm, shrift yoki stil yuklamaydi. Unga yuklash huquqini berish — keraksiz eshik. "Eng kam huquq" tamoyili sarlavhalarga ham tegishli: kerak bo'lmagan narsa — 'none'.
3. CSP: API uchun default-src 'none'
3.1 Qaysi direktiva qayerga "qaytadi"
CSP'da default-src — zaxira qiymat. script-src, style-src, img-src, connect-src kabi fetch direktivalari yozilmagan bo'lsa, brauzer default-src ni oladi. Lekin hamma direktiva shunday emas: frame-ancestors, base-uri va form-action default-src ga qaytmaydi. Ularni yozmasangiz — cheklov yo'q.
Buni kichik funksiya bilan ko'raylik. U CSP satrini bo'laklaydi va direktiva uchun amaldagi qiymatni topadi:
const SUKUT =
"default-src 'self';base-uri 'self';font-src 'self' https: data:;" +
"form-action 'self';frame-ancestors 'self';img-src 'self' data:;" +
"object-src 'none';script-src 'self';script-src-attr 'none';" +
"style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests";
const API =
"default-src 'none';base-uri 'none';form-action 'none';" +
"frame-ancestors 'none'";
const FAQAT_DEFAULT = "default-src 'none'";
function oqi(csp) {
const m = new Map();
for (const qism of csp.split(";")) {
const [nom, ...qiymat] = qism.trim().split(/\s+/);
if (nom) m.set(nom, qiymat.join(" "));
}
return m;
}
// fetch direktivalari default-src ga "qaytadi", qolganlari — yo'q
const FETCH = ["connect-src", "img-src", "script-src", "style-src"];
function amaldagi(csp, d) {
if (csp.has(d)) return csp.get(d);
if (FETCH.includes(d) && csp.has("default-src")) {
return `${csp.get("default-src")} (default-src dan)`;
}
return "CHEKLOV YO'Q";
}
for (const [nom, matn] of [["sukut", SUKUT], ["api", API],
["faqat default", FAQAT_DEFAULT]]) {
const csp = oqi(matn);
console.log(`--- ${nom}: ${csp.size} ta direktiva`);
for (const d of ["connect-src", "style-src", "frame-ancestors"]) {
console.log(d.padEnd(16), amaldagi(csp, d));
}
}Konsolda:
--- sukut: 11 ta direktiva
connect-src 'self' (default-src dan)
style-src 'self' https: 'unsafe-inline'
frame-ancestors 'self'
--- api: 4 ta direktiva
connect-src 'none' (default-src dan)
style-src 'none' (default-src dan)
frame-ancestors 'none'
--- faqat default: 1 ta direktiva
connect-src 'none' (default-src dan)
style-src 'none' (default-src dan)
frame-ancestors CHEKLOV YO'QUchinchi guruhga qarang. default-src 'none' "hamma narsani yopdi" degan tuyg'u beradi, lekin iframe himoyasi yo'q. Shuning uchun vazifalar-api siyosatida to'rtta direktiva bor: default-src 'none' + qaytmaydigan uchtasi — base-uri, form-action, frame-ancestors. Funksiyamiz soddalashtirilgan: haqiqiy brauzerda script-src-elem avval script-src ga, keyin default-src ga qaytadi, lekin g'oya shu.
3.2 Bu CSP nimani himoya qiladi
Siz o'ylashingiz mumkin: "JSON'da skript yo'q, CSP nimaga kerak?". Ikki holat:
- Xato javobi yoki qidiruv natijasi foydalanuvchi matnini qaytaradi. Agar biror sabab bilan javob
text/htmlbo'lib ketsa (kod xatosi, proxy),<script>ishga tushishi mumkin.default-src 'none'bunda ham skriptni bloklaydi. - API'ning o'z HTML sahifasi bor —
public/index.html(qisqa yo'riqnoma). U skriptsiz, shuning uchun'none'bilan ham to'g'ri ko'rinadi.
Muhim noto'g'ri tushuncha: API'ning CSP'si frontend'ni himoya qilmaydi. CSP faqat o'sha javob sahifa sifatida ochilganda ishlaydi. vazifalar frontend'ining CSP'sini frontend'ni beradigan server (Vite, nginx) qo'yadi — buni 13-qismda yozgansiz.
3.3 Kanon topilmasi: CSP o'z sahifasini ham to'sdi
Yangi siyosat bilan birinchi to'liq tekshiruvda brauzer testlaridan biri yiqildi (kanon tekshiruvi, holat 25):
--- e2e (haqiqiy brauzer, Chromium headless shell): ℹ tests 10 ℹ pass 9 ℹ fail 1 ℹ skipped 0
✖ o'z sahifasidan (bir origin) fetch POST JSON — ishlaydi (201) (708.7692ms)Test API'ning / sahifasini (o'sha public/index.html) ochib, undan fetch("/v1/vazifalar") qilardi. Endi bu sahifada connect-src yo'q, demak default-src 'none' ishlaydi — brauzer o'z origin'iga ham fetch ni taqiqladi.
Bu xato emas, kutilgan xulq: API sahifa bermaydi, demak uning sahifasidan so'rov ham kerak emas. Kanon testni ikki qismga ajratdi: endi test CSP'ning fetch ni to'sishini ham tekshiradi (TypeError kutadi), "frontend" sahifasi esa test serverida API middleware'laridan oldin beriladi (/frontend.html). Prodakshnda frontend'ni nginx beradi.
Saboq: qat'iy sarlavha qo'yganda brauzer testi shart. curl bu yiqilishni hech qachon ko'rsatmas edi.
Tekshirib ko'ring: Hamkasbingiz "frontend'imizda XSS bo'lmasligi uchun API'ga
default-src 'none'qo'ydim" dedi. Uning fikrida nima noto'g'ri?
Javob
CSP faqat uni olgan hujjatga (sahifaga) tegishli. Frontend sahifasi frontend serveridan keladi va o'z CSP'siga ega. API JSON javobidagi CSP frontend sahifasining skriptlariga ta'sir qilmaydi — u faqat API javobi brauzerda alohida sahifa bo'lib ochilgan holatni himoya qiladi. Frontend'ning XSS himoyasi — frontend sahifasining CSP'si va xavfsiz render (XSS va DOM xavfsizligi).
4. Clickjacking: frame-ancestors va X-Frame-Options
4.1 Hujum
Clickjacking — hujumchi sizning sahifangizni o'z saytida ko'rinmas <iframe> ichiga qo'yadi va ustiga "Sovg'ani olish" tugmasini chizadi. Foydalanuvchi tugmani bosaman deb, aslida sizning sahifangizdagi "Hisobni o'chirish" ni bosadi. Cookie brauzerda bor — so'rov uning nomidan ketadi.
4.2 Ikki sarlavha
| Sarlavha | Qiymat | Eslatma |
|---|---|---|
Content-Security-Policy: frame-ancestors 'none' |
hech kim iframe'ga qo'ya olmaydi | zamonaviy standart |
X-Frame-Options: DENY |
xuddi shu | eski brauzerlar uchun |
Helmet sukuti SAMEORIGIN va frame-ancestors 'self' — o'z saytingiz iframe'ga qo'ya oladi. API uchun bunga ham ehtiyoj yo'q, shuning uchun ikkalasi ham 'none'/DENY. Ikkala sarlavha bor bo'lsa, zamonaviy brauzer CSP'dagi frame-ancestors ga amal qiladi. Eski sarlavha esa faqat zaxira.
Eslatma: frame-ancestors ni <meta http-equiv> orqali qo'yib bo'lmaydi — faqat HTTP sarlavhasi (Brauzer xavfsizlik modeli). Bu yana bir sabab: himoya serverda.
5. HSTS: faqat HTTPS orqasida
5.1 Nima qiladi
Strict-Transport-Security: max-age=31536000; includeSubDomains — brauzerga: "keyingi bir yil davomida bu domenga va uning hamma subdomeniga faqat HTTPS orqali kel". Foydalanuvchi http:// ni yozsa ham, brauzer o'zi https:// ga o'tadi. Bu SSL stripping hujumini (ochiq Wi-Fi'da birinchi http so'rovni ushlab, HTTPS'ga o'tkazmaslik) yopadi.
5.2 Nega kanon uni sukutda o'chirdi
- RFC 6797 ga ko'ra brauzer
httporqali kelgan HSTS sarlavhasini e'tiborsiz qoldiradi. Lokalhttp://127.0.0.1da u foydasiz. - Prodakshnda Node odatda HTTPS'ni o'zi emas, oldidagi proxy (nginx) orqali ko'radi (Reverse proxy orqasida Node). Shuning uchun "HTTPS bormi?" degan qarorni kod emas, sozlama beradi:
HSTS=true. includeSubDomains— hamma subdomen HTTPS'da bo'lishi shart. Eskihttpda qolgan bitta subdomen (masalan, ichki panel) bir yilga ochilmay qoladi.
Preload — domenni brauzerlarning ichki ro'yxatiga yozdirish (hstspreload.org). Shunda birinchi tashrifdan oldin ham faqat HTTPS. Lekin ro'yxatdan chiqish oylab davom etadi. Kanon buni kodga qo'ymadi — bu domen egasining qarori.
6. Qolgan uchtasi: CORP, Referrer va Permissions-Policy
Cross-Origin-Resource-Policy: same-site. Sukut same-origin edi. Bizning frontend API bilan bir saytda turadi: Vite proxy yoki nginx orqali (keyingi darsda ko'ramiz). Lekin har doim ham bir origin'da emas. same-site — bir saytdagi boshqa origin <img> bilan ola oladi, begona sayt — yo'q. 25-qismda ogohlantirgan edik: rasm beradigan API uchun bu qiymat cross-origin bo'lishi kerak bo'lardi. vazifalar-api rasm bermaydi.
Referrer-Policy: no-referrer. Havola bosilganda manzil (unda kursor, qidiruv so'zi bo'lishi mumkin) boshqa saytga ketmaydi. Sukutda bor, qoladi.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(). Bo'sh qavslar — "hech kimga ruxsat yo'q". Helmet bu sarlavhani qo'ymaydi, shuning uchun kanon uni o'zining kichik middleware'i bilan qo'shdi.
7. Hujumchi nigohi
| Hujumchi nima qiladi | Qaysi sarlavha to'xtatadi | Qachon ishlamaydi | Test |
|---|---|---|---|
| API sahifasini iframe'ga qo'yadi | frame-ancestors 'none', DENY |
default-src ga ishonib qolsangiz |
aniq CSP satri, DENY (404 da ham) |
| javobga HTML kiritib, sahifa sifatida ochtiradi | nosniff, default-src 'none' |
Content-Type noto'g'ri bo'lsa |
nosniff; e2e: fetch to'siladi |
ochiq Wi-Fi'da birinchi http ni ushlaydi |
HSTS | birinchi tashrifda (preload'siz) | hsts: true da aniq qiymat |
o'z saytida <img src="api…"> |
CORP same-site |
cross-origin qo'yilgan bo'lsa |
same-site qiymati |
X-Powered-By dan freymvork versiyasini biladi |
app.disable |
— | sarlavha yo'q |
Oxirgi qatorni oshirib baholamang: freymvorkni yashirish hujumchini biroz sekinlashtiradi, xolos. Bu "noaniqlik orqali xavfsizlik" emas, shunchaki keraksiz ma'lumotni bermaslik.
8. Ko'p uchraydigan xatolar
| Xato | Nega xavfli | To'g'risi |
|---|---|---|
| "helmet qo'ydim — tayyor" | sukut sahifa uchun, API uchun yumshoq | siyosatni ilovaga moslash |
faqat default-src 'none' |
frame-ancestors qaytmaydi |
to'rtta direktiva |
HSTS'ni http da yoqish |
brauzer e'tiborsiz qoldiradi, chalg'itadi | faqat HTTPS proxy orqasida |
includeSubDomains ni o'ylamay |
http subdomen ochilmay qoladi |
subdomenlarni tekshirib |
| API CSP'si frontend'ni himoya qiladi deb o'ylash | CSP faqat o'z hujjatiga | frontend o'z CSP'si bilan |
| xato javobida sarlavha yo'q | 404 sahifasini iframe'ga qo'yish mumkin | helmet eng boshda |
sarlavhani faqat curl bilan tekshirish |
brauzer xulqi ko'rinmaydi | brauzer testi |
9. Mashqlar
Mashqlar kurs/mashqlar/28/24-helmet/ da.
1-mashq (oson): to'g'ri yoki noto'g'ri
- Xavfsizlik sarlavhalari curl bilan yuborilgan hujum so'rovini ham to'xtatadi.
default-src 'none'bo'lsa,frame-ancestorsyozilmasa ham iframe taqiqlangan.httporqali kelgan HSTS sarlavhasini brauzer e'tiborsiz qoldiradi.- API javobidagi CSP frontend sahifasidagi XSS'dan himoya qiladi.
X-XSS-Protection: 0— eski filtrni ataylab o'chirish.
Yechim
- Noto'g'ri — sarlavhalarga faqat brauzer amal qiladi.
- Noto'g'ri —
frame-ancestorsdefault-srcga qaytmaydi (yuqoridagi "faqat default" natijasi). - To'g'ri — RFC 6797 shuni talab qiladi.
- Noto'g'ri — CSP faqat uni olgan hujjatga tegishli.
- To'g'ri — o'sha filtrning o'zi zaiflik manbai bo'lgan.
2-mashq (o'rta): sarlavha tekshiruvchisi
sarlavhaTekshir(sarlavhalar) funksiyasini yozing. U sarlavhalar obyektini oladi (nomlar har xil registrda bo'lishi mumkin) va muammolarni ; bilan ulab qaytaradi, muammo bo'lmasa — "toza". Tekshiruvlar: X-Content-Type-Options aynan nosniff; CSP bor; CSP'da frame-ancestors yo'q bo'lsa X-Frame-Options: DENY bo'lsin; X-Powered-By bo'lmasin. Uchta to'plamda sinang: oddiy Express, faqat default-src 'none' + nosniff, kanon. Ishora: nomlarni Object.entries va toLowerCase bilan kichik harfga keltiring (Object metodlarining to'liq sharhi).
Yechim
function sarlavhaTekshir(s) {
const h = Object.fromEntries(
Object.entries(s).map(([k, v]) => [k.toLowerCase(), v]),
);
const xatolar = [];
if (h["x-content-type-options"] !== "nosniff") {
xatolar.push("nosniff yo'q");
}
const csp = h["content-security-policy"] ?? "";
if (csp === "") xatolar.push("CSP yo'q");
const deny = h["x-frame-options"] === "DENY";
if (!csp.includes("frame-ancestors") && !deny) {
xatolar.push("iframe himoyasi yo'q");
}
if ("x-powered-by" in h) {
xatolar.push(`freymvork oshkor: ${h["x-powered-by"]}`);
}
return xatolar.length === 0 ? "toza" : xatolar.join("; ");
}
const ODDIY_EXPRESS = {
"X-Powered-By": "Express",
"Content-Type": "application/json; charset=utf-8",
};
const FAQAT_DEFAULT = {
"Content-Security-Policy": "default-src 'none'",
"X-Content-Type-Options": "nosniff",
};
const KANON = {
"Content-Security-Policy":
"default-src 'none';base-uri 'none';form-action 'none';" +
"frame-ancestors 'none'",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
};
console.log(sarlavhaTekshir(ODDIY_EXPRESS));
console.log(sarlavhaTekshir(FAQAT_DEFAULT));
console.log(sarlavhaTekshir(KANON));Konsolda:
nosniff yo'q; CSP yo'q; iframe himoyasi yo'q; freymvork oshkor: Express
iframe himoyasi yo'q
tozaIkkinchi qator — darsdagi asosiy tuzoq: CSP bor, nosniff bor, lekin iframe himoyasi yo'q. HTTP sarlavha nomlari registrga sezgir emas, shuning uchun avval hammasini kichik harfga keltirdik.
3-mashq (qiyin): CSP yig'uvchi
cspYoz(direktivalar) funksiyasini yozing: obyektdan ({ "default-src": ["'none'"] }) CSP satrini yig'adi. Qoidalar: none, self, unsafe-inline, unsafe-eval tirnoqsiz yozilsa — xato; * — xato; script-src da 'unsafe-inline' — xato; frame-ancestors berilmasa — oxiriga frame-ancestors 'none' qo'shiladi. Xato bo'lsa { xatolar }, bo'lmasa { csp } qaytsin. Uchta to'plamda sinab, natijani satrma-satr chiqaring.
Yechim
const KALIT_SOZLAR = ["none", "self", "unsafe-inline", "unsafe-eval"];
function cspYoz(direktivalar) {
const xatolar = [];
const qismlar = [];
for (const [nom, qiymatlar] of Object.entries(direktivalar)) {
for (const q of qiymatlar) {
if (KALIT_SOZLAR.includes(q)) {
xatolar.push(`${nom}: ${q} tirnoqsiz — '${q}' bo'lsin`);
}
if (q === "*") xatolar.push(`${nom}: * — hamma joyga ruxsat`);
if (nom === "script-src" && q === "'unsafe-inline'") {
xatolar.push("script-src: 'unsafe-inline' — XSS eshigi");
}
}
qismlar.push([nom, ...qiymatlar].join(" "));
}
if (!("frame-ancestors" in direktivalar)) {
qismlar.push("frame-ancestors 'none'");
}
if (xatolar.length > 0) return { xatolar };
return { csp: qismlar.join(";") };
}
function chiqar(n) {
if (n.csp) console.log(n.csp);
else for (const x of n.xatolar) console.log("XATO:", x);
}
chiqar(cspYoz({ "default-src": ["'none'"] }));
chiqar(cspYoz({
"default-src": ["'self'"],
"img-src": ["'self'", "data:"],
"frame-ancestors": ["'self'"],
}));
chiqar(cspYoz({
"default-src": ["none"],
"script-src": ["'self'", "'unsafe-inline'"],
"connect-src": ["*"],
}));Konsolda:
default-src 'none';frame-ancestors 'none'
default-src 'self';img-src 'self' data:;frame-ancestors 'self'
XATO: default-src: none tirnoqsiz — 'none' bo'lsin
XATO: script-src: 'unsafe-inline' — XSS eshigi
XATO: connect-src: * — hamma joyga ruxsatTirnoqsiz none — CSP'dagi eng ayyor xato: brauzer uni none degan xost nomi deb tushunadi va siyosat siz o'ylagandek ishlamaydi. Funksiya xatolarni yig'ib, bittasida to'xtamaydi — dasturchi hammasini bir martada ko'radi. frame-ancestors ni avtomatik qo'shish — "xavfsiz sukut" tamoyili (Xavfsizlik tamoyillari).
10. Loyiha qadami: API uchun qat'iy sarlavhalar
10.1 src/ilova.ts
Bitta helmet() qatori o'rniga API uchun moslangan sozlama. Telefon uchun qatorlarni bo'ldik va izohlarni qisqartirdik, kod ma'nosi kanondagidek (bo'lingan nusxa tip, lint va testlardan o'tdi — biz tekshirdik):
// helmet: API — JSON, sahifa emas. Sukutdagi CSP sahifa uchun;
// API javobi brauzerda sahifa sifatida ochilsa (yoki XSS orqali
// HTML qaytsa) hech narsa yuklanmasin: default-src 'none'.
// Iframe'ga qo'yib bo'lmaydi (frame-ancestors + DENY — eski
// brauzerlar uchun), CORP same-site — boshqa sayt <img>/<script>
// bilan o'qiy olmaydi.
app.use(helmet({
contentSecurityPolicy: {
useDefaults: false,
directives: {
"default-src": ["'none'"],
"base-uri": ["'none'"],
"form-action": ["'none'"],
"frame-ancestors": ["'none'"],
},
},
xFrameOptions: { action: "deny" },
crossOriginResourcePolicy: { policy: "same-site" },
// HSTS faqat HTTPS orqasida (proxy); 1 yil + subdomenlar;
// preload — domen egasi qarori (hstspreload.org)
strictTransportSecurity: hsts
? { maxAge: 31_536_000, includeSubDomains: true }
: false,
}));
// Permissions-Policy — helmet qo'ymaydi: API sahifasi kamera,
// mikrofon, joylashuvga murojaat qila olmasin
app.use((_req, res, next) => {
res.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=(), payment=()",
);
next();
});useDefaults: false — muhim: aks holda helmet bizning to'rtta direktivamizni sukutdagi o'n bittasi bilan birlashtirardi va style-src https: kabi qiymatlar qolardi. Bu middleware'lar cors va routerlardan oldin turadi — 404 va xato javoblarida ham sarlavha bo'ladi.
10.2 Test
test/middleware.test.ts dagi helmet testi endi har qiymatni aniq tekshiradi (avval faqat "CSP bormi" edi):
test("helmet sarlavhalari (API uchun qat'iy CSP) " +
"va x-powered-by yo'q", async () => {
const r = await xomSorov(`${asos}/v1/vazifalar`);
const s = r.sarlavhalar;
assert.equal(s["x-content-type-options"], "nosniff");
assert.equal(s["content-security-policy"],
"default-src 'none';base-uri 'none';" +
"form-action 'none';frame-ancestors 'none'");
assert.equal(s["x-frame-options"], "DENY");
assert.equal(s["cross-origin-resource-policy"], "same-site");
assert.equal(s["cross-origin-opener-policy"], "same-origin");
assert.equal(s["referrer-policy"], "no-referrer");
assert.equal(s["permissions-policy"],
"camera=(), microphone=(), geolocation=(), payment=()");
// HSTS faqat hsts: true (HTTPS proxy orqasida)
assert.equal(s["strict-transport-security"], undefined);
assert.equal(s["x-powered-by"], undefined);
const xato = await xomSorov(`${asos}/yoq`); // xato javobida ham
assert.equal(xato.sarlavhalar["x-frame-options"], "DENY");
});Nega aniq qiymat? "Sarlavha bor" testi kimdir keyinroq 'none' ni 'self' ga almashtirsa ham o'tib ketaverardi. Aniq qiymat — siyosatning o'zini qotiradi. s qisqa nomini telefon uchun qo'shdik. HSTS yoqilgan holatni brauzer testlari to'plami (e2e/cors.e2e.ts, keyingi darsda) tekshiradi: hsts: true bilan sarlavha aynan max-age=31536000; includeSubDomains.
E2e testlarga yana bitta o'zgarish kerak — yuqoridagi kanon topilmasi. e2e/yordam.ts dagi test serveri "frontend" sahifasini API'dan oldin, tashqi Express'da beradi:
// /frontend.html — "frontend" sahifasi API bilan bir origin'da
// (prod'da nginx beradi). API'ning o'z sahifalari CSP 'none' —
// ulardan fetch ham taqiqlangan, shuning uchun test sahifasi API
// middleware'laridan OLDIN, tashqi express'da
const tashqi = express();
tashqi.get("/frontend.html", (_q, r) => {
r.type("html").send(
"<!doctype html><meta charset=utf-8><p>frontend</p>",
);
});
tashqi.use(ilovaYarat({
ombor: (egasi) => new PgOmbori(pool, egasi), auth, ...qoshimcha,
}));
const server = createServer(tashqi);
await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));e2e/csrf.e2e.ts dagi "o'z sahifasidan" testi esa avval / da fetch ning to'silishini (TypeError) tekshiradi, keyin /frontend.html ga o'tadi.
10.3 Jonli tekshiruv
Serverni ishga tushirib, sarlavhasiz so'rov yuboring (biz ishga tushirdik: shu kod, Node 24.21.0, lokal PostgreSQL 17.6; Date, X-Request-Id, ETag va ulanish qatorlarini olib tashladik):
curl -i "$A/v1/vazifalar?limit=1"Chiqishni «27-qism oxiridagi sarlavhalar» bo'limidagi sukut bilan solishtiring — CSP, X-Frame-Options, CORP va yangi Permissions-Policy qatorlariga qarang:
HTTP/1.1 401 Unauthorized
Content-Security-Policy: default-src 'none';base-uri 'none';form-action 'none';frame-ancestors 'none'
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-site
Origin-Agent-Cluster: ?1
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: DENY
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 0
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Location,X-Request-Id,Idempotency-Replayed,ETag,Link,Deprecation,Sunset
WWW-Authenticate: Cookie realm="vazifalar"
Content-Type: application/problem+json; charset=utf-8
Content-Length: 234Nima bo'ldi? Uchta qiymat qat'iylashdi (CSP to'rtta direktiva, DENY, same-site), bittasi qo'shildi (Permissions-Policy), qolganlari sukutdagidek. 401 javobida ham hamma sarlavha bor — helmet autentifikatsiyadan oldin ishlaydi. /yoq (404) va / (public/index.html) javoblarida xuddi shu to'plam chiqdi. Access-Control-* qatorlari — keyingi darsning mavzusi.
Bu holat keyingi dars bilan birga yakunlanadi — hali commit qilmang. CORS sozlamasi va brauzer testlari qo'shilgach, ikkalasini bitta commit qilamiz.
11. Real ishda
- Xavfsizlik skanerlari (masalan, OWASP ZAP yoki brauzerdagi "security headers" tekshiruvchilari) birinchi navbatda shu sarlavhalarni sanaydi. Hisobotda "Missing X-Frame-Options" kabi qatorlar — eng ko'p uchraydigan topilmalardan.
- Katta loyihalarda sarlavhalarni ko'pincha reverse proxy (nginx, CDN) qo'yadi — hamma xizmat uchun bir joyda. Unda ilova va proxy bir xil sarlavhani ikki marta qo'ymasligiga e'tibor bering: ikki xil CSP bo'lsa, brauzer ikkalasini qo'llaydi va eng qattig'i yutadi.
- CSP'ni sahifada birdaniga qattiq qilish xavfli —
Content-Security-Policy-Report-Onlybilan avval kuzatiladi (Brauzer xavfsizlik modeli). JSON API uchun bu bosqich kerak emas: u hech narsa yuklamaydi. - Intervyuda: "helmet nima qiladi?", "API'ga CSP kerakmi?", "HSTS va preload xavfi?", "clickjacking'dan qanday himoyalanasiz?".
Xulosa
- Xavfsizlik sarlavhalari — brauzerga ko'rsatma; curl va hujumchi skriptiga ta'sir qilmaydi.
helmet()sukuti sahifa uchun; JSON API uchun siyosat toraytiriladi:default-src 'none'+base-uri,form-action,frame-ancestors.frame-ancestors,base-uri,form-actiondefault-srcga qaytmaydi — alohida yozish shart.- Clickjacking:
frame-ancestors 'none'+ eski brauzerlar uchunX-Frame-Options: DENY. - HSTS — faqat HTTPS orqasida (
HSTS=true),includeSubDomainsva preload — ongli qaror. - Permissions-Policy'ni helmet qo'ymaydi — o'zimiz qo'shdik.
- Har qiymat test bilan qotiriladi; qat'iy CSP o'z sahifasidan
fetchni ham to'sdi — buni faqat brauzer testi ko'rsatdi.
Keyingi dars: CORS chuqur — vazifalar-api ga cookie bilan boshqa origin'dan murojaat: credentials, preflight, * tuzog'i va ruxsat ro'yxatining hiylali xatolari. O'sha darsda bu holatni commit qilamiz.
Manbalar
- Helmet hujjati: https://helmetjs.github.io/ (helmet 8.3.0; sukutdagi sarlavhalar va
useDefaults) - MDN: Content-Security-Policy va direktivalar (fetch direktivalari,
frame-ancestors,base-uri,form-action): https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy - W3C Content Security Policy Level 3: https://www.w3.org/TR/CSP3/
- RFC 6797 — HTTP Strict Transport Security (HSTS), xavfsiz bo'lmagan kanal orqali kelgan sarlavhani e'tiborsiz qoldirish qoidasi: https://www.rfc-editor.org/rfc/rfc6797
- HSTS preload ro'yxati: https://hstspreload.org/
- OWASP HTTP Headers Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html
- OWASP Clickjacking Defense Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html
- Kanon (IlmHamroh, 2026-10-27/28): hujum yuzasi tajribasi (27-qism yakuni sarlavhalari), holat 25 birinchi va ikkinchi tekshiruvi (e2e 9/10 → 10/10). Darsdagi brauzer misollari va jonli
curl(Node 24.21.0, PostgreSQL 17.6) — biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!