تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع

جارٍ التحميل…

01 / ملخص الدرس

تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع

توكنات موقّعة بـ secret واحد hard-coded من يوم ما الموقع نزل، ولما يتسرّب، عندك اختيارين وحشين: تغيّره وكل الـ sessions تقع، أو تسيبه والمهاجم يزوّر. بنفهم الـ HMAC صح، والـ kid، و keyring فيه الحالي والقديم، ودورة الـ rotation بتلات مراحل، و HKDF علشان مفتاح لكل غرض، و rotation لباسوردات الـ database و API keys بـ dual credentials، و secret managers. والتحدي: createSigner قصاد سبعة وعشرين حالة.

02 / شرح الدرس مكتوبًا

نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.

أهلًا بيك. الدرس اللي فات كان الـ secret بتاع الـ webhook فيه اتنين: جديد وقديم. النهارده هنفهم ليه. كل secret هيتسرّب في يوم من الأيام، أو على الأقل لازم تتصرف كأنه هيتسرّب: حد عمل commit لـ env file، أو الـ secret طلع في log، أو حد من الفريق ساب الشركة، أو laptop اتسرق. والسؤال مش يا ترى هيحصل ولا لأ، السؤال لما يحصل، تقدر تغيّره في ساعة من غير ما حاجة تقع؟ sign dot js: token موقّع بـ HMAC، زي unsubscribe links أو sessions. الـ payload بـ base64url، و dot، والتوقيع. والـ secret مكتوب في الـ code. نشغّل problem dot js.

problem.js
// problem.js: ليه مفتاح واحد ثابت مشكلة
import { makeToken, readToken } from "./sign.js";

const token = makeToken({ user: "mahmoud", action: "unsubscribe" });
console.log("توكن:", token.slice(0, 40) + "...");
console.log("اتقرا:", readToken(token));

// السيناريو: المفتاح اتسرّب (في git history، أو log، أو laptop اتسرق).
// عايز تغيّره. بس عندك مليون توكن متوقّع بالقديم في إيدين الناس:
// - لو غيّرت SECRET، كل التوكنات القديمة تبقى invalid فجأة ← كل الـ sessions تقع، وكل unsubscribe link يموت.
// - ولو سبته، المهاجم اللي معاه المفتاح يقدر يزوّر أي توكن.
// محتاج تقبل الاتنين وقت التغيير، وتعرف أنهي توكن اتعمل بأنهي مفتاح.
console.log("\nمحتاج rotation: توقّع بالجديد، واقبل القديم، لحد ما القديم يخلص.");

المخرجات

$ node problem.js
توكن: eyJ1c2VyIjoibWFobW91ZCIsImFjdGlvbiI6InVu...
اتقرا: { user: 'mahmoud', action: 'unsubscribe' }

محتاج rotation: توقّع بالجديد، واقبل القديم، لحد ما القديم يخلص.

الـ token شغّال. بس اقرا الـ comment: لو غيّرت الـ SECRET، كل token في إيد أي حد يبقى invalid في نفس الوقت. كل المستخدمين يتعمل لهم logout، وكل link في كل email يموت. فالناس بتسيبه. وده بالضبط اللي المهاجم عايزه. وفيه مشكلتين تانيين في الـ code: المقارنة بـ triple equals بتسرّب الوقت، شفناها في درس الـ webhook. والـ secret نفسه بيوقّع كل حاجة: لو نفس الـ secret بيوقّع unsubscribe links و sessions، المهاجم ممكن ياخد token من نوع، ويستخدمه في مكان نوع تاني، لو الـ payload شكله قريب.

الحل اسمه keyring و kid. كل key ليه id، ودايمًا تاريخ زي ألفين ستة وعشرين dash واحد. الـ token نفسه فيه الـ kid، فلما يرجعلك، تعرف أنهي key تتحقق بيه على طول، من غير ما تجرّب الكل. والـ kid جُوّه الـ data الموقّعة، علشان محدش يغيّره. ودورة الـ rotation تلات مراحل: واحد: تضيف الـ key الجديد في الأول، والقديم يفضل في الـ keyring. من دلوقتي كل token جديد بيتوقّع بالجديد، والقديمة لسه شغّالة. اتنين: تستنى لحد ما أطول TTL يعدّي، يعني لو الـ session بتعيش أسبوع، تستنى أسبوع. تلاتة: تشيل القديم. ولو الـ key اتسرّب فعلًا، ما تستناش: تعمل المرحلة واحد وتلاتة مع بعض، وتقبل إن الناس تعمل login تاني. أحسن من مهاجم بيزوّر.

وحاجة تانية: key لكل غرض. بدل ما نفس الـ secret يوقّع كل حاجة، بنشتق منه key لكل purpose بـ HKDF. HKDF ده function بتاخد secret وكلمة زي tokens session، وتطلّع key جديد. نفس الـ secret وكلمة تانية، تطلّع key تاني خالص، ومحدش يقدر يرجع منه للـ secret. فالـ token الـ unsubscribe متوقّع بـ key، والـ session بـ key تاني، ولو حد بدّلهم، التوقيع يفشل. ده اسمه domain separation. وده مش للـ tokens بس: باسورد الـ database: تعمل user تاني بالباسورد الجديد، والـ app يتنقل له، وبعدين تمسح القديم. يعني برضه فيه وقت الاتنين شغّالين. API keys بتوع العملاء: تدّي العميل key جديد، والقديم يفضل شغّال أسبوعين، وتبعتله warning. ومكانهم: secret manager زي AWS Secrets Manager أو Vault، مش env files في الـ repo. والـ app يقرا الـ keyring من هناك، وبعض الـ managers بتعمل rotation أوتوماتيك.

main dot js بيعمل الـ rotation بالتلات مراحل، بـ tokens dot js اللي هتكتبه. وفي الآخر بيجرّب token unsubscribe مكان session. التحدي: اكتب createSigner في tokens dot js. test dot js فيه سبعة وعشرين حالة، وبيقرا الـ code بتاعك علشان يتأكد إنك مش بتقارن بـ triple equals، ومفيش secrets مكتوبة.

تمرين

createSigner({ keyring, purpose, now }): sign بأول مفتاح وشكل v1.kid.body.sig مع exp، و verify بيختار المفتاح بالـ kid، و HMAC بمفتاح HKDF للـ purpose، و timingSafeEqual، و exp، و null لأي مدخل غلط.

الحل. derive بـ hkdfSync، والـ info فيه الـ purpose. الـ Map بتتعمل مرة واحدة، فالـ HKDF مش بيتحسب كل request. sign بيحط الـ exp، والـ data فيها v1 والـ kid والـ body، والتوقيع عليهم كلهم. verify: شيك الشكل، وهات الـ key بالـ kid، وقارن الطول الأول علشان timingSafeEqual ما يعملش throw، وبعدين parse وشيك الـ exp. والـ v1 في الأول دي version للـ format نفسه: لو في يوم غيّرت الـ algorithm، تعمل v2، وتقبل الاتنين وقت التغيير. نفس الفكرة بتاعة الـ kid، بس للـ format. نشغّل main dot js.

tokens.js
// tokens.js: توكنات موقّعة بـ HMAC، بـ kid للـ rotation، ومفتاح مشتق لكل غرض
import { createHmac, hkdfSync, timingSafeEqual } from "node:crypto";

const b64 = (v) => Buffer.from(v).toString("base64url");

// مفتاح لكل غرض من نفس الـ secret: توكن unsubscribe ما ينفعش يتقري كـ session
const derive = (secret, purpose) => Buffer.from(hkdfSync("sha256", secret, "", `tokens:${purpose}`, 32));

export function createSigner({ keyring, purpose, now = () => Date.now() }) {
  if (!keyring.length) throw new Error("keyring فاضي");
  // نشتق مرة واحدة: kid ← key
  const keys = new Map(keyring.map(({ kid, secret }) => [kid, derive(secret, purpose)]));
  const current = keyring[0].kid;
  const mac = (key, data) => createHmac("sha256", key).update(data).digest();

  return {
    sign(payload, ttlSec) {
      const body = b64(JSON.stringify({ ...payload, exp: Math.floor(now() / 1000) + ttlSec }));
      const data = `v1.${current}.${body}`; // الـ kid جُوّه الـ data الموقّعة
      return `${data}.${mac(keys.get(current), data).toString("base64url")}`;
    },

    verify(token) {
      if (typeof token !== "string" || token.length > 4096) return null;
      const parts = token.split(".");
      if (parts.length !== 4 || parts[0] !== "v1") return null;
      const [, kid, body, sig] = parts;
      const key = keys.get(kid); // بنختار المفتاح بالـ kid، ما بنجرّبش الكل
      if (!key) return null; // kid اتشال أو مزوّر
      const expected = mac(key, `v1.${kid}.${body}`);
      const given = Buffer.from(sig, "base64url");
      if (given.length !== expected.length || !timingSafeEqual(given, expected)) return null;
      let payload;
      try {
        payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
      } catch {
        return null;
      }
      if (typeof payload?.exp !== "number" || payload.exp <= Math.floor(now() / 1000)) return null;
      return { ...payload, kid };
    },
  };
}

المخرجات

$ node main.js
١) توكن يناير                              ✔ mahmoud (kid 2026-01)
٢) توكن يناير القديم (لسه شغّال)           ✔ mahmoud (kid 2026-01)
٢) توكن فبراير الجديد                      ✔ sara (kid 2026-02)
٣) توكن يناير بعد ما شلناه                 ✘ مرفوض
٣) توكن فبراير                             ✔ sara (kid 2026-02)
توكن unsubscribe اتبعت مكان session        ✘ مرفوض

token يناير شغّال في المرحلة الأولى والتانية. في التالتة، لما شلنا يناير، اترفض، وفبراير شغّال. والـ unsubscribe اترفض كـ session، مع إن نفس الـ secret. ونشغّل test dot js.

tokens.js

المخرجات

$ node test.js
✅ الشكل v1.kid.body.sig v1.2026-01.eyJ1c2VyIjoibWFobW9
✅ verify بيرجّع الـ payload والـ kid 
✅ الـ exp اتحط صح 
✅ sign بيستخدم أول مفتاح 
✅ rotation: توكن يناير شغّال لما الـ keyring فيه فبراير ويناير 
✅ بعد ما يناير يتشال ← null 
✅ نفس الـ kid بـ secret تاني (مفتاح اتسرّب واتغيّر) ← null 
✅ kid مزوّر ← null 
✅ تغيير الـ kid لمفتاح تاني موجود ← null (الـ kid جُوّه التوقيع) 
✅ body متغيّر (role admin) ← null 
✅ توقيع بالـ secret الخام من غير HKDF ← null 
✅ توقيع ناقص حرف ← null 
✅ توقيع فاضي ← null 
✅ v2 ← null 
✅ مدخل زبالة "" ← null من غير throw 
✅ مدخل زبالة "a.b" ← null من غير throw 
✅ مدخل زبالة "v1.2026-01.x.y.z" ← null من غير throw 
✅ مدخل زبالة null ← null من غير throw 
✅ مدخل زبالة 42 ← null من غير throw 
✅ مدخل زبالة "v1.aaaaaaaaaaaaaaaa ← null من غير throw 
✅ توكن unsubscribe اتقرا كـ session ← null 
✅ توكن unsubscribe اتقرا كـ unsubscribe ← شغّال 
✅ قبل الـ exp بتانية ← شغّال 
✅ عند الـ exp ← null 
✅ keyring فاضي ← throw 
✅ مفيش === بين التوقيعات (timingSafeEqual بس) 
✅ مفيش secrets مكتوبة في tokens.js 

27/27 نجحوا

كله نجح. الـ JWT اللي شفناه قبل كده بيعمل نفس الحكاية: الـ kid في الـ header، و JWKS فيه كل الـ keys النشطة. ولو بتعمل encryption مش توقيع بس، الفكرة اسمها envelope encryption: key لكل data، متشفّر بـ key رئيسي في KMS، وتغيير الـ key الرئيسي ما يحتاجش تشفّر كل الـ data تاني. الدرس الجاي: supply chain و SBOM. الـ code بتاعك أمان، بس إنت عامل install لـ ألف package من ناس ما تعرفهمش. إزاي تعرف إيه اللي جُوّه، وإيه اللي فيه vulnerability.

main.js
// rotate.js: rotation على تلات مراحل، بالترتيب ده بالضبط
import { createSigner } from "./tokens.js";

const JAN = { kid: "2026-01", secret: "s_lab_jan_4e7b20" };
const FEB = { kid: "2026-02", secret: "s_lab_feb_9a3f1c" };
const show = (label, v) => console.log(label.padEnd(42), v ? `✔ ${v.user} (kid ${v.kid})` : "✘ مرفوض");

// المرحلة ١: يناير بس
let s = createSigner({ keyring: [JAN], purpose: "session" });
const old = s.sign({ user: "mahmoud" }, 3600);
show("١) توكن يناير", s.verify(old));

// المرحلة ٢: فبراير بقى الحالي، ويناير لسه للتحقق
s = createSigner({ keyring: [FEB, JAN], purpose: "session" });
const fresh = s.sign({ user: "sara" }, 3600);
show("٢) توكن يناير القديم (لسه شغّال)", s.verify(old));
show("٢) توكن فبراير الجديد", s.verify(fresh));

// المرحلة ٣: بعد ما الـ TTL بتاع يناير يعدّي، نشيله
s = createSigner({ keyring: [FEB], purpose: "session" });
show("٣) توكن يناير بعد ما شلناه", s.verify(old));
show("٣) توكن فبراير", s.verify(fresh));

// ومفتاح لكل غرض: نفس الـ secret، بس توكن unsubscribe ما يتقريش كـ session
const unsub = createSigner({ keyring: [FEB], purpose: "unsubscribe" }).sign({ user: "sara" }, 3600);
show("توكن unsubscribe اتبعت مكان session", s.verify(unsub));
sign.js
// sign.js: توكن موقّع بـ HMAC. فيه secret واحد hard-coded، ومفيش طريقة تغيّره
import { createHmac } from "node:crypto";

const SECRET = "supersecret123"; // نفس المفتاح من يوم ما الموقع نزل

const b64 = (s) => Buffer.from(s).toString("base64url");
const mac = (data) => createHmac("sha256", SECRET).update(data).digest("base64url");

// توكن = payload.signature (unsubscribe link، أو session، أو أي حاجة موقّعة)
export function makeToken(payload) {
  const data = b64(JSON.stringify(payload));
  return `${data}.${mac(data)}`;
}

export function readToken(token) {
  const [data, sig] = token.split(".");
  if (mac(data) !== sig) return null; // string compare: بيسرّب الوقت كمان
  return JSON.parse(Buffer.from(data, "base64url").toString());
}
test.js
import { createSigner } from "./tokens.js";
import { createHmac } from "node:crypto";
import { readFileSync } from "node:fs";

let ok = 0, all = 0;
const check = (name, cond, extra = "") => { all++; if (cond) ok++; console.log(cond ? "✅" : "❌", name, extra); };
const JAN = { kid: "2026-01", secret: "s_lab_jan_4e7b20" };
const FEB = { kid: "2026-02", secret: "s_lab_feb_9a3f1c" };
let clock = 1_800_000_000_000;
const now = () => clock;
const mk = (keyring, purpose = "session") => createSigner({ keyring, purpose, now });

const t = mk([JAN]).sign({ user: "mahmoud", role: "user" }, 600);
const parts = t.split(".");
check("الشكل v1.kid.body.sig", parts.length === 4 && parts[0] === "v1" && parts[1] === "2026-01", t.slice(0, 30));
const v = mk([JAN]).verify(t);
check("verify بيرجّع الـ payload والـ kid", v?.user === "mahmoud" && v?.role === "user" && v?.kid === "2026-01");
check("الـ exp اتحط صح", v?.exp === 1_800_000_000 + 600);
check("sign بيستخدم أول مفتاح", mk([FEB, JAN]).sign({ user: "x" }, 60).split(".")[1] === "2026-02");
check("rotation: توكن يناير شغّال لما الـ keyring فيه فبراير ويناير", mk([FEB, JAN]).verify(t)?.user === "mahmoud");
check("بعد ما يناير يتشال ← null", mk([FEB]).verify(t) === null);
check("نفس الـ kid بـ secret تاني (مفتاح اتسرّب واتغيّر) ← null", mk([{ kid: "2026-01", secret: "other" }]).verify(t) === null);
check("kid مزوّر ← null", mk([JAN]).verify(["v1", "2099-01", parts[2], parts[3]].join(".")) === null);
check("تغيير الـ kid لمفتاح تاني موجود ← null (الـ kid جُوّه التوقيع)", mk([FEB, JAN]).verify(["v1", "2026-02", parts[2], parts[3]].join(".")) === null);

const evil = Buffer.from(JSON.stringify({ user: "mahmoud", role: "admin", exp: 1_800_000_600 })).toString("base64url");
check("body متغيّر (role admin) ← null", mk([JAN]).verify(["v1", "2026-01", evil, parts[3]].join(".")) === null);
const rawMac = createHmac("sha256", JAN.secret).update(`v1.2026-01.${evil}`).digest("base64url");
check("توقيع بالـ secret الخام من غير HKDF ← null", mk([JAN]).verify(`v1.2026-01.${evil}.${rawMac}`) === null);
check("توقيع ناقص حرف ← null", mk([JAN]).verify(t.slice(0, -1)) === null);
check("توقيع فاضي ← null", mk([JAN]).verify(parts.slice(0, 3).join(".") + ".") === null);
check("v2 ← null", mk([JAN]).verify(t.replace(/^v1/, "v2")) === null);
for (const bad of ["", "a.b", "v1.2026-01.x.y.z", null, 42, "v1." + "a".repeat(5000)])
  check(`مدخل زبالة ${JSON.stringify(bad)?.slice(0, 20)} ← null من غير throw`, (() => { try { return mk([JAN]).verify(bad) === null; } catch { return false; } })());

const unsub = mk([JAN], "unsubscribe").sign({ user: "mahmoud" }, 600);
check("توكن unsubscribe اتقرا كـ session ← null", mk([JAN], "session").verify(unsub) === null);
check("توكن unsubscribe اتقرا كـ unsubscribe ← شغّال", mk([JAN], "unsubscribe").verify(unsub)?.user === "mahmoud");

clock += 599_000;
check("قبل الـ exp بتانية ← شغّال", mk([JAN]).verify(t) !== null);
clock += 1_000;
check("عند الـ exp ← null", mk([JAN]).verify(t) === null);
check("keyring
…
03 / دروس المسار (122)
  1. 01أهلًا بيك: يعني إيه backend، والكورس ده ماشي إزاي
  2. 02الملفات والفولدرات والـ terminal: تتحرك على جهازك زي المبرمجين
  3. 03Node وأول برنامج: تشغّل وتقرا رسالة الخطأ
  4. 04القيم والمتغيرات والأنواع: const و let و typeof
  5. 05القرارات والتكرار: if و === والـ truthy والـ loops
  6. 06الـ functions: parameters و return والـ arrow functions والـ scope
  7. 07الـ objects والـ arrays: destructuring و spread و map و filter و reduce
  8. 08JSON والنصوص والتواريخ: اللغة اللي الـ backend بيتكلم بيها
  9. 09الـ callbacks والـ closures وأول لمسة async: setTimeout
  10. 10الـ classes والـ errors: throw و try/catch و stack trace
  11. 11git: تحفَظ كل خطوة وترجعلها، وما تسرّبش أسرارك
  12. 12ESM و CommonJS و package exports: Node بيلاقي الـ modules إزاي
  13. 13الـ Promises و async/await: sequential و parallel والأخطاء والإلغاء
  14. 14الـ event loop والـ microtasks: Node بيعمل كل حاجة بـ thread واحد إزاي
  15. 15الـ Streams والـ Backpressure: ملفات أكبر من الـ memory
  16. 16الـ File System والـ paths: قراية وكتابة صح، ومن غير path traversal
  17. 17الـ Buffers والبيانات الـ binary: الـ bytes من جُوّه
  18. 18Crypto APIs والأسرار: random و hash و HMAC و AES-GCM
  19. 19الـ Worker Threads: شغل CPU تقيل من غير ما توقف الـ server
  20. 20الـ Errors على مستوى الـ process: operational و programmer و crash-only
  21. 21الـ Node test runner والـ debugging: tests من غير مكتبات
  22. 22HTTP من جُوّه: الـ methods والـ status codes والـ headers
  23. 23Express والـ Routing: الـ params والـ query والترتيب والـ Router
  24. 24Express 5: إيه اللي اتغيّر، وإزاي تنقل مشروع قديم
  25. 25الـ Middleware ودورة حياة الـ request جُوّه Express
  26. 26Validation للمدخلات بـ Zod: ما تصدّقش أي حاجة جاية من برة
  27. 27تصنيف الأخطاء و Problem Details: شكل واحد لكل الـ errors
  28. 28الـ CORS والـ Cookies: المتصفح بيحمي المستخدم إزاي
  29. 29رفع الملفات وحدود الحجم: multer من غير ثغرات
  30. 30الـ Realtime: Server-Sent Events و WebSockets
  31. 31OpenAPI والـ API Versioning: عقد مكتوب، وتغيير من غير ما تكسر حد
  32. 32PostgreSQL: الـ DDL وأنواع البيانات الصح لكل عمود
  33. 33الـ Constraints: PK و FK و UNIQUE و CHECK — الـ database بتحمي نفسها
  34. 34الـ Normalization والـ ERD: تقسّم البيانات صح من أول يوم
  35. 35الـ JOINs والتجميع: INNER و LEFT و GROUP BY و HAVING و window functions
  36. 36JSONB في PostgreSQL: إمتى تستخدمه، وإزاي تسأله وتعمله index
  37. 37الـ Indexes من جُوّه: B-tree و GIN و BRIN
  38. 38Partial و Covering indexes: indexes أصغر وأسرع
  39. 39الـ Pagination بالـ Keyset: ليه OFFSET بيبطّأ وبيكرر
  40. 40Full Text Search في Postgres: بحث بالكلمات، بالعربي والإنجليزي
  41. 41EXPLAIN ANALYZE والـ Planner: اقرا الـ plan وصلّح الـ query
  42. 42الـ Transactions بـ node-postgres: كله أو ولا حاجة
  43. 43الـ MVCC ومستويات الـ Isolation: transactions كتير في نفس الوقت
  44. 44الـ Locks والـ Deadlocks: مين مستني مين
  45. 45SKIP LOCKED و Job Leasing: Postgres كـ job queue
  46. 46Row Level Security والـ Multi-tenancy: كل عميل يشوف بياناته بس
  47. 47Migrations من غير downtime: expand و backfill و contract
  48. 48الـ Replication والـ Read Replicas: نسخ حية من الـ database
  49. 49الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر
  50. 50الـ Partitioning والـ VACUUM والمراقبة: Postgres على المدى الطويل
  51. 51Redis وأنواع البيانات: strings و hashes و lists و sets و sorted sets
  52. 52Cache-aside والـ TTL: Redis قدام الـ database
  53. 53الـ Cache invalidation والـ Stampede: لما الـ key الشهير يموت
  54. 54Lua والـ Atomicity في Redis: كام أمر كأنهم أمر واحد
  55. 55الـ Rate Limiting في Redis: fixed window و sliding window و token bucket
  56. 56الـ Sessions في Redis: تسجيل دخول تقدر تلغيه في أي وقت
  57. 57Redis Streams والـ Consumer Groups: log بيتوزع على workers
  58. 58Pub/Sub و BullMQ: إشارات بين الـ instances، وطوابير شغل محترمة
  59. 59Redis لما يقع: الـ persistence (RDB و AOF) والـ replication
  60. 60Redis في الإنتاج: الـ ACLs والـ Cluster والمراقبة
  61. 61تخزين الـ Passwords بـ Argon2id: صح من أول مرة
  62. 62Session Fixation والـ Rotation: id جديد مع كل صلاحية جديدة
  63. 63الـ Cookie Flags والـ CSRF: المتصفح بيبعت الـ cookie، فلازم تتأكد مين اللي طلب
  64. 64الـ XSS والـ Content Security Policy: ما تخليش النص يتحوّل لـ code
  65. 65الـ OAuth و OpenID Connect و PKCE: login بـ Google من غير ما تشوف الـ password
  66. 66التأكد من الـ JWT صح: التوقيع والـ claims والـ scopes
  67. 67الـ BOLA و BFLA: الـ token السليم مش معناه إن الطلب مسموح
  68. 68الـ RBAC و ABAC والملكية: policy واحدة بدل ifs متفرقة
  69. 69الـ MFA والـ recovery: التاني factor اللي بجد
  70. 70الـ Passkeys و WebAuthn: login من غير password وضد الـ phishing
  71. 71الـ Threat Modeling بـ STRIDE: فكّر زي المهاجم قبل ما تكتب code
  72. 72الـ SQL Injection والـ Parameterization: الـ data ما تبقاش code
  73. 73الـ SSRF والـ Egress: ما تخليش الـ server بتاعك يزور الشبكة الداخلية
  74. 74الـ Security Headers والـ TLS: كل response يقول للـ browser يحمي المستخدم إزاي
  75. 75توقيع الـ Webhooks والـ Replay: الـ webhook ده جاي فعلًا من Stripe؟
  76. 76تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقعانت هنا
  77. 77سلسلة التوريد والـ SBOM: إنت واثق في ألف package ما تعرفهمش؟
  78. 78الـ Audit والـ Privacy Logs: سجّل مين عمل إيه، من غير ما تسجّل الأسرار
  79. 79اختبار الأمان و OWASP ASVS: الأمن جُوّه الـ CI، مش audit آخر السنة
  80. 80الـ Prototype Pollution والـ Resource DoS: request واحد يخلّي الكل admin أو يوقّع الـ server
  81. 81الـ SLI والـ SLO والـ Error Budget: إمتى تصحّي الـ on-call وإمتى تسيبه نايم
  82. 82الـ Event Loop Delay والـ ELU: لما Node نفسه يتخنق
  83. 83الـ Worker Threads والـ CPU Profiling: الشغل التقيل بره الـ event loop، والوقت راح فين
  84. 84الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقع
  85. 85الـ N+1 Queries والـ DataLoader: صفحة واحدة بمِيّتين query
  86. 86الـ HTTP Caching والـ Compression: أسرع request هو اللي ما بيوصلش
  87. 87الـ Load Testing والـ Soak Testing: الـ server بيستحمل قد إيه بجد
  88. 88الـ Concurrency Limits والـ Bulkheads: dependency بطيئة ما توقعش الـ app كله
  89. 89الـ Timeouts والـ Retries والـ Circuit Breakers: إزاي تتعامل مع dependency بطيئة أو واقعة
  90. 90OpenTelemetry والـ Distributed Tracing: الـ request راح فين وضاع وقته فين
  91. 91Docker Layers والـ Build Context: ليه الـ build بياخد دقيقة وفيه الـ .env بتاعك
  92. 92Multi-stage Builds و Non-root: image صغيرة ومن غير root
  93. 93Docker Compose والـ Networks: الـ services بتلاقي بعض إزاي
  94. 94Docker Volumes: الـ data بتتحفَظ فين
  95. 95Healthcheck و Restart و Resource Limits: running مش معناها شغّال
  96. 96BuildKit والـ Build Secrets: الـ token ما يدخلش الـ image
  97. 97Reverse Proxy و TLS: اللي واقف قدام الـ app
  98. 98Multi-arch Images والـ Native Modules: اشتغلت عندي
  99. 99CI/CD و Rollback و Canary: الـ deploy يبقى ممل
  100. 100Kubernetes من الأول: Pods و Deployments و Services و Probes
  101. 101Modular Monolith و DDD: حدود واضحة من غير network
  102. 102Transactional Outbox و Inbox: الـ event ما يضيعش وما يتنفذش مرتين
  103. 103Sagas: عملية واحدة على تلات services من غير transaction واحدة
  104. 104CQRS و Event Sourcing: الـ events هي الحقيقة
  105. 105الـ Queues والـ Brokers: BullMQ و RabbitMQ و Kafka و SQS
  106. 106Realtime على أكتر من instance: Redis pub/sub و presence و resume
  107. 107الـ Object Storage والـ Media: presigned URLs و uploads مباشرة
  108. 108التكامل مع الـ Email والـ Search: retries و webhooks و indexes
  109. 109GraphQL و gRPC في الإنتاج: contracts و DataLoader و limits و deadlines
  110. 110Kafka على أكتر من zone و region: الـ replication والـ ISR والـ acks والـ MirrorMaker والـ failover
  111. 111TypeScript و runtime boundaries: الـ types بتختفي، والـ schemas لأ
  112. 112ORM ولا SQL: query builders و repositories و migrations و transactions
  113. 113الـ Serverless من جُوّه: cold starts و freeze و timeouts و connections
  114. 114الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
  115. 115Contract Testing و Property-Based Testing بـ fast-check
  116. 116الـ Payments: idempotency و webhooks و refunds و reconciliation
  117. 117الـ Timezones والـ Internationalization: UTC و DST والـ locales والـ plurals والفلوس
  118. 118الـ Privacy والـ Data Retention: consent و export و erasure و crypto shredding
  119. 119الـ ADRs والـ Feature Flags: القرارات المكتوبة والـ rollout من غير deploy
  120. 120Multi-region active-active: حل الـ conflicts بـ HLC و CRDTs
  121. 121Readiness و Liveness و Startup Probes: الـ pod جاهز ولا عايش؟
  122. 122Incident Runbooks: من الـ alert للـ postmortem