قوِّ واجهة برمجة Node في بيئة الإنتاج: CORS صحيح، وتحديد المعدّل، وترويسات الأمان، ودفاعات ضد أبرز مخاطر OWASP لواجهات البرمجة.
افتح هذا الدرس في Kodokonتُصدِر OWASP قائمة بأبرز 10 مخاطر مخصّصة لواجهات البرمجة. والمخاطر الثلاثة المهيمنة هي: BOLA (كسر التخويل على مستوى الكائن - الوصول إلى كائن مستخدم آخر بتغيير معرّف)، وكسر المصادقة (Broken Authentication) (رموز مُتحقَّق منها بشكل سيّئ، وأسرار ضعيفة)، والاستهلاك غير المقيَّد للموارد (Unrestricted Resource Consumption) (لا حدّ على المعدّل أو الحجم أو الوقت). لاحظ أن أيًّا من الثلاثة لا يُحَل بتثبيت مكتبة: فهي خصائص تصميمك. لنبدأ بالآليات الشاملة: CORS، وتحديد المعدّل، والترويسات.
import { createServer } from "node:http";
const allowed = new Set(["https://app.example.com"]);
const server = createServer((req, res) => {
const origin = req.headers.origin;
if (origin && allowed.has(origin)) {
res.setHeader(
"Access-Control-Allow-Origin",
origin,
);
res.setHeader("Vary", "Origin");
}
if (req.method === "OPTIONS") {
res.setHeader(
"Access-Control-Allow-Methods",
"GET,POST,PUT,DELETE",
);
res.setHeader(
"Access-Control-Allow-Headers",
"content-type,authorization",
);
res.setHeader("Access-Control-Max-Age", "86400");
res.writeHead(204).end();
return;
}
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ ok: true }));
});
server.listen(3000);افهم بوضوح ما ليس عليه CORS: فهو ليس حمايةً للخادم. إذ يتجاهل curl أو خادمٌ خلفيٌّ تابعٌ لطرف ثالث هذه الترويسات تمامًا؛ والمتصفّح وحده هو من يفرضها، لحماية مستخدميه. وثمة ثلاث قواعد للخبراء: أرجِع المصدر الدقيق من قائمة سماح (لا تُعِد أبدًا صدى ترويسة Origin بشكل أعمى)، وأضِف Vary: Origin كي لا تُفسِد الذاكرات المؤقّتة الوسيطة، واعلم أن المتصفّحات ترفض الرمز البديل * بمجرد أن يحمل الطلب ملفات تعريف ارتباط (credentials).
const WINDOW_MS = 60_000;
const LIMIT = 100;
const hits = new Map();
function check(ip) {
const now = Date.now();
const entry = hits.get(ip);
if (!entry || now - entry.start >= WINDOW_MS) {
hits.set(ip, { start: now, count: 1 });
return { ok: true, remaining: LIMIT - 1 };
}
entry.count += 1;
const remaining = Math.max(LIMIT - entry.count, 0);
return { ok: entry.count <= LIMIT, remaining };
}
console.log(check("203.0.113.7"));تُكلّف ترويسات الأمان بضعة أسطر وتحجب فئات كاملة من الهجمات: فـ HSTS تفرض HTTPS للزيارات المستقبلية، وX-Content-Type-Options: nosniff تمنع المتصفّح من تخمين نوع MIME، وسياسة CSP المُقيِّدة (default-src 'none') تُبطِل استغلال استجابة واجهة برمجة عُرِضت خطأً في متصفّح. واحذف أيضًا أيّ ترويسة تكشف حزمتك التقنية: فـ Express تضيف X-Powered-By افتراضيًا - عطّلها بـ app.disable('x-powered-by').
function setSecurityHeaders(res) {
res.setHeader(
"Strict-Transport-Security",
"max-age=63072000; includeSubDomains",
);
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader(
"Content-Security-Policy",
"default-src 'none'; frame-ancestors 'none'",
);
res.setHeader("Cache-Control", "no-store");
}
export { setSecurityHeaders };