1. Giới thiệu
Trong lớp học lập trình này, bạn sẽ kết hợp hai lớp học lập trình trước đó. Bạn sẽ gửi cho Antigravity một yêu cầu về tính năng, sau đó xem công cụ này lập kế hoạch bằng mô hình mối đe doạ, viết các chương trình kiểm thử không thành công, triển khai tính năng và đẩy mã. Khi bạn đẩy mã, cổng bảo mật từ lớp học lập trình trước sẽ chạy CodeMender trên các tệp đã thay đổi. Vì ứng dụng mẫu vẫn có một lỗ hổng chèn lệnh mở trong tệp mà tác nhân đang chỉnh sửa, nên lần đẩy đầu tiên sẽ bị chặn và bạn sẽ thấy tác nhân xoá kết quả kiểm thử trước rồi đẩy lại mà không cần CodeMender nhắc nhở trong suốt quá trình.
Kiến thức bạn sẽ học được
- Cách chạy CodeMender ở chế độ không có giao diện người dùng để cả hook và tác nhân đều không chặn lời nhắc
[Y/n]. - Những gì vòng lặp Lập kế hoạch → Đỏ → Xanh lục → Cải tiến tạo ra khi tác nhân xây dựng một tính năng:
threat_model.md, kiểm thử, mã và một thay đổi. - Cách cổng bảo mật phản hồi khi một tệp được đẩy có kết quả mở và cách tác nhân sử dụng các kỹ năng
security-test-writervàdefensive-developerđể xoá các kết quả đó. - Cách đọc nhật ký kiểm tra mà cổng để lại:
.security-gate/findings-log.ndjson, quy ước tự động phát triển trongCONTEXT.mdvà nhật ký git.
Điều kiện tiên quyết
- Không gian làm việc
~/codemender-labtừ hai lớp học lập trình trước, với cây làm việc git sạch trên nhánhmain. - Đã cài đặt Antigravity, có thể mở
~/codemender-labdưới dạng thư mục. git,jqvà môi trường ảo Python đã cài đặt Flask.
2. Xác minh chế độ thiết lập từ các lớp học lập trình trước
Bước 1: Quay lại thư mục của phòng thí nghiệm
cd ~/codemender-lab && source .venv/bin/activate
Bước 2: Xác nhận các tệp khung đã có sẵn
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
Bạn sẽ thấy 6 thư mục kỹ năng (defensive-developer, history-context-seeder, local-refactor-scanner, security-test-writer, skill-evolution-updater, threat-model-assessor), tests/__init__.py và tests/test_data_service.py, một git status trống và main.
Chạy kiểm thử hook ngoại tuyến thêm một lần nữa để xác nhận không có gì bị lệch:
bash .agents/tests/run_tests.sh | tail -1
60 passed, 0 failed
Bước 3: Xác nhận môi trường CodeMender
cm init --verify
Công cụ này sẽ báo cáo git là loại VCS và python3 -m compileall -q . là lệnh bản dựng, như đã định cấu hình trong phần Kiến thức cơ bản về CodeMender CLI.
Bước 4: Xoá các kết quả cũ
cm clean
Nhập y khi được nhắc. Thao tác này sẽ dọn sạch ~/.codemender/state.db.
Bước 5: Tạo một kho lưu trữ từ xa để đẩy lên
Hook so sánh cây làm việc của bạn với origin/main và git push cần một nơi để lưu trữ. Bạn chỉ cần một kho lưu trữ trống trên cùng một máy:
git init --bare ~/codemender-remote.git
git remote add origin ~/codemender-remote.git
git push -u origin main
Thao tác đẩy này diễn ra trong thiết bị đầu cuối của riêng bạn, vì vậy, hook Antigravity không thấy thao tác này. Từ đây, tác nhân sẽ thực hiện các thao tác đẩy.
3. Chạy CodeMender không có giao diện người dùng
Bước 1: Tắt lời nhắc xác nhận
Theo mặc định, CodeMender sẽ hỏi [Y/n] trước mỗi lệnh mà công cụ này chạy và mỗi tệp mà công cụ này ghi. Bên trong một hook, không có ai trả lời. Mở ~/.codemender/config.yaml và đặt cả hai cờ tools thành false hoặc chạy:
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
Mục kết quả sẽ có dạng như sau:
tools:
confirm_commands: false
confirm_writes: false
Bước 2: Xác minh
cm init --verify
4. Yêu cầu về tính năng
Bước 1: Mở không gian làm việc
Mở ~/codemender-lab dưới dạng một thư mục trong Antigravity và bắt đầu một cuộc trò chuyện mới. Quy tắc secure_tdd_workflow luôn bật hiện là một phần của mỗi lượt và sáu kỹ năng đã có sẵn.
Bước 2: Giao nhiệm vụ cho tác nhân
Dán câu lệnh sau. Lưu ý rằng chính sách này không đề cập đến vấn đề bảo mật. Việc hướng tác nhân đến mô hình hoá mối đe doạ, phát triển ưu tiên kiểm thử và các mẫu phòng thủ là nhiệm vụ của quy tắc và kỹ năng, chứ không phải câu lệnh:
Thêm một điểm cuối POST /update_profile vào sample_app/data_service.py để cho phép người dùng cập nhật địa chỉ email của họ. Thành phần này chấp nhận user_id và email làm trường biểu mẫu. Khi thay đổi hoàn tất và bộ kiểm thử đạt, hãy cam kết và đẩy thay đổi đó lên origin main.
Bước 3: Phê duyệt lệnh của nhân viên hỗ trợ
Hook chạy bên ngoài hệ thống quyền của công cụ, nhưng các lệnh của tác nhân thì không. Bạn sẽ thấy các câu lệnh cho python3 -m unittest ..., git add/commit/push và mọi lệnh cm mà Gemini quyết định chạy. Phê duyệt các quyền đó; nếu Antigravity đề nghị ghi nhớ quyết định, hãy chấp nhận để vòng lặp khắc phục sau này trong lớp học lập trình này không bị đình trệ do các lời nhắc lặp lại.
5. Việc mà tác nhân làm: Lập kế hoạch, Đỏ, Xanh lục, Cải tiến
Sau đây là một lần chạy tiêu biểu. Cách diễn đạt, tên tệp và mã chính xác của tác nhân sẽ khác nhau, nhưng không sao cả. Thứ tự không được thay đổi: mô hình mối đe doạ trước khi kiểm thử, kiểm thử không thành công trước khi viết mã, bộ kiểm thử thành công trước khi cam kết.
Giai đoạn A: Lập kế hoạch và mô hình hoá mối đe doạ
Tác nhân đọc CONTEXT.md, tải threat-model-assessor và ghi threat_model.md tại thư mục gốc của không gian làm việc:
# 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.
Giai đoạn B: Kiểm thử không thành công (ĐỎ)
Tác nhân tải security-test-writer và tạo tests/test_update_profile.py. Một phiên bản thông thường sử dụng users.db dùng một lần trong thư mục đang hoạt động, đó là lý do lớp học lập trình trước đó đã thêm users.db vào .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)
Tác nhân chạy python3 -m unittest discover -s tests và xác nhận các kiểm thử mới không thành công với 404 != 200 và 404 != 400, vì tuyến đường chưa tồn tại. Đó là lý do chính đáng để thất bại. Lỗi do lỗi nhập hoặc lỗi đánh máy không phải là ĐỎ; đó là một kiểm thử bị hỏng.
Giai đoạn C: Triển khai tối thiểu (XANH LÁ)
Tác nhân tải defensive-developer và thêm điểm cuối. Hàm này sử dụng truy vấn có tham số vì CONTEXT.md đặt tên đó làm mẫu bắt buộc cho 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
Giờ đây, bộ kiểm thử đã thành công: 4 kiểm thử mới cộng với kiểm thử đặc tính từ lớp học lập trình trước đó.
Giai đoạn D: Tái cấu trúc, quét, cam kết
Tác nhân tải local-refactor-scanner, xem xét git diff, chạy lại toàn bộ bộ kiểm thử và cam kết:
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. Cổng bảo mật trên git push
Lần đẩy đầu tiên bị từ chối
Tác nhân chạy git push origin main. Antigravity so khớp công cụ run_command với hooks.json và chạy security_gate_hook.sh. Công cụ này nhận ra thao tác đẩy và quét các tệp đã thay đổi: sample_app/data_service.py, tests/test_update_profile.py và threat_model.md (CodeMender bỏ qua các tệp .md; cm find trên đường dẫn đó không tìm thấy tệp nguồn và hook tiếp tục).
Quá trình này sẽ mất một vài phút. Hook này đồng bộ, vì vậy, lượt của tác nhân có vẻ như bị treo ở lệnh đẩy trong khi cm find và cm report chạy. Khi quá trình quét kết thúc, cổng sẽ tìm thấy lỗi chèn lệnh trong /ping mà CodeMender CLI Basics chưa từng khắc phục. Đó là CRITICAL, OPEN và nằm trong một tệp đang được đẩy, vì vậy, hook sẽ từ chối:
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", ...}
Thao tác đẩy không chạy. Tác nhân thấy văn bản reason là kết quả của lệnh gọi công cụ và cổng đã ghi lại một sự kiện DENIED_TO_AGENT cho kết quả này.
Tác nhân sẽ xoá kết quả kiểm thử trước
Trong thông báo từ chối, hãy nêu tên các kỹ năng cần sử dụng và mục quy tắc 6 (Giao thức khắc phục nhiều kết quả) yêu cầu xử lý từng kết quả một. Đối với kết quả tìm được của /ping, một lần chạy đại diện sẽ có dạng như sau.
Một chương trình kiểm thử ranh giới không thành công trong 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)
Cách khắc phục tối thiểu, thay thế os.system bằng subprocess.run dạng danh sách theo yêu cầu của CONTEXT.md, cộng với trình phân tích cú pháp danh sách cho phép thay vì kiểm tra chuỗi:
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"
Tác nhân chạy toàn bộ bộ kiểm thử, cam kết bằng một thông báo mô tả kết quả tìm được và cách khắc phục, rồi đẩy lại.
Lần đẩy thứ hai được cho phép
Cổng quét lại sample_app/data_service.py. Lỗi chèn lệnh đã được khắc phục. Không còn kết quả chặn nào, vì vậy, cổng kiểm tra nhật ký của chính nó, thấy rằng kết quả mà nó từ chối trước đó không còn xuất hiện và ghi lại kết quả trước khi cho phép đẩy:
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 chạy và kho lưu trữ từ xa hiện có 3 thay đổi mới: tính năng, bản sửa lỗi và một thay đổi docs(context) do chính hook thực hiện.
7. Xem xét biên bản kiểm tra
Mọi quyết định của cổng đều được lưu trên ổ đĩa dưới dạng văn bản thuần tuý. Từ một thiết bị đầu cuối trong ~/codemender-lab:
Nhật ký cổng
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}
Mỗi dòng cũng chứa actor (email git của bạn) và commit (SHA ngắn tại thời điểm đó), giúp trả lời câu hỏi "ai đã cho phép điều này và khi nào" mà không cần cơ sở dữ liệu.
Nhật ký 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
...
Bối cảnh phát triển
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.
Mục nhập của hook rất ngắn gọn vì nó chỉ biết mã nhận dạng, tệp và mức độ nghiêm trọng của kết quả. Kỹ năng skill-evolution-updater có nhiệm vụ biến điều này thành một quy tắc mà con người có thể viết. Yêu cầu tác nhân: "Sử dụng skill-evolution-updater để viết lại quy ước tự động phát triển gần đây nhất trong CONTEXT.md dưới dạng quy tắc dự án và đề xuất mọi thay đổi về SKILL.md mà quy ước đó ngụ ý."
Chế độ xem của CodeMender
cm report
Giờ đây, kết quả tìm thấy về việc chèn lệnh sẽ cho thấy trạng thái không phải là OPEN, đồng thời threat_model.md và hai tệp kiểm thử mới sẽ nằm trong kho lưu trữ để tính năng tiếp theo dựa vào đó.
8. Dọn dẹp
Khôi phục chế độ mặc định có tính tương tác của 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
Xoá trạng thái cục bộ của CodeMender (nhập y tại câu lệnh):
cm clean
Cách xoá cả thư mục phòng thí nghiệm:
cd ~ && rm -rf ~/codemender-lab ~/codemender-remote.git
Xin chúc mừng! Bạn đã hoàn thành lớp học lập trình Quy trình bảo mật toàn diện dựa trên tác nhân bằng CodeMender. Bạn đã thấy CLI trong lớp học lập trình đầu tiên, các kỹ năng và hook trong lớp học lập trình thứ hai, cũng như một tác nhân xử lý một lệnh đẩy bị chặn, được khắc phục, ghi nhật ký và cho phép mà không cần lời nhắc CodeMender trong vòng lặp.