1. مقدمة
في هذا الدرس التطبيقي حول الترميز، ستجمع بين الدرسَين التطبيقيَين السابقَين. ستقدّم طلب ميزة إلى Antigravity، ثم ستشاهد كيف يخطّط لتنفيذها باستخدام نموذج تهديد، ويكتب اختبارات فاشلة، وينفّذ الميزة، ثم يرسلها. عند إرسال التغييرات، يشغّل حاجز الأمان من الدرس التطبيقي السابق CodeMender على الملفات التي تم تغييرها. بما أنّ نموذج التطبيق لا يزال يحتوي على ثغرة حقن أوامر مفتوحة في الملف الذي يعدّله الوكيل، يتم حظر عملية الإرسال الأولى، وسيظهر لك الوكيل وهو يزيل النتيجة من خلال اختبارها أولاً ثم يرسلها مرة أخرى، بدون أي طلبات من CodeMender خلال هذه العملية.
ما ستتعرّف عليه
- كيفية تشغيل CodeMender بدون واجهة مستخدم لكي لا يحظر الـ hook أو الوكيل طلبات
[Y/n]. - ما تنتجه حلقة "الخطة" ← "أحمر" ← "أخضر" ← "إعادة الهيكلة" عندما ينشئ الوكيل ميزة:
threat_model.md، والاختبارات، والرمز البرمجي، وعملية الإتمام. - كيفية استجابة بوابة الأمان عندما يحتوي ملف تم دفعه على نتائج مفتوحة، وكيفية استخدام الوكيل لمهارات
security-test-writerوdefensive-developerلمسحها. - How to read the audit trail the gate leaves behind:
.security-gate/findings-log.ndjson, auto-evolved conventions inCONTEXT.md, and git history.
المتطلبات الأساسية
- مساحة عمل
~/codemender-labمن الدرسَين التطبيقيَّين السابقَين حول الترميز، مع شجرة عمل git نظيفة في الفرعmain - تم تثبيت Antigravity، ويتوفّر
~/codemender-labلفتحه كمجلد. -
gitوjqوبيئة Python الافتراضية مع تثبيت Flask
2. التحقّق من الإعداد من الدروس التطبيقية السابقة حول الترميز
الخطوة 1: العودة إلى دليل التمرين العملي
cd ~/codemender-lab && source .venv/bin/activate
الخطوة 2: التأكّد من توفّر ملفات إطار العمل
ls .agents/hooks.json .agents/security_gate_hook.sh .agents/rules/secure_tdd_workflow.md
ls .agents/skills
ls tests
git status --short && git branch --show-current
من المفترض أن تظهر لك أدلة المهارات الستة (defensive-developer وhistory-context-seeder وlocal-refactor-scanner وsecurity-test-writer وskill-evolution-updater وthreat-model-assessor) وtests/__init__.py وtests/test_data_service.py وgit status فارغ وmain.
شغِّل اختبارات الروابط بلا إنترنت مرة أخرى للتأكّد من عدم حدوث أي تغيير:
bash .agents/tests/run_tests.sh | tail -1
60 passed, 0 failed
الخطوة 3: تأكيد بيئة CodeMender
cm init --verify
يجب أن يظهر git كنوع VCS وpython3 -m compileall -q . كأمر الإنشاء، كما تم ضبطهما في أساسيات واجهة سطر أوامر CodeMender.
الخطوة 4: محو النتائج القديمة
cm clean
اكتب y عندما يُطلب منك ذلك. سيؤدي هذا الإجراء إلى إفراغ ~/.codemender/state.db.
الخطوة 5: إنشاء مستودع عن بُعد لإرسال التغييرات إليه
يقارن الخطاف شجرة العمل مع origin/main، ويحتاج git push إلى مكان ما للذهاب إليه. يكفي مستودع مكشوف على نفس الجهاز:
git init --bare ~/codemender-remote.git
git remote add origin ~/codemender-remote.git
git push -u origin main
يحدث هذا الدفع في جهازك الطرفي، لذا لا يراه خطاف Antigravity. من الآن فصاعدًا، سيُجري الوكيل عمليات الدفع.
3- تشغيل CodeMender Headless
الخطوة 1: إيقاف طلبات التأكيد
يطلب CodeMender تلقائيًا إذن "[Y/n]" قبل تنفيذ أي أمر أو كتابة أي ملف. داخل الخطاف، لا يوجد أحد هناك للإجابة. افتح ~/.codemender/config.yaml واضبط علامتَي tools على false، أو شغِّل:
sed -i.bak 's/^ confirm_commands: true/ confirm_commands: false/; s/^ confirm_writes: true/ confirm_writes: false/' ~/.codemender/config.yaml
rm ~/.codemender/config.yaml.bak
يجب أن يظهر القسم الناتج على النحو التالي:
tools:
confirm_commands: false
confirm_writes: false
الخطوة 2: التحقّق
cm init --verify
4. طلب الميزة
الخطوة 1: فتح مساحة العمل
افتح ~/codemender-lab كمجلد في Antigravity وابدأ محادثة جديدة. أصبحت قاعدة "secure_tdd_workflow" المفعَّلة دائمًا جزءًا من كل دور، وأصبحت المهارات الست متاحة.
الخطوة 2: إعطاء الوكيل المهمة
ألصِق الطلب التالي. لاحظ أنه لا يذكر شيئًا عن الأمان. توجيه الوكيل نحو نمذجة التهديدات والتطوير القائم على الاختبار أولاً والأنماط الدفاعية هو مهمة القاعدة والمهارات، وليس الطلب:
أضِف نقطة نهاية POST /update_profile إلى sample_app/data_service.py تتيح للمستخدم تعديل عنوان بريده الإلكتروني. ويقبل user_id وemail كحقول نماذج. عند اكتمال التغيير واجتياز الحزمة الاختبارية، يمكنك تنفيذ التغيير وإرساله إلى origin main.
الخطوة 3: الموافقة على طلبات الوكيل
يتم تشغيل الـ hook خارج نظام أذونات الأدوات، ولكن لا يتم تشغيل أوامر الوكيل نفسه. ستظهر لك طلبات من python3 -m unittest ... وgit add/commit/push وأي أمر cm يقرّر تشغيله. يُرجى الموافقة عليها، وإذا عرضت Antigravity تذكُّر القرار، يُرجى قبوله حتى لا تتوقف حلقة الإصلاح لاحقًا في هذا الدرس التطبيقي حول الترميز بسبب الطلبات المتكررة.
5- ما يفعله الوكيل: التخطيط، والاختبارات الفاشلة، والاختبارات الناجحة، وإعادة التصميم
في ما يلي مثال على عملية تشغيل. ستختلف صياغة الوكيل وأسماء الملفات والرمز الدقيق، وهذا أمر طبيعي. ما لا ينبغي أن يختلف هو الترتيب: نموذج التهديد قبل الاختبارات، والاختبارات الفاشلة قبل الكود، ومجموعة خضراء قبل الالتزام.
المرحلة "أ": التخطيط ونمذجة التهديدات
يقرأ الوكيل CONTEXT.md، ويحمّل threat-model-assessor، ويكتب threat_model.md في جذر مساحة العمل:
# Feature Plan & Threat Model: Update Profile Email
## 1. Functional Scope & Requirements
- **Goal**: Allow a user to change the email address stored for their account.
- **Deliverables**: `POST /update_profile` in `sample_app/data_service.py`; tests in `tests/test_update_profile.py`.
- **Functional Acceptance Criteria**: Valid `user_id` + `email` returns 200 and the row is updated; unknown `user_id` returns 404.
## 2. Entry Points & Data Inputs
- Endpoint / Input: `POST /update_profile`, form fields `user_id`, `email`
- Format & Constraints: `user_id` positive integer; `email` <= 254 chars, single `@`, non-empty local and domain parts.
## 3. Trust Boundaries & Access Controls
- Authentication: None exists in this sample. Recorded as a known gap; out of scope for this change.
- Authorization Role: N/A (see above).
## 4. STRIDE Threat Matrix & Security Acceptance Criteria
- Tampering: SQL injection through `user_id`/`email` -> parameterized `UPDATE`; test that `"1 OR 1=1"` returns 400.
- Tampering: Malformed email stored -> strict validation; test that `"not-an-email"` returns 400.
- Information Disclosure: Stack traces on bad input -> return JSON error bodies only.
- Denial of Service: Oversized email -> length cap at 254; test returns 400.
المرحلة ب: الاختبارات الفاشلة (أحمر)
يحمّل الوكيل security-test-writer وينشئ tests/test_update_profile.py. يستخدم الإصدار العادي ملف users.db مؤقتًا في دليل العمل، ولهذا السبب أضاف الدرس التطبيقي السابق حول الترميز users.db إلى .gitignore:
import os
import sqlite3
import unittest
from sample_app.data_service import app
class UpdateProfileTests(unittest.TestCase):
def setUp(self):
conn = sqlite3.connect('users.db')
conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, email TEXT)")
conn.execute("INSERT OR REPLACE INTO users (id, username, email) VALUES (1, 'alice', 'old@example.com')")
conn.commit()
conn.close()
self.client = app.test_client()
def tearDown(self):
os.remove('users.db')
def test_updates_email_for_valid_request(self):
r = self.client.post('/update_profile', data={'user_id': '1', 'email': 'new@example.com'})
self.assertEqual(r.status_code, 200)
def test_rejects_non_numeric_user_id(self):
r = self.client.post('/update_profile', data={'user_id': '1 OR 1=1', 'email': 'new@example.com'})
self.assertEqual(r.status_code, 400)
def test_rejects_malformed_email(self):
r = self.client.post('/update_profile', data={'user_id': '1', 'email': 'not-an-email'})
self.assertEqual(r.status_code, 400)
def test_rejects_oversized_email(self):
r = self.client.post('/update_profile', data={'user_id': '1', 'email': 'a' * 250 + '@x.io'})
self.assertEqual(r.status_code, 400)
يُشغّل الوكيل python3 -m unittest discover -s tests ويتأكّد من تعذُّر الاختبارات الجديدة مع ظهور الخطأين 404 != 200 و404 != 400، لأنّ المسار غير موجود بعد. هذا هو السبب الصحيح للفشل. الفشل الناتج عن خطأ في الاستيراد أو خطأ مطبعي ليس أحمر؛ إنه اختبار معطل.
المرحلة ج: الحد الأدنى من التنفيذ (باللون الأخضر)
يحمّل الوكيل defensive-developer ويضيف نقطة النهاية. يستخدم طلب بحث ذي معلَمات لأنّ CONTEXT.md يحدّد ذلك كنمط مطلوب لـ SQL:
import re
EMAIL_PATTERN = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
@app.route('/update_profile', methods=['POST'])
def update_profile():
user_id = request.form.get('user_id', '')
email = request.form.get('email', '')
if not user_id.isdigit():
return jsonify({"error": "user_id must be a positive integer"}), 400
if len(email) > 254 or not EMAIL_PATTERN.match(email):
return jsonify({"error": "invalid email address"}), 400
conn = sqlite3.connect('users.db')
try:
cursor = conn.execute("UPDATE users SET email = ? WHERE id = ?", (email, int(user_id)))
conn.commit()
finally:
conn.close()
if cursor.rowcount == 0:
return jsonify({"error": "user not found"}), 404
return jsonify({"status": "updated", "user_id": int(user_id)}), 200
تنجح المجموعة الآن في اجتياز الاختبارات الأربعة الجديدة بالإضافة إلى اختبار التوصيف من الدرس التطبيقي السابق حول الترميز.
المرحلة الرابعة: تحسين البنية والفحص والتثبيت
يحمّل الوكيل local-refactor-scanner، ويراجع git diff، ويعيد تشغيل المجموعة الكاملة، وينفّذ ما يلي:
git add sample_app/data_service.py tests/test_update_profile.py threat_model.md
git commit -m "feat: add POST /update_profile with validated input and parameterized update"
6. بوابة الأمان في git push
تم رفض الإشعار الفوري الأول
يعمل الوكيل git push origin main. تطابق أداة Antigravity أداة run_command مع hooks.json وتُشغّل security_gate_hook.sh، التي تتعرّف على عملية الإرسال وتفحص الملفات التي تم تغييرها: sample_app/data_service.py وtests/test_update_profile.py وthreat_model.md (تتخطّى CodeMender ملفات .md، ولا تعثر cm find في هذا المسار على أي ملفات مصدر وينتقل الرمز البرمجي إلى الخطوة التالية).
ستستغرق هذه العملية بضع دقائق. يكون الربط متزامنًا، لذا يبدو أنّ دور الوكيل معلّق على أمر push أثناء تشغيل cm find وcm report. عند انتهاء الفحص، تعثر البوابة على حقن الأوامر في /ping الذي لم يتم إصلاحه من خلال CodeMender CLI Basics. إنّه CRITICAL، وهو OPEN، وهو في ملف يتم دفعه، لذا يرفض الخطاف:
Stage 2: Running Semantic Analysis & Verification (CodeMender)...
Detected 1 blocking vulnerabilit(y/ies). Evaluating attempt budget per finding...
Finding 80bdddd6-... (CRITICAL) in /home/you/codemender-lab/sample_app/data_service.py: Attempt 1/3
{"allow_tool": false, "decision": "deny", "reason": "Security Gate blocked push (1 unresolved finding(s)).
Action Required: Execute the Secure TDD Loop sequentially for each finding:
For each finding below (one at a time):
1. Invoke 'security-test-writer' to author one failing boundary test in tests/ (assert RED).
2. Invoke 'defensive-developer' to apply the minimal defensive fix (assert GREEN).
3. Run the local test to confirm it passes before moving to the next finding.
Once all findings are resolved, run the full regression test suite (python3 -m unittest discover -s tests) and retry 'git push'.
Open Findings:
- [80bdddd6-...] (CRITICAL) in /home/you/codemender-lab/sample_app/data_service.py (Attempt 1/3): OS Command Injection in ping_host endpoint", ...}
لم يتم تنفيذ عملية الإرسال. يرى الوكيل نص reason كنتيجة لاستدعاء الأداة، وقد سجّل البوابة حدث DENIED_TO_AGENT لهذه النتيجة.
يزيل الوكيل النتيجة من خلال اختبارها أولاً
يحدّد الرفض المهارات التي يجب استخدامها، وتنصّ القاعدة رقم 6 (بروتوكول معالجة النتائج المتعدّدة) على معالجة النتائج واحدة تلو الأخرى. بالنسبة إلى النتيجة "/ping"، تظهر عملية تشغيل تمثيلية على النحو التالي.
اختبار حدود غير ناجح في tests/test_ping.py:
import unittest
from sample_app.data_service import app
class PingBoundaryTests(unittest.TestCase):
def setUp(self):
self.client = app.test_client()
def test_rejects_shell_metacharacters(self):
r = self.client.get('/ping', query_string={'ip': '127.0.0.1; id'})
self.assertEqual(r.status_code, 400)
def test_rejects_hostname_that_is_not_an_ip(self):
r = self.client.get('/ping', query_string={'ip': 'example.com'})
self.assertEqual(r.status_code, 400)
The minimal fix, replacing os.system with a list-form subprocess.run as CONTEXT.md requires, plus an allow-list parser instead of string checks:
import ipaddress
import subprocess
@app.route('/ping')
def ping_host():
ip = request.args.get('ip', '')
try:
target = str(ipaddress.ip_address(ip))
except ValueError:
return jsonify({"error": "ip must be a valid IP address"}), 400
subprocess.run(["ping", "-c", "1", target], shell=False, check=False, timeout=5)
return "Ping initiated"
يُشغّل الوكيل مجموعة الاختبارات الكاملة، ويُضيف رسالة تصف المشكلة والحلّ، ثم يُرسل التغييرات مرة أخرى.
يُسمح بالدفع الثاني
تعيد البوابة فحص sample_app/data_service.py. تمت إزالة حقن الأوامر. لم تعُد هناك نتائج حظر، لذا تتحقّق البوابة من سجلّها الخاص، وترى أنّ النتيجة التي رفضتها سابقًا لم تعُد موجودة، وتسجّل النتيجة قبل السماح بالدفع:
Stage 2: Running Semantic Analysis & Verification (CodeMender)...
No blocking vulnerabilities detected. Code verified clean.
Updated CONTEXT.md with auto-evolved convention for 80bdddd6-....
Continuous Evolution Suggestion: Consider updating relevant SKILL.md files (e.g. security-test-writer / defensive-developer) to encode CRITICAL.
{"allow_tool": true, "decision": "allow"}
يتم تشغيل git push origin main، ويحتوي المستودع البعيد الآن على ثلاث عمليات دمج جديدة: الميزة، والإصلاح، وعملية دمج docs(context) التي أجراها الخطاف نفسه.
7. مراجعة مسار التدقيق
كل ما قررته البوابة موجود على القرص في نص عادي. من وحدة طرفية في ~/codemender-lab:
سجلّ البوابة
jq -c '{ts, event, tool, finding_id, file, attempt}' .security-gate/findings-log.ndjson
{"ts":"2026-11-13T22:41:07Z","event":"DENIED_TO_AGENT","tool":"codemender","finding_id":"80bdddd6-...","file":"/home/you/codemender-lab/sample_app/data_service.py","attempt":1}
{"ts":"2026-11-13T22:58:32Z","event":"FIXED","tool":"codemender","finding_id":"80bdddd6-...","file":"/home/you/codemender-lab/sample_app/data_service.py","attempt":null}
يتضمّن كل سطر أيضًا actor (عنوان بريدك الإلكتروني في git) وcommit (خوارزمية SHA قصيرة في ذلك الوقت)، ما يتيح الإجابة عن السؤال "مَن سمح بذلك ومتى" لاحقًا بدون قاعدة بيانات.
سجلّ Git
git log --oneline -5
a1b2c3d docs(context): record auto-evolved convention for 80bdddd6-...
e4f5a6b fix: validate ip and use subprocess list form in /ping (CWE-78)
c7d8e9f feat: add POST /update_profile with validated input and parameterized update
...
السياق المتطوّر
tail -n 4 CONTEXT.md
## 4. Continuous Evolution: Auto-Evolved Conventions
- **Auto-Evolved Convention (80bdddd6-...)**: In `/home/you/codemender-lab/sample_app/data_service.py`, maintain defensive validation for: CRITICAL.
يكون إدخال الردّ التلقائي موجزًا لأنّه يعرف فقط معرّف النتيجة والملف ومستوى الخطورة. تتوفّر مهارة "skill-evolution-updater" لتحويل هذه البيانات إلى قاعدة يمكن لشخص كتابتها. اطلب من الوكيل: "استخدِم skill-evolution-updater لإعادة كتابة آخر قاعدة تم تطويرها تلقائيًا في CONTEXT.md كقاعدة مشروع، واقترِح أي تغيير في SKILL.md يتطلّبه ذلك".
طريقة عرض CodeMender
cm report
يُفترض الآن أن تظهر نتيجة حقن الأوامر بحالة غير OPEN، وأن يكون الملف threat_model.md وملفا الاختبار الجديدان في المستودع لتستند إليهما الميزة التالية.
8. جارٍ التنظيف
استعادة الإعدادات التلقائية التفاعلية في CodeMender:
sed -i.bak 's/^ confirm_commands: false/ confirm_commands: true/; s/^ confirm_writes: false/ confirm_writes: true/' ~/.codemender/config.yaml
rm ~/.codemender/config.yaml.bak
محو الحالة المحلية لـ CodeMender (اكتب y في الطلب):
cm clean
لإزالة أدلة التمرين أيضًا:
cd ~ && rm -rf ~/codemender-lab ~/codemender-remote.git
تهانينا! لقد أكملت الدرس التطبيقي حول الترميز "سير عمل الأمان الوكيل الشامل باستخدام CodeMender". لقد تعرّفت على واجهة سطر الأوامر (CLI) من الدرس التطبيقي الأول حول الترميز، والمهارات والخطاف من الدرس الثاني، ووكيل يعمل من خلال عملية دفع تم حظرها وإصلاحها وتسجيلها والسماح بها بدون طلب CodeMender في الحلقة.