תהליך עבודה אג'נטי מקצה לקצה לאבטחה באמצעות CodeMender

1. מבוא

ב-Codelab הזה תשלבו את שני ה-Codelabs הקודמים. אתם תגישו ל-Antigravity הגשת בקשה להוספת תכונה, ואז תראו איך הוא מתכנן אותה באמצעות מודל איומים, כותב בדיקות שנכשלות, מטמיע את הפיצ'ר ודוחף אותה. כשמבצעים דחיפה, שער האבטחה מה-Codelab הקודם מריץ את CodeMender על הקבצים שהשתנו. באפליקציה לדוגמה עדיין יש הזרקת פקודות פתוחה בקובץ שהסוכן עורך, ולכן הפוש הראשון נחסם. אתם תראו שהסוכן מנקה את הממצא קודם כל בבדיקה ואז מבצע פוש שוב, בלי הנחיות אנושיות מ-CodeMender לאורך התהליך.

מה תלמדו

  • איך להריץ את CodeMender בלי ממשק משתמש, כדי שה-hook והסוכן לא יחסמו את ההנחיות של [Y/n].
  • מה נוצר בלולאת "תכנון ← אדום ← ירוק ← שכתוב קוד" כשהסוכן יוצר תכונה: threat_model.md, בדיקות, קוד ו-commit.
  • איך שער האבטחה מגיב כשקובץ שנדחף כולל ממצאים פתוחים, ואיך הנציג משתמש במיומנויות security-test-writer ו-defensive-developer כדי לנקות אותם.
  • איך לקרוא את נתיב הביקורת שהשער משאיר מאחור: .security-gate/findings-log.ndjson, מוסכמות שהתפתחו באופן אוטומטי ב-CONTEXT.md והיסטוריית git.

דרישות מוקדמות

  • סביבת העבודה ~/codemender-lab משני ה-Codelabs הקודמים, עם עץ עבודה נקי של Git בענף main.
  • האפליקציה Antigravity הותקנה, ו-~/codemender-lab זמינה לפתיחה כתיקייה.
  • ‫git, jq והסביבה הווירטואלית של Python עם Flask מותקן.

2. אימות ההגדרה משיעורי ה-Codelab הקודמים

שלב 1: חוזרים למאגר של Lab

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.

מריצים שוב את בדיקות ה-hook אופליין כדי לוודא ששום דבר לא השתנה:

bash .agents/tests/run_tests.sh | tail -1
60 passed, 0 failed

שלב 3: מאשרים את סביבת CodeMender

cm init --verify

הוא אמור לדווח על git כסוג ה-VCS ועל python3 -m compileall -q . כפקודת ה-build, כמו שהוגדר בקטע "היסודות של CodeMender CLI".

שלב 4: מנקים ממצאים ישנים

cm clean

מקלידים y כשמוצגת בקשה. הפעולה הזו מרוקנת את ~/.codemender/state.db.

שלב 5: יצירת מאגר מרוחק שאליו אפשר לדחוף את הקוד

ה-hook משווה בין עץ העבודה לבין origin/main, וצריך מקום שאליו git push יוכל לעבור. מאגר ריק באותו מחשב מספיק:

git init --bare ~/codemender-remote.git
git remote add origin ~/codemender-remote.git
git push -u origin main

הדחיפה הזו מתרחשת בטרמינל שלכם, כך שה-hook של Antigravity לא רואה אותה. מעכשיו, הסוכן מבצע את הפעולות.

3. הפעלת CodeMender ללא ממשק משתמש גרפי (GUI)

שלב 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: נותנים לנציג את המשימה

מדביקים את ההנחיה הבאה. שימו לב שאין שם שום אזכור לאבטחה. ההנחיה לסוכן להתמקד במידול איומים, בפיתוח שמתחיל בבדיקות ובדפוסי הגנה היא התפקיד של הכללים והמיומנויות, ולא של ההנחיה:

מוסיפים נקודת קצה (endpoint) של POST /update_profile ל-sample_app/data_service.py שמאפשרת למשתמש לעדכן את כתובת האימייל שלו. הוא מקבל את user_id ו-email כשדות בטופס. כשהשינוי הושלם וחבילת המקרים לבדיקה עברה, מבצעים commit ודוחפים ל-origin main.

שלב 3: מאשרים את הפקודות של הסוכן

ה-hook פועל מחוץ למערכת ההרשאות של הכלי, אבל הפקודות של הנציג עצמו לא. הוא יציג הנחיות ל-python3 -m unittest ..., ל-git add/commit/push ולכל פקודת cm שהוא יחליט להריץ. מאשרים אותן. אם Antigravity מציע לזכור את ההחלטה, מאשרים אותה כדי שלולאת התיקון בהמשך ה-Codelab הזה לא תתעכב בגלל הנחיות חוזרות.

5. מה הסוכן עושה: תכנון, אדום, ירוק, שכתוב קוד (refactoring)

בהמשך מופיעה הרצה מייצגת. הניסוח, שמות הקבצים והקוד המדויק של הסוכן שלכם יהיו שונים, וזה בסדר. מה שלא צריך להשתנות הוא הסדר: מודל איומים לפני בדיקות, בדיקות שנכשלות לפני קוד, חבילת בדיקות שעוברות לפני השמירה.

שלב א': תכנון ומודל איומים

הסוכן קורא את 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 חד-פעמי בספריית העבודה, ולכן ב-Codelab הקודם הוספנו את 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

עכשיו חבילת הבדיקות עוברת: ארבע הבדיקות החדשות ובנוסף בדיקת האפיון משיעור ה-Codelab הקודם.

שלב ד': שכתוב, סריקה, אישור

הסוכן טוען את local-refactor-scanner, בודק את git diff, מריץ מחדש את כל חבילת הבדיקות ומבצע commit:

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, שמזהה את ה-push וסורק את הקבצים שהשתנו: sample_app/data_service.py,‏ tests/test_update_profile.py ו-threat_model.md (‫CodeMender מדלג על קובצי .md;‏ cm find בנתיב הזה לא מוצא קובצי מקור וה-hook ממשיך הלאה).

הפעולה תימשך כמה דקות. ה-hook הוא סינכרוני, כך שנראה שהתור של הנציג תקוע בפקודת ה-push בזמן ש-cm find ו-cm report פועלים. בסיום הסריקה, השער מוצא את הזרקת הפקודה בקובץ /ping, ש-CodeMender CLI Basics לא תיקן. הוא CRITICAL, הוא OPEN והוא נמצא בקובץ שנדחף, ולכן ה-hook דוחה:

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 כתוצאה של קריאת ה-API לכלי, והשער רשם אירוע 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)

התיקון המינימלי, החלפת os.system ב-subprocess.run בצורת רשימה כפי שנדרש על ידי CONTEXT.md, בתוספת מנתח רשימת היתרים במקום בדיקות מחרוזות:

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"

הסוכן מריץ את כל חבילת הבדיקות, מבצע commit עם הודעה שמתארת את הממצא ואת התיקון, ודוחף שוב.

ההתראה השנייה מותרת

השער סורק מחדש את sample_app/data_service.py. הזרקת הפקודה נעלמה. לא נותרו ממצאים חוסמים, ולכן השער בודק את היומן שלו, רואה שהממצא שהוא דחה קודם כבר לא קיים, ומתעד את התוצאה לפני שהוא מאפשר את ה-push:

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) שה-hook ביצע בעצמו.

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.

הרשומה של ה-hook היא קצרה כי היא מכילה רק את מזהה הממצא, הקובץ ורמת החומרה. המיומנות skill-evolution-updater קיימת כדי להפוך את זה לכלל שאדם היה כותב. מבקשים מהנציג: "Use skill-evolution-updater to rewrite the last auto-evolved convention in CONTEXT.md as a project rule, and suggest any SKILL.md change it implies."

התצוגה של 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

כדי להסיר גם את ספריות ה-Lab:

cd ~ && rm -rf ~/codemender-lab ~/codemender-remote.git

מעולה! סיימתם את ה-Codelab בנושא תהליך עבודה אג'נטי מקצה לקצה לאבטחה באמצעות CodeMender. ראיתם את ה-CLI מה-Codelab הראשון, את המיומנויות וה-hook מהשני, וסוכן שפועל באמצעות דחיפה שנחסמה, תוקנה, תועדה ביומן ואושרה בלי הנחיה של CodeMender בלולאה.