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