إنتقل إلى المحتوى الرئيسي

عقلية الأمان في Vibe Coding مع Cursor

Vibe coding security with Cursor

البرمجة بنمط Vibe مع Cursor سريعة وممتعة: تصف ما تريد، فيكتب الوكيل الكود، وتواصل التدفق دون التوقّف لقراءة كل سطر. المشكلة أن نموذجًا مدرّبًا على تلبية المطالبات سينتج بسرور كودًا يعمل ويبدو نظيفًا بينما يحمل مع ذلك حقن SQL، أو مفاتيح API مضمّنة، أو فحص تفويض يثق بالعميل. يمنحك هذا الدليل عقلية أمنية قابلة للتكرار لسير عمل Vibe Coding في Cursor، من اكتشاف الأخطاء التي ينتجها الكود المولّد بالذكاء الاصطناعي أكثر من غيرها إلى تدقيق المخرجات، وفرض القواعد، والتعامل مع الأسرار، وتشغيل قائمة فحص قبل النشر قبل أن يُشحن أي شيء.

ما هو Vibe Coding ولماذا يهمّ الأمان​

Vibe coding يعني أن توجّه بالنيّة وتدع النموذج يملأ تفاصيل التنفيذ. تطلب «نقطة نهاية تسجيل دخول مع كوكي جلسة» فيكتب Cursor المسار واستدعاء قاعدة البيانات والوسيط. المخرجات عادةً تعمل وظيفيًا، لكن الوظيفي ليس مثل الآمن. النموذج يُحسّن لجعل الكود يعمل، لا للدفاع ضد مهاجم يقرأ نفس الكود.

يهمّ الأمان في سير العمل هذا أكثر لا أقل، لثلاثة أسباب:

  • أنت تراجع أقل. عندما يكتب النموذج عشرات الأسطر لكل مطالبة، تصبح خطوة المراجعة البشرية عنق الزجاجة وتُتجاهَل.
  • تعلّم النموذج من كود غير آمن. تحتوي المستودعات العامة على الكثير من SQL المدمج بالنصوص وتخزين الرموز من جانب العميل، فتظهر تلك الأنماط في الإكمالات.
  • نطاق التأثير كبير. يمكن لوضع Agent لمس ملفات كثيرة دفعة واحدة، فيتكرّر نمط غير آمن واحد عبر قاعدة الكود.

الأمان ليس مرحلة منفصلة تُضاف في النهاية. إنه عادة ثابتة تدمجها في طريقة مطالبتك ومراجعتك ونشرك مع Cursor.

أنت المراجع دائمًا

قد يكتب وكيل Cursor كودًا يُترجم ويجتاز اختباراتك ومع ذلك يُشحن ثغرة. لا يستطيع النموذج التوقيع على مخاطرك. عامل كل كتلة مولّدة كمسودة من مطوّر مبتدئ سريع لكن مهمل.

أكثر الثغرات شيوعًا في الكود المولّد بالذكاء الاصطناعي​

هذه أنماط الفشل التي تظهر مرارًا وتكرارًا في الكود المولّد بالذكاء الاصطناعي. تعلّم تمييز شكل كل واحدة حتى تكتشفها أثناء المسح لا أثناء قراءة كل سطر.

الثغرةكيف تبدو في الكود المولّدلماذا ينتجها النموذج
حقن SQLاستعلام مبني بدمج النصوص بدلًا من المعاملاتالدمج أسهل في الكتابة ويُقرأ «بشكل طبيعي»
XSSإدخال مستخدم غير مُعقَّم يُعرض بـ innerHTML أو dangerouslySetInnerHTMLعرض HTML مباشرة أقصر طريق إلى النتيجة البصرية
أسرار / مفاتيح API مضمّنةconst API_KEY = "sk-live-..." متروك في ملف المصدرالقيمة الحرفية تجعل المقتطف مكتفيًا ذاتيًا وقابلًا للتشغيل
مصادقة / إدارة جلسة معطوبةرموز مخزّنة في localStorage، بلا انتهاء، بلا تحقّق من جانب الخادمتخزين العميل أسهل للعرض التوضيحي في المتصفح
CORS متساهلAccess-Control-Allow-Origin: * مع بيانات اعتمادالنجمة هي الإصلاح بسطر واحد يوقف الخطأ الفوري
إلغاء تسلسل غير آمنpickle.loads أو eval أو الثقة بـ JSON مباشرةً في كائناتإلغاء تسلسل كل شيء مريح ويفترض إدخالًا موثوقًا
نقاط نهاية إدارية مكشوفةمسارات مثل /admin/users بلا فحص دورالنموذج ينفّذ الميزة لكنه ينسى الحارس
فقدان التحقّق من الإدخالالجسم يُستخدم مباشرةً في المنطق بلا فحص مخططالتحقّق يبدو زائدًا حين ينجح المسار السعيد أصلًا

حقن SQL في JavaScript​

إليك الثنائي الكلاسيكي: استعلام يدمج إدخال المستخدم، ونفس الاستعلام مكتوبًا بعناصر نائبة مُعامَلة.

// VULNERABLE: user input is concatenated straight into the query
app.get('/user', async (req, res) => {
const sql = "SELECT * FROM users WHERE email = '" + req.query.email + "'";
const rows = await db.query(sql);
res.json(rows);
});
// FIXED: parameterized query, input never becomes part of the SQL string
app.get('/user', async (req, res) => {
const sql = 'SELECT * FROM users WHERE email = $1';
const rows = await db.query(sql, [req.query.email]);
res.json(rows);
});

فحوصات التفويض في Python​

النسخة المعرّضة تثق بعلم يُرسله العميل. والنسخة المصحّحة تفحص الجلسة المصادق عليها على الخادم.

# VULNERABLE: the client tells the server whether it is an admin
@app.get("/admin/users")
def admin_users(request):
if request.args.get("is_admin") == "true":
return db.list_users()
return {"error": "forbidden"}
# FIXED: the server decides based on the authenticated session
@app.get("/admin/users")
def admin_users(request):
user = request.session.get("user")
if not user or user["role"] != "admin":
abort(403)
return db.list_users()

تخزين الرموز: localStorage مقابل كوكي httpOnly​

تخزين رمز جلسة في localStorage يجعله قابلًا للقراءة من أي سكربت في الصفحة، وهذا يحوّل خطأ XSS واحدًا إلى استيلاء كامل على الحساب.

// VULNERABLE: token readable by any script, no protection against XSS
localStorage.setItem('token', response.token);
// FIXED: server sets an httpOnly cookie that scripts cannot read
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 1000 * 60 * 60,
});

تدقيق الكود المولّد بالذكاء الاصطناعي مع Cursor​

التدقيق لا يعني قراءة كل سطر. إنه يعني طلب مرور ثانٍ من النموذج لمهاجمة ما كتبه المرور الأول. Cursor جيد في هذا عندما تعطيه الشخصية الصحيحة وهدفًا واضحًا.

مطالبة بشخصية مراجع أمني​

أعد استخدام هذه المطالبة في وضع Agent بعد أن ينهي الوكيل ميزة. تجبر النموذج على التحوّل من بانٍ إلى مهاجم.

You are a security reviewer auditing code a previous agent just wrote.
For every file changed in this feature:
1. List each place that handles user input, authentication, or secrets.
2. For each, describe the concrete attack an unauthenticated user could run.
3. Show the vulnerable line and the fixed line side by side.
4. Rate severity as low, medium, high, or critical with one sentence of reasoning.
Do not rewrite the whole feature. Report findings only.

طلب نمذجة التهديدات في وضع Agent​

وراء مراجعة الكود المكتمل، يمكنك أن تطلب من Cursor نمذجة تهديدات قبل أن يكتب. هذا يُظهر عيوب التصميم التي تفوتها المراجعة على مستوى السطر.

Before implementing this billing endpoint, do a short threat model:
- Who can call this route and with what privileges?
- What happens if the amount, currency, or user ID is tampered with?
- Which trust boundary does this input cross?
Then implement the endpoint with those mitigations in place.

استخدام .cursor/rules لفرض الفحوصات​

يمكنك ترميز التدقيق نفسه كقاعدة حتى يشغّله الوكيل تلقائيًا عند كل تغيير. تُحمَّل القواعد في .cursor/rules/ إلى السياق، ما يعني أن قائمة الفحص الأمني حاضرة دائمًا حين يعمل الوكيل.

دقّق بعد كل جلسة وكيل

اجعل «شغّل مراجعة أمنية» ردًّا منعكسًا بعد أي جلسة وكيل تلمس المصادقة أو معالجة الإدخال أو الأسرار. يكلّف مطالبة واحدة ويلتقط أنماط الفشل من الجدول أعلاه قبل وصولها إلى إيداع.

فرض الأمان بقواعد Cursor​

ملف قواعد المشروع يُبقي توقعاتك الأمنية أمام النموذج دون تكرار نفسك. أنشئ .cursor/rules/security.mdc وأودعه ليحصل كل متعاون على نفس الحواجز.

---
description: Security requirements for all code in this project
globs: ["**/*.ts", "**/*.tsx", "**/*.py", "**/*.js"]
alwaysApply: true
---

# Security requirements

- Never write SQL by concatenating user input. Use parameterized queries or an ORM.
- Never render user input with innerHTML or dangerouslySetInnerHTML. Escape it or use a safe renderer.
- Never hardcode secrets, API keys, or tokens. Read them from environment variables.
- Never grant administrative access based on a value supplied by the client.
- Always validate request bodies against a schema before use.
- Always set httpOnly, secure, and sameSite on session cookies.
- Never use eval, exec, or pickle.loads on untrusted input.
- When adding a new endpoint, state explicitly which role can call it.
- When touching auth or input handling, add a security-review note in the response.

مع alwaysApply: true، تُحقن القاعدة في كل طلب، فيصحّح الوكيل نفسه أثناء الكتابة بدلًا من التقاطك للمشكلات لاحقًا. يمكنك أيضًا تحديد نطاق القواعد لمجلدات معيّنة بـ globs حتى لا تعكّر قواعد أمان الخلفية عمل الواجهة.

إدارة الأسرار ومتغيّرات البيئة​

الأسرار المضمّنة هي الثغرة الأسهل في الشحن عن طريق الخطأ، لأن القيمة الحرفية تجعل الكود قابلًا للتشغيل والنموذج يحب الكود القابل للتشغيل. أبقِ الأسرار خارج المصدر تمامًا.

أنماط متغيّرات البيئة​

# .env.example -- commit this, it documents the shape without real values
DATABASE_URL=postgres://user:password@localhost:5432/app
SESSION_SECRET=replace-me
STRIPE_SECRET_KEY=replace-me
# Load locally, never commit the real .env
cp .env.example .env
# .env -- gitignored, contains the real values and stays on your machine
DATABASE_URL=postgres://realuser:realpass@prod-host:5432/app
SESSION_SECRET=a-long-random-value-from-a-secret-manager
STRIPE_SECRET_KEY=sk_live_the_actual_key

أضف دائمًا .env والملفات ذات الصلة إلى .gitignore قبل إيداعك الأول لا بعده.

# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key

فحص الأسرار قبل الإيداع بـ gitleaks​

خطاف ما قبل الإيداع يوقف سرًّا قبل أن يصل إلى تاريخ المستودع، وهذا أرخص بكثير من تدوير مفتاح مسرَّب.

# Install gitleaks (Homebrew example)
brew install gitleaks

# Scan the whole repo once
gitleaks detect --source . --verbose

# Or install it as a pre-commit hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
pre-commit install
pre-commit run --all-files
دوّر أولًا، نظّف ثانيًا

إن وقع سر حقيقي يومًا في إيداع، دوّر الاعتماد فورًا. حذف السطر أو إعادة كتابة التاريخ لا يلغي تسريب مفتاح سبق دفعه إلى مستودع بعيد.

التحقّق من المصادقة والتفويض يدويًا​

تفوت الفحوصات الآلية أخطاء المنطق، لذا لا يزال عليك التحقّق من التدفق يدويًا. تنقّل عبر هذه الفحوصات مقابل نسخة تعمل.

فحوصات الجلسة والمصادقة​

  • سجّل الخروج، ثم اطلب مسارًا محميًا بالرمز القديم. يجب أن يفشل بـ 401، لا أن يعيد بيانات.
  • غيّر معرّف المستخدم الخاص بك في رابط أو جسم إلى معرّف مستخدم آخر. يجب أن يرفضه الخادم، لا أن يثق بالمعامل.
  • استدعِ مسارًا إداريًا كغير إداري. يجب أن يعيد 403 بصرف النظر عن أي علم يوفّره العميل.
  • تأكّد من ضبط الكوكيز بـ httpOnly وsecure وانتهاء صلاحية. افتح وحدة تحكم المتصفح وحاول قراءة الكوكي بـ document.cookie؛ يجب ألا تظهر.
  • أعد ضبط كلمة مرور وتحقّق من إبطال الجلسة القديمة.

فحوصات الرموز والترويسات​

# A protected route without credentials must not return data
curl -i https://api.example.com/me

# A protected route with a forged or expired token must fail
curl -i https://api.example.com/me -H "Authorization: Bearer forged.token.value"

# An admin route as a normal user must return 403
curl -i https://api.example.com/admin/users -H "Authorization: Bearer $USER_TOKEN"

شغّل هذه مقابل بيئة اختبار تحاكي الإنتاج، لا مقابل localhost فقط حيث البيانات قابلة للتجاهل.

فحص الاعتماديات وسلسلة التوريد​

كودك الخاص جزء فقط من السطح. الكود المولّد يجلب اعتماديات، والحزمة المعرّضة ثغرة شحنتها. افحص الشجرة عند كل تغيير.

# Node / npm -- built in, no extra install
npm audit
npm audit --production

# Python
pip-audit
pip-audit -r requirements.txt
# Snyk for a cross-language view
npm install -g snyk
snyk test
snyk monitor

# Or run the Snyk CLI against a Python project
pip install snyk
snyk test --file=requirements.txt

وصّل ماسحًا في CI ليعمل عند كل طلب سحب بدلًا من الاعتماد على الذاكرة.

# .github/workflows/security.yml (excerpt)
- name: Scan dependencies
run: npm audit --production --audit-level=high
ثبّت الاعتماديات الجديدة وراجعها

قبل قبول حزمة اقترحها نموذج، تحقّق من عدد تنزيلاتها وتاريخ آخر نشر والمشرف عليها. الحزم المتشابهة في الاسم (Typosquatted) ناقل هجوم حقيقي، وقد يقترح نموذج اسمًا قريبًا من المكتبة الحقيقية لا هو نفسه.

قائمة فحص أمني قبل النشر​

شغّل هذه القائمة قبل كل نشر. إنها قصيرة عمدًا حتى تستخدمها فعلًا.

  • لا أسرار أو مفاتيح API أو رموز مضمّنة في أي ملف مُودَع
  • ملف .env والملفات المفاتيح في .gitignore ولم تُودَع يومًا
  • كل SQL يستخدم استعلامات مُعامَلة أو ORM، بلا دمج نصوص
  • كل إدخال مستخدم مُتحقَّق منه مقابل مخطط قبل الاستخدام
  • رموز الجلسة مخزّنة في كوكيز httpOnly، لا localStorage
  • كل مسار محمي يعيد 401/403 عند عدم المصادقة أو عدم التفويض
  • المسارات الإدارية تتحقّق من الدور من جانب الخادم، لا من قيمة يوفّرها العميل
  • CORS يسمح فقط بأصولك المعروفة، لا * مع بيانات اعتماد
  • تقارير npm audit / pip-audit لا تسرد نتائج عالية أو حرجة
  • ردود الأخطاء لا تسرّب آثار المكدّس أو SQL أو المسارات الداخلية
  • التسجيل لا يسجّل رموزًا أو كلمات مرور أو أجسام الطلبات كاملة
  • جرى مرور مراجعة أمنية على كل ملف لمسه الوكيل في هذا السبرنت

الإبلاغ بالنتائج ومعالجتها​

النتائج مفيدة فقط إذا أُصلحت وتُتبّعت. أعطِ كل واحدة بنية كافية للتصرف بها فقط.

  1. سجّل النتيجة. سطر واحد بالملف والسطر ونوع الثغرة والخطورة.
  2. اكتب الإصلاح كفرق. اجعل Cursor ينتج التغيير من السطر المعرّض إلى المصحّح، ثم راجعه.
  3. أضف اختبارًا يثبت الإصلاح. اختبار انحدار لحقن أو تجاوز المصادقة يمنعه من العودة.
  4. حدّث القاعدة. إذا أنتج الوكيل الخلل، أضف سطرًا إلى .cursor/rules/security.mdc حتى لا يُنتجه مرة أخرى.
  5. أعد الاختبار وأغلق. أعد تشغيل الفحوصات اليدوية والماسح مقابل النسخة المصحّحة.
Finding: CWE-89 SQL injection, src/api/users.ts:42, severity high
Vulnerable: db.query("SELECT * FROM users WHERE id = " + id)
Fixed: db.query("SELECT * FROM users WHERE id = $1", [id])
Regression test: request /user?id=1' OR '1'='1 returns no rows
Rule update: added "never concatenate user input into SQL" to security.mdc
Status: fixed, verified in staging

عامل ملف القواعد كسجل نامٍ لكل خطأ ارتكبه الوكيل. مع الوقت يصبح أثمن أصل أمني في المستودع، لأنه يرمّز بالضبط الطرق التي أخطأ بها كودك سابقًا.