1. Введение
В этой практической работе вы объедините две предыдущие. Вы отправите Antigravity запрос на добавление функции, а затем посмотрите, как агент планирует работу с моделью угроз, пишет тесты, которые не проходят, реализует функцию и отправляет ее. При отправке изменений шлюз безопасности из предыдущей практической работы запускает CodeMender для проверки измененных файлов. Поскольку в файле, который редактирует агент, по-прежнему есть открытая уязвимость к внедрению команд, первая отправка блокируется. Вы увидите, как агент сначала устраняет проблему, а затем снова отправляет код. При этом CodeMender не запрашивает у пользователя никаких подсказок.
Что вы узнаете
- Как запустить CodeMender в фоновом режиме, чтобы ни хук, ни агент не блокировали подсказки
[Y/n]. - Что получается в результате цикла "Планирование → Красный → Зеленый → Рефакторинг", когда агент создает функцию:
threat_model.md, тесты, код и коммит. - Как шлюз безопасности реагирует, когда в отправленном файле есть открытые результаты, и как агент использует навыки
security-test-writerиdefensive-developer, чтобы их устранить. - Как читать журнал аудита, который оставляет за собой шлюз:
.security-gate/findings-log.ndjson, автоматически развивающиеся соглашения вCONTEXT.mdи история git.
Требования
- Рабочая область
~/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
В качестве типа VCS должно быть указано git, а в качестве команды сборки – python3 -m compileall -q ., как настроено в разделе "Основы CodeMender CLI".
Шаг 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 ее не видит. После этого агент будет отправлять push-уведомления.
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. Одобрите команды агента
Хук выполняется вне системы разрешений инструмента, но собственные команды агента – нет. Вы увидите запросы на доступ к python3 -m unittest ..., git add/commit/push и любой команде cm, которую решит выполнить Gemini. Одобрите их. Если 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, поскольку маршрут ещё не существует. Это правильная причина неудачи. Сбой, вызванный ошибкой импорта или опечаткой, не является КРАСНЫМ, это сломанный тест.
Этап C. Минимальная реализация (ЗЕЛЕНЫЙ)
Агент загружает 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
Первое 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", ...}
Push-уведомление не было отправлено. Агент видит текст 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)
Минимальное исправление: замена 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"
Агент запускает полный набор тестов, фиксирует изменения с сообщением, описывающим проблему и ее решение, и снова отправляет код.
Второе нажатие разрешено
Ворота повторно сканируют 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 в цикле.