انتقل إلى المحتوى
التوثيق بالعربية

هذه ترجمة للصفحة الأصلية بالإنجليزية. عند وجود أي اختلاف، تُعتمد النسخة الإنجليزية.

الأمان

الافتراضيات السيئة أخطاء برمجية. يفصل DaloyJS بين الضوابط التي تفرضها النواة والبرمجيات الوسيطة الأمنية الرسمية، فتُحظر الأمور الخطرة افتراضيًا وتبقى الأمور الخاصة بالنشر صريحة.

تحمي ضوابط النواة كل تطبيق افتراضيًا. وتضيف البرمجيات الوسيطة الرسمية السياسة التي تعتمد على مساراتك ومستخدميك وطريقة نشرك.
طبقتان من الدفاع
ضوابط تفرضها النواةمفعّلة افتراضيًا، دون الحاجة إلى استدعاء أي برمجية وسيطة
bodyLimitBytessafeJsonParsesanitizeHeaderValueرفض اجتياز المسارrequestTimeoutMs405 + Allowتنقيح 5xx في الإنتاج
برمجيات وسيطة أمنية رسميةصريحة، لأن السياسة قرار يخص النشر
secureHeaders()cors()csrf()rateLimit()session()bearerAuth()
تُحظر الأمور الخطرة في النواة دون أي إعداد. أما الأمور الخاصة بالنشر (CSP، وأصول CORS، وأسرار الجلسات، وطرح CSRF) فتبقى برمجيات وسيطة صريحة تفعّلها بنفسك.

ما الذي تفرضه النواة

تحدث هذه الفحوص داخل App أو داخل محوّل بيئة التشغيل نفسه. تحصل عليها التطبيقات دون استدعاء أي برمجية وسيطة.

كل طلب يمر عبر سلسلة الحواجز
  1. 01الدخولالطلب الواردالطريقة، المسار، الترويسات، الجسم
  2. 02سقف الحجمحد حجم الجسمContent-Length فوق السقف يعطي 413
  3. 03التحليلتحليل JSON معزَّزيحذف __proto__ / constructor
  4. 04التوجيهالموجّه + فحص الطريقةيُحل .. إلى المسار المعياري. الطريقة الخاطئة 405
  5. 05المعالجمعالجك مكتوب الأنواعيعمل تحت requestTimeoutMs
لا يصل الطلب إلى معالجك إلا بعد اجتياز سقف الجسم، والمحلل الآمن من تلويث النماذج الأولية، وحمايات المسار والطريقة. وأي شيء يفشل في حماية ما ترفضه النواة قبل أن تعمل شيفرتك.
التهديدالسلوك المدمج
حجب الخدمة بحجم الجسمقراءة متدفقة، وسقف صارم (الافتراضي 1 MiB)، وفحص Content-Length أولًا -> 413. منذ 1.5.4 يغطي السقف كل مسار، بما في ذلك المعالجات التي تقرأ ctx.request مباشرةً؛ وقبل ذلك كان يغطي المسارات ذات مخطط الجسم فقط. يُطبَّق السقف عند قراءة الجسم، فالمسار الذي يتجاهل جسمه لا يُرفض بسببه.
تلويث النماذج الأوليةيحذف safeJsonParse المفاتيح __proto__ وconstructor وprototype عبر دالة reviver.
UTF-8 مشوّه في الأجساممنذ 1.16.0 تفك محللات أجسام JSON وurlencoded والنص الترميز بصرامة: الجسم الذي يحتوي على تسلسل UTF-8 غير صالح أو مُطوَّل يُجاب بـ 400 بدلًا من التحقق منه بعد استبدال U+FFFD (RFC 8259 §8.1). ويظل النص غير ASCII السليم وعلامة BOM في البداية قابلين للتحليل. أما المعالج الذي يقرأ ctx.request.text() بنفسه فيحتفظ بفك الترميز المتساهل الخاص بالمنصة.
تقسيم الترويسات أو الاستجابةيرفض sanitizeHeaderName / sanitizeHeaderValue محارف CRLF وNUL.
اجتياز المسارطبقتان. أولًا يُوحَّد عنوان الطلب: يحل تحليل URL وفق WHATWG المقاطع . / .. (وصيغها %2e)، ويطبّق محوّل Node الخطوة نفسها على أهداف الطلب الخام. ثم يرفض موجّه النواة أي مقطع نقطة أو مقطع فارغ // يتبقى، ولا يربط أبدًا الاجتياز المرمّز (%2e%2e، ..%2F) في المعاملات. تطابق المسارات نصوصًا دقيقة، فلا يوجد مجلد يمكن الهروب إليه.
Slow-loris والمعالجات المعلّقةيعيد requestTimeoutMs (الافتراضي 30 ثانية) الرمز 408 ويُطلق ctx.request.signal للتفكيك التعاوني. منذ 1.5.4 يغطي الخطافات وقراءة الجسم والمعالج (وقبل ذلك: المعالج فقط). ويضبط محوّل Node أيضًا مهلات المقابس.
حدود معطّلة بقيمة خاطئةمنذ 1.5.4، يجب أن تكون bodyLimitBytes وrequestTimeoutMs وmaxHeaderCount وjsonMaxKeys وjsonMaxDepth أعدادًا صحيحة منتهية غير سالبة؛ وترمي NaN (مثل Number(process.env.UNSET)) والقيم غير الصالحة الأخرى خطأً عند الإنشاء في كل البيئات.
أنواع محتوى غير مدعومةالمسارات ذات مخططات الجسم ترفض أنواع المحتوى غير المسموح بها -> 415.
الالتباس في الطريقةاستجابة 405 حقيقية مع ترويسة Allow، وليس 404 مضللًا أبدًا.
كشف المعلومات (5xx)يُنقَّح detail في problem+json لاستجابات 5xx ما لم تكن البيئة development أو test صراحةً؛ وNODE_ENV غير المضبوط يؤدي إلى التنقيح أيضًا.

البرمجيات الوسيطة الأمنية الرسمية

هذه جزء من DaloyJS وموثقة معًا، لكنها تبقى صريحة لأن CSP وCORS ومفاتيح تحديد المعدل وأسرار الجلسات وطرح CSRF قرارات تخص النشر.

ts
import {
  requestId,
  secureHeaders,
  cors,
  rateLimit,
  bearerAuth,
  timing,
} from "@daloyjs/core";

app.use(requestId());           // نشر x-request-id
app.use(secureHeaders());       // CSP وHSTS وX-Frame-Options وCOOP وCORP وno-sniff ...
app.use(cors({                  // قائمة سماح صريحة؛ لا * مع بيانات الاعتماد أبدًا
  origin: ["https://app.example.com"],
  credentials: true,
  methods: ["GET", "POST"],
}));
app.use(rateLimit({             // لكل نظير TCP افتراضيًا؛ اضبط trustedProxies خلف وكيل
  windowMs: 60_000,
  max: 120,
}));
app.use(timing());              // ترويسة Server-Timing للمراقبة

ضع rateLimit() أو loginThrottle() قبل مصادقة bearer/basic/JWK/mTLS عندما يجب أن يُحتسب فشل بيانات الاعتماد. سيظل المحدِّد يستهلك المحاولة ويعيد 429 بعد السقف حتى لو رفضت المصادقة في preBody. ولا يقرأ DaloyJS جسم الطلب المصرّح به لفعل ذلك. وينبغي لمولّد المفاتيح المخصص في هذا المسار استخدام بيانات الطلب الخام أو حالة يملؤها خطاف preBody سابق.

تربط القوالب الرسمية هذه البرمجيات لك: تفعّل قوالب Node وBun وDeno كلًا من secureHeaders() وrequestId() وrateLimit(). وتفعّل قوالب Cloudflare Worker وVercel كلًا من secureHeaders() وrequestId() مع حدود أضيق للجسم والمهلة مناسبة للحافة.

ابدأ بالبرمجيات الوسيطة أدناه ما لم يكن لديك سبب ملموس لتغييرها. هذا يُبقي الخيارات الخطرة صريحة ومتسقة.

البيئةخط الأساس الموصى به
واجهة برمجية على Node / Bun / DenorequestId() وsecureHeaders() وrateLimit()، وcors() عندما تكون الواجهة عابرة للأصول.
Cloudflare WorkersrequestId() وsecureHeaders() افتراضيًا. استخدم cors() فقط عند الحاجة، وفضّل محدِّدًا خارجيًا أو مشتركًا على الافتراضي في الذاكرة عندما تمتد الحركة عبر عزلات (isolates) كثيرة.
VercelrequestId() وsecureHeaders() افتراضيًا. أضف cors() فقط عند الحاجة، واستخدم محدِّدًا مشتركًا إن احتجت إلى عدّادات دائمة عبر المناطق.
تطبيق مصادقته بملفات تعريف الارتباطأضف session() وcsrf() فوق خط الأساس حتى تُحمى المسارات المعدِّلة من هجمات النماذج وfetch عبر المواقع.
خلف وكيل عكسي موثوقأبقِ خط الأساس، ثم اضبط rateLimit() مع keyGenerator صريح، أو اضبط trustProxyHeaders: true فقط بعد أن يحذف الوكيل ترويسات التمرير ويعيد كتابتها.

csrf() للمسارات التي تغيّر الحالة

استخدم csrf() (بالإنجليزية) لحماية نقاط النهاية المعدِّلة. هناك استراتيجيتان مدعومتان:

  • ملف تعريف الارتباط مزدوج الإرسال (الافتراضي): يضبط ملف تعريف ارتباط يحمل رمزًا في الطلبات الآمنة، ويتطلب القيمة نفسها في الترويسة x-csrf-token للطرق غير الآمنة، ويرفض عدم التطابق بـ 403 آمن من هجمات التوقيت.
  • Fetch Metadata (strategy: "fetch-metadata"): حماية دون رموز تعتمد على الترويسة الحديثة Sec-Fetch-Site. لا ذهاب وإياب لملف تعريف الارتباط، ولا ارتباط بعرض HTML. موصى بها لتطبيقات المتصفح الجديدة.
ts
import { csrf } from "@daloyjs/core";

// ملف تعريف الارتباط الكلاسيكي مزدوج الإرسال (الافتراضي).
app.use(csrf());

// حماية Fetch-Metadata دون رموز (موصى بها لتطبيقات المتصفح).
app.use(csrf({
  strategy: "fetch-metadata",
  allowedOrigins: ["https://app.example.com"],
}));

افتراضيات secureHeaders()

text
content-security-policy: default-src 'self'; frame-ancestors 'none'
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-frame-options: DENY
referrer-policy: no-referrer
permissions-policy: camera=(), microphone=(), geolocation=(), clipboard-write=()
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin

إذا احتجت إلى CSP مختلفة، أو أردت تعطيل HSTS في التطوير المحلي، أو احتجت إلى سياسة أذونات أكثر تساهلًا، فمرّر الخيارات إلى secureHeaders() صراحةً. والترويسة القديمة X-XSS-Protection: 0 اختيارية عبر xssProtection: true لعمليات النشر التي تريد تعطيل مرشحات XSS القديمة في المتصفحات صراحةً.

CSP مع nonce لكل طلب وTrusted Types

يستطيع secureHeaders() بناء CSP من خريطة توجيهات وحقن nonce جديد لكل طلب في script-src وscript-src-elem وstyle-src وstyle-src-elem، إضافة إلى إصدار require-trusted-types-for 'script' لتعزيز الحماية من DOM XSS في وقت التشغيل. ويُعرض الـ nonce في ctx.state.cspNonce حتى تتمكن المعالجات من وضعه في وسوم <script nonce="...">.

ts
import { secureHeaders } from "@daloyjs/core";

app.use(secureHeaders({
  contentSecurityPolicy: {
    directives: {
      "default-src": "'self'",
      "script-src": "'self'",
      "style-src": "'self'",
      "img-src": ["'self'", "data:"],
    },
    nonce: true,
    trustedTypes: { policies: ["default"] },
  },
}));

app.get(
  "/page",
  {
    operationId: "page",
    responses: { 200: { description: "ok" } },
  },
  // أعد HTML بنفسك حتى تكون CSP ذات الـ nonce من secureHeaders هي التي تُرسل.
  async ({ state }) => ({
    status: 200,
    body: `<!doctype html>
<script nonce="${state.cspNonce}">
  // inline bootstrap is allowed only via this fresh nonce
</script>`,
    headers: { "content-type": "text/html; charset=utf-8" },
  }),
);

لا تعرض هذه الصفحة باستخدام htmlResponse() من @daloyjs/core/docs: فتلك الدالة المساعدة ترسل Content-Security-Policy خاصة بها (مضبوطة لواجهات توثيق Swagger / Scalar، مع 'unsafe-inline') وستتجاوز CSP الصارمة ذات الـ nonce أعلاه، فلا يعود الـ nonce هو ما يتحكم في السكربتات المضمّنة. احتفظ بـ htmlResponse() لمسار توثيق الواجهة البرمجية، وأعد جسم Response خاصًا بك للصفحات المحمية بالـ nonce.

المصادقة

ts
import { bearerAuth, basicAuth, timingSafeEqual } from "@daloyjs/core";

// Bearer (رموز معتمة، أو JWT يُتحقق منه عبر `validate` الخاصة بك).
app.post(
  "/admin/purge",
  {
    operationId: "adminPurge",
    hooks: bearerAuth({
      validate: (token) => timingSafeEqual(token, process.env.ADMIN_TOKEN!),
      realm: "admin",
    }),
    responses: { 204: { description: "ok" }, 401: { description: "denied" } },
  },
  async () => ({ status: 204 as const, body: undefined }),
);

// المصادقة الأساسية (RFC 7617).
app.use(basicAuth({
  realm: "books-api",
  verify: (user, pass) =>
    timingSafeEqual(user, "admin") &&
    timingSafeEqual(pass, process.env.ADMIN_PASSWORD ?? ""),
}));

حقن SQL

لا يأتي Daloy بمشغّل قاعدة بيانات، لكن حدود HTTP التي يملكها فعلًا (مخططات Zod الصارمة، ومحلل JSON المعزَّز، وسقوف حجم الجسم) تقلّص السطح الذي يصل إلى طبقة المستودع لديك. راجع SQL injection (بالإنجليزية) للأنماط الآمنة وغير الآمنة لكل ORM (Prisma وDrizzle وKysely والمشغّلات الخام)، ووصفة قائمة سماح لـ ORDER BY الديناميكي، وقواعد grep التي يستخدمها المشرفون لاكتشاف التراجعات.

حقن الأوامر

بيئة تشغيل DaloyJS خالية من child_process بموجب بوابة CI، لذا لا يستطيع الإطار نفسه تشغيل أوامر الصدفة. راجع Command injection (بالإنجليزية) للشكل الآمن لمعالج يحتاج فعلًا إلى استدعاء برنامج خارجي (execFile + مصفوفة argv، وليس أبدًا exec(`cmd $${input}`))، وفخ BatBadBut في Windows، وقواعد grep التي تمنع دخول أخطاء جديدة وقت مراجعة طلبات الدمج.

لوحات الإدارة

تبني واجهة إدارة أو نجاح عملاء فوق DaloyJS؟ راجع Secure admin panels (بالإنجليزية) للنمط الموصى به: مسارات internal: true، وipRestriction()، وCSP صارمة مع nonce لكل طلب، ومصادقة لكل مسؤول، ومجموعات rateLimit() لخنق تسجيل الدخول، وتسجيل تدقيق منظَّم، وكلها مطابقة بندًا ببند لقائمة Aikido العامة "secure admin panel".

سلسلة التوريد

يُوزَّع DaloyJS عبر pnpm لنموذج تثبيت أكثر صرامة. ترث تطبيقات pnpm المنشأة بالقوالب ضوابط وقت التثبيت، بينما يضيف مستودع DaloyJS نفسه وحزمة GitHub Actions الاختيارية ضوابط CI/CD ضد أنماط تسميم الذاكرة المؤقتة، وتصيّد المشرفين، وإساءة استخدام رموز OIDC التي ظهرت في حوادث npm الأخيرة.

  • عزل صارم: لا تستطيع الحزم الوصول إلى الاعتماديات الشبحية.
  • مخزن معنون بالمحتوى: كل بايت مُجزَّأ ومُتحقق منه.
  • ملف قفل مجمّد في CI مع --ignore-scripts: تثبيتات قابلة لإعادة الإنتاج دون تنفيذ سكربتات دورة الحياة المتعدية.
  • verifyStoreIntegrity، قراءات تكتشف التلف.
  • strictPeerDependencies، لا عدم تطابق صامت في اعتماديات النظراء.
  • minimumReleaseAge: 1440 في pnpm-workspace.yaml، انتظر 24 ساعة قبل تثبيت الإصدارات الجديدة (يتجاهل pnpm 11 الصيغة الموجودة في .npmrc؛ راجع تعزيز التثبيت).
  • ignoreScripts: true في القوالب، وstrictDepBuilds مع قائمة سماح allowBuilds في مستودع DaloyJS: لا سكربتات تثبيت للاعتماديات إلا ما رُوجع منها. هذا يوقف ديدان سكربتات التثبيت، لا الحمولات التي تعمل عند الاستيراد.
  • GitHub Actions مثبتة بـ SHA: تثبّت مسارات عمل GitHub الاختيارية المولَّدة إجراءات الأطراف الثالثة على التزامات ثابتة، لا على وسوم قابلة للتغيير.
  • نشر محمي لحزم DaloyJS على npm: تستخدم حزم الإطار نفسه مسار عمل إصدار قائمًا على الوسوم فقط، وموافقة بيئة محمية، والنشر الموثوق عبر OIDC، و--provenance.

إذا كان تطبيقك المولَّد خارج GitHub، فانقل الأجزاء القابلة للنقل مباشرةً وترجم قواعد مسارات عمل GitHub إلى مضيف CI لديك. لا يستطيع الإطار فرض حماية الفروع أو تقييد خروج المشغّلات في تثبيت خاص على GitLab أو Bitbucket أو Azure DevOps أو في البنية المحلية.

الوكلاء الموثوقون وتحديد المعدل

لم يعد DaloyJS يثق افتراضيًا بـ X-Forwarded-For أو X-Real-IP عند اشتقاق مفتاح تحديد المعدل. يمكن للعميل تزوير هذه الترويسات ما لم يحذفها الوكيل العكسي ويعيد كتابتها. لذلك يعتمد المحدِّد الافتراضي على نظير TCP غير القابل للتزوير (منذ 1.3.7؛ مع دلو مشترك واحد فقط في بيئات التشغيل التي لا تكشف النظير) حتى توفّر keyGenerator صريحًا أو تفعّل trustProxyHeaders: true / trustedProxies خلف وكيل موثوق. وعندما تكون ترويسات الوكيل موثوقة، يكون المفتاح هو إدخال X-Forwarded-For الأيمن (الذي أضافه وكيلك)، وليس أبدًا إدخالًا أيسر أضافه مهاجم في البداية. وتصرّح السلاسل متعددة القفزات بعدد قفزاتها عبر trustedHops. وعندما يمكن الوصول إلى الأصل نفسه مباشرةً، اضبط trustedProxies حتى يُتحقق من مقبس النظير مقابل قائمة سماح CIDR قبل تصديق أي ترويسة تمرير (راجع ملاحظة autoBan، بالإنجليزية).

إذا كنت قد صرّحت بالفعل ببنية شبكتك على التطبيق عبر behindProxy، فلا داعي لتكرارها. فـ rateLimit() أو loginThrottle() الذي لا يضبط أيًا من keyGenerator وtrustProxyHeaders وtrustedHops وtrustedProxies يتبع behindProxy الخاص بالتطبيق، فيحصل كل عميل خلف وكيلك (أو على Vercel وCloudflare Workers، حيث لا يوجد مقبس نظير) على دلوه الخاص بدلًا من أن يتشارك الجميع دلوًا واحدًا. وخيارات المحدِّد الصريحة هي الغالبة دائمًا، وtrustProxyHeaders: false يلغي ذلك. اجعل behindProxy مطابقًا للواقع: فضّل { cidrs } عندما تعرف عناوين الوكيل، لأن { hops } يثق بالإدخالات المُمرَّرة الأيمن من أي شخص يستطيع الوصول إلى التطبيق مباشرةً.

ts
const app = new App({ behindProxy: { hops: 1 } }); // وكيل موثوق واحد في المقدمة
app.use(rateLimit({ windowMs: 60_000, max: 120 })); // يعتمد على عنوان IP الحقيقي للعميل

لمسارات إدخال بيانات الاعتماد، استخدم loginThrottle() (بالإنجليزية) عبر مسارات /login وOTP وإعادة تعيين كلمة المرور، وwsRateLimit() على ترقيات WebSocket المرتبطة بها. ويمكن للدالتين الاستهلاك من دلو groupId نفسه.

موارد التوثيق المستضافة ذاتيًا

لم تعد دوال التوثيق المساعدة المدمجة تفرض CSP مصممة لـ jsDelivr. يمكنك استضافة موارد Swagger UI أو Scalar بنفسك، وإضافة nonce إلى سكربت الإقلاع، وإصدار CSP من الأصل نفسه لمسار التوثيق.

ts
import {
  swaggerUiHtml,
  htmlResponse,
} from "@daloyjs/core/docs";

const nonce = crypto.randomUUID();
const html = swaggerUiHtml({
  specUrl: "/openapi.json",
  scriptNonce: nonce,
  assets: {
    swaggerUiCssUrl: "/docs-assets/swagger-ui.css",
    swaggerUiBundleUrl: "/docs-assets/swagger-ui.js",
  },
});

return htmlResponse(html, {
  assetOrigins: [],
  scriptNonce: nonce,
  allowInlineStyles: false,
});

تعزيز التثبيت

في pnpm 11 وما بعده، توجد إعدادات وقت التثبيت في pnpm-workspace.yaml بمفاتيح camelCase. يتجاهل pnpm 11 صيغة .npmrc لهذه الإعدادات، لذا فإن الإعداد المعتمد على .npmrc وحده يفقد بصمت فترة الانتظار وسياسة السكربتات. احتفظ بنسخة مطابقة في .npmrc فقط لـ npm والأدوات الأقدم.

pnpm-workspace.yamlyaml
minimumReleaseAge: 1440    # skip versions published in the last 24 hours
blockExoticSubdeps: true   # transitive deps must come from the registry
ignoreScripts: true        # no dependency lifecycle scripts
strictPeerDependencies: true
verifyStoreIntegrity: true
preferFrozenLockfile: true

تحمي هذه الإعدادات التثبيتات التي تستخدمها. يوقف ignoreScripts ديدان سكربتات التثبيت (من نوع Shai-Hulud)؛ أما الحمولة التي تعمل عندما تستورد شيفرتك الحزمة فلا يخففها إلا فترة الانتظار وملف القفل الملتزَم به. ويكتب create-daloy هذا الملف لقوالب pnpm.

لدليل CI/CD والمشرفين الكامل، اقرأ Supply-chain security (بالإنجليزية). شغّل pnpm audit --prod في CI وقبل الإصدار.

مطابقة OWASP API Security Top 10

لشرح بندًا ببند لكيفية معالجة Daloy لكل إدخال في OWASP API Security Top 10 (2023) إضافة إلى أفضل الممارسات الشاملة (التشفير، والتحقق، وتحديد المعدل، والتسجيل، والجرد، وأمان واجهات الأطراف الثالثة)، اقرأ OWASP API Top 10 mapping (بالإنجليزية).

الإبلاغ عن ثغرة

استخدم الإبلاغ الخاص عن الثغرات في GitHub على github.com/daloyjs/daloy/security/advisories/new مع خطوات إعادة الإنتاج. لا تفتح مشكلة عامة تحتوي على تفاصيل الاستغلال.

آخر تحديث: