Alur Kerja Keamanan Agentic End-to-End dengan CodeMender

1. Pengantar

Dalam codelab ini, Anda akan menggabungkan dua codelab sebelumnya. Anda akan memberikan permintaan fitur kepada Antigravity, lalu melihatnya membuat rencana dengan model ancaman, menulis pengujian yang gagal, menerapkan fitur, dan melakukan push. Saat push, gerbang keamanan dari codelab sebelumnya menjalankan CodeMender terhadap file yang diubah. Karena aplikasi contoh masih memiliki injeksi perintah terbuka dalam file yang sedang diedit agen, push pertama akan diblokir, dan Anda akan melihat agen menghapus temuan dengan menguji terlebih dahulu dan melakukan push lagi, tanpa perintah manusia dari CodeMender selama prosesnya.

Yang akan Anda pelajari

  • Cara menjalankan CodeMender secara headless sehingga hook maupun agen tidak memblokir perintah [Y/n].
  • Hasil dari loop Rencana → Merah → Hijau → Refaktorisasi saat agen membangun fitur: threat_model.md, pengujian, kode, dan commit.
  • Cara gerbang keamanan merespons saat file yang dikirim memiliki temuan terbuka, dan cara agen menggunakan keterampilan security-test-writer dan defensive-developer untuk menghapusnya.
  • Cara membaca jejak audit yang ditinggalkan gate: .security-gate/findings-log.ndjson, konvensi yang berkembang secara otomatis di CONTEXT.md, dan histori git.

Prasyarat

  • Ruang kerja ~/codemender-lab dari dua codelab sebelumnya, dengan hierarki kerja git yang bersih di cabang main.
  • Antigravity diinstal, dengan ~/codemender-lab tersedia untuk dibuka sebagai folder.
  • git, jq, dan lingkungan virtual Python dengan Flask yang terinstal.

2. Memverifikasi Penyiapan dari Codelab Sebelumnya

Langkah 1: Kembali ke direktori lab

cd ~/codemender-lab && source .venv/bin/activate

Langkah 2: Pastikan file framework sudah ada

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

Anda akan melihat enam direktori skill (defensive-developer, history-context-seeder, local-refactor-scanner, security-test-writer, skill-evolution-updater, threat-model-assessor), tests/__init__.py dan tests/test_data_service.py, git status kosong, dan main.

Jalankan pengujian hook offline sekali lagi untuk memastikan tidak ada yang berubah:

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

Langkah 3: Konfirmasi lingkungan CodeMender

cm init --verify

Langkah ini akan melaporkan git sebagai jenis VCS dan python3 -m compileall -q . sebagai perintah build, seperti yang dikonfigurasi di Dasar-Dasar CodeMender CLI.

Langkah 4: Hapus temuan yang sudah tidak berlaku

cm clean

Ketikkan y saat diminta. Tindakan ini akan mengosongkan ~/.codemender/state.db.

Langkah 5: Buat remote untuk mengirim ke

Hook membandingkan hierarki kerja Anda dengan origin/main, dan git push memerlukan tempat untuk dituju. Repositori kosong di mesin yang sama sudah cukup:

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

Push ini terjadi di terminal Anda sendiri, sehingga hook Antigravity tidak melihatnya. Dari sini, agen akan melakukan push.

3. Menjalankan CodeMender Headless

Langkah 1: Nonaktifkan dialog konfirmasi

Secara default, CodeMender akan meminta [Y/n] sebelum menjalankan setiap perintah dan menulis setiap file. Di dalam hook, tidak ada yang menjawab. Buka ~/.codemender/config.yaml dan tetapkan kedua flag tools ke false, atau jalankan:

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

Bagian yang dihasilkan akan terlihat seperti berikut:

tools:
  confirm_commands: false
  confirm_writes: false

Langkah 2: Verifikasi

cm init --verify

4. Permintaan Fitur

Langkah 1: Buka ruang kerja

Buka ~/codemender-lab sebagai folder di Antigravity dan mulai percakapan baru. Aturan secure_tdd_workflow yang selalu aktif kini menjadi bagian dari setiap giliran, dan enam skill tersedia.

Langkah 2: Berikan tugas kepada agen

Tempelkan perintah berikut. Perhatikan bahwa tidak ada yang disebutkan tentang keamanan. Mengarahkan agen ke pembuatan model ancaman, pengembangan yang mengutamakan pengujian, dan pola defensif adalah tugas aturan dan keterampilan, bukan perintah:

Tambahkan endpoint POST /update_profile ke sample_app/data_service.py yang memungkinkan pengguna memperbarui alamat email mereka. Alat ini menerima user_id dan email sebagai kolom formulir. Setelah perubahan selesai dan suite pengujian lulus, commit dan kirim ke origin main.

Langkah 3: Setujui perintah agen

Hook berjalan di luar sistem izin alat, tetapi perintah agen itu sendiri tidak. Perkirakan perintah untuk python3 -m unittest ..., git add/commit/push, dan perintah cm apa pun yang diputuskan untuk dijalankan. Setujui; jika Antigravity menawarkan untuk mengingat keputusan, terima agar loop perbaikan nanti dalam codelab ini tidak terhenti karena perintah berulang.

5. Yang Dilakukan Agen: Plan, Red, Green, Refactor

Berikut adalah contoh prosesnya. Kata-kata, nama file, dan kode persis agen Anda akan berbeda, dan itu tidak masalah. Yang tidak boleh berbeda adalah urutannya: model ancaman sebelum pengujian, pengujian yang gagal sebelum kode, rangkaian hijau sebelum commit.

Fase A: Merencanakan dan membuat model ancaman

Agen membaca CONTEXT.md, memuat threat-model-assessor, dan menulis threat_model.md di root ruang kerja:

# 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.

Fase B: Pengujian yang gagal (MERAH)

Agen memuat security-test-writer dan membuat tests/test_update_profile.py. Versi standar menggunakan users.db sekali pakai di direktori kerja, itulah sebabnya codelab sebelumnya menambahkan users.db ke .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)

Agen menjalankan python3 -m unittest discover -s tests dan mengonfirmasi bahwa pengujian baru gagal dengan 404 != 200 dan 404 != 400, karena rute belum ada. Itu adalah alasan yang tepat untuk gagal. Kegagalan yang disebabkan oleh error impor atau kesalahan ketik bukanlah MERAH; ini adalah pengujian yang rusak.

Fase C: Implementasi minimal (HIJAU)

Agen akan memuat defensive-developer dan menambahkan endpoint. Kueri ini menggunakan kueri berparameter karena CONTEXT.md menamakannya sebagai pola yang diperlukan untuk 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

Suite sekarang lulus: empat pengujian baru ditambah pengujian karakterisasi dari codelab sebelumnya.

Fase D: Melakukan refaktorisasi, memindai, melakukan commit

Agen memuat local-refactor-scanner, meninjau git diff, menjalankan ulang seluruh suite, dan melakukan 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. Gerbang Keamanan di git push

Push pertama ditolak

Agen ini berjalan git push origin main. Antigravity mencocokkan alat run_command dengan hooks.json dan menjalankan security_gate_hook.sh, yang mengenali push dan memindai file yang diubah: sample_app/data_service.py, tests/test_update_profile.py, dan threat_model.md (CodeMender melewati file .md; cm find di jalur tersebut tidak menemukan file sumber dan hook melanjutkan).

Proses ini memerlukan waktu beberapa menit. Hook ini bersifat sinkron, sehingga giliran agen tampak berhenti pada perintah push saat cm find dan cm report berjalan. Setelah pemindaian selesai, gate akan menemukan injeksi perintah di /ping yang tidak pernah diperbaiki oleh CodeMender CLI Basics. File tersebut adalah CRITICAL, OPEN, dan berada dalam file yang sedang di-push, sehingga hook menolak:

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", ...}

Push tidak berjalan. Agen melihat teks reason sebagai hasil dari panggilan alatnya, dan gate telah mencatat peristiwa DENIED_TO_AGENT untuk temuan ini.

Agen menghapus temuan test-first

Penolakan tersebut menyebutkan keterampilan yang akan digunakan, dan item aturan 6 (Protokol Remediasi Multi-Temuan) menyatakan untuk mengambil temuan satu per satu. Untuk temuan /ping, operasi yang representatif akan terlihat seperti ini.

Pengujian batas yang gagal di 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)

Perbaikan minimal, mengganti os.system dengan subprocess.run dalam bentuk daftar seperti yang diperlukan CONTEXT.md, ditambah parser daftar yang diizinkan, bukan pemeriksaan string:

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"

Agen menjalankan seluruh rangkaian, melakukan commit dengan pesan yang menjelaskan temuan dan perbaikan, lalu mengirimkannya lagi.

Push kedua diizinkan

Gate memindai ulang sample_app/data_service.py. Injeksi perintah hilang. Tidak ada lagi temuan pemblokiran, sehingga gate memeriksa lognya sendiri, melihat bahwa temuan yang ditolak sebelumnya tidak ada lagi, dan mencatat hasilnya sebelum mengizinkan 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 berjalan, dan remote kini memiliki tiga commit baru: fitur, perbaikan, dan commit docs(context) yang dibuat oleh hook itu sendiri.

7. Meninjau Jejak Audit

Semua yang diputuskan oleh gate ada di disk dalam teks biasa. Dari terminal di ~/codemender-lab:

Log gateway

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}

Setiap baris juga berisi actor (email git Anda) dan commit (SHA singkat pada saat itu), yang membuat pertanyaan "siapa yang mengizinkan ini dan kapan" dapat dijawab tanpa database.

Histori 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
...

Konteks yang berkembang

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.

Entri hook ringkas karena hanya mengetahui ID temuan, file, dan tingkat keparahan. Keterampilan skill-evolution-updater ada untuk mengubahnya menjadi aturan yang akan ditulis manusia. Tanyakan kepada agen: "Gunakan skill-evolution-updater untuk menulis ulang konvensi yang terakhir berkembang otomatis di CONTEXT.md sebagai aturan project, dan sarankan perubahan SKILL.md yang tersirat."

Tampilan CodeMender

cm report

Temuan injeksi perintah sekarang akan menampilkan status non-OPEN, dan threat_model.md serta dua file pengujian baru berada di repositori untuk fitur berikutnya yang akan dibangun.

8. Membersihkan

Pulihkan setelan default interaktif 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

Hapus status lokal CodeMender (ketik y di prompt):

cm clean

Untuk menghapus direktori lab juga:

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

Selamat! Anda telah menyelesaikan codelab Alur Kerja Keamanan Agentic End-to-End dengan CodeMender. Anda telah melihat CLI dari codelab pertama, keterampilan dan hook dari codelab kedua, serta agen yang bekerja melalui push yang diblokir, diperbaiki, dicatat ke dalam log, dan diizinkan tanpa perintah CodeMender dalam loop.