CodeMender-এর মাধ্যমে এন্ড-টু-এন্ড এজেন্টিক নিরাপত্তা ওয়ার্কফ্লো

১. ভূমিকা

এই কোডল্যাবে আপনি আগের দুটি কোডল্যাব একসাথে ব্যবহার করবেন। আপনি Antigravity-কে একটি ফিচার রিকোয়েস্ট দেবেন, তারপর এটি থ্রেট মডেলের সাথে কীভাবে পরিকল্পনা করে, ব্যর্থ টেস্ট লেখে, ফিচারটি প্রয়োগ করে এবং পুশ করে তা দেখবেন। পুশ করার সময়, আগের কোডল্যাবের নিরাপত্তা গেট পরিবর্তিত ফাইলের উপর CodeMender রান করে। যেহেতু স্যাম্পেল অ্যাপ্লিকেশনে এখনও একটি ওপেন কমান্ড ইনজেকশন রয়েছে, তাই এজেন্ট ফাইলটি এডিট করছে, প্রথম পুশ ব্লক হয়ে যায় এবং আপনি দেখতে পাবেন যে এজেন্ট ফাইন্ডিং টেস্ট-ফার্স্ট মুছে দিচ্ছে এবং আবার পুশ করছে, এই প্রসেসে CodeMender থেকে কোনও হিউম্যান প্রম্পট ছাড়াই।

আপনি কী শিখবেন

  • কীভাবে CodeMender হেডলেসভাবে রান করা যায় যাতে হুক বা এজেন্ট কোনওটিই [Y/n] প্রম্পটে ব্লক না করে।
  • এজেন্ট কোনও ফিচার তৈরি করার সময় প্ল্যান → লাল → সবুজ → রিফ্যাক্টর লুপ কী তৈরি করে: threat_model.md, টেস্ট, কোড এবং একটি কমিট।
  • পুশ করা ফাইলে ওপেন ফাইন্ডিং থাকলে সিকিউরিটি গেট কীভাবে রেসপন্স করে এবং এজেন্ট কীভাবে security-test-writer ও defensive-developer স্কিল ব্যবহার করে সেগুলি সমাধান করে।
  • গেট যে অডিট ট্রেল রেখে যায় তা কীভাবে পড়তে হয়: .security-gate/findings-log.ndjson, CONTEXT.md-এ অটো-ইভলভড কনভেনশন এবং গিট ইতিহাস।

পূর্বশর্ত

  • আগের দুটি কোডল্যাব থেকে ~/codemender-lab ওয়ার্কস্পেস, main ব্রাঞ্চে একটি ক্লিন গিট ওয়ার্কিং ট্রি সহ।
  • Antigravity ইনস্টল করা হয়েছে, ~/codemender-lab ফোল্ডার হিসেবে খোলার জন্য উপলভ্য।
  • git, jq এবং Flask ইনস্টল করা Python ভার্চুয়াল এনভায়রনমেন্ট।

২. আগের কোডল্যাব থেকে সেট-আপ যাচাই করা

ধাপ ১: ল্যাব ডিরেক্টরিতে ফিরে যান

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

ধাপ ২: ফ্রেমওয়ার্ক ফাইল ঠিক জায়গায় আছে কিনা তা কনফার্ম করুন

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

ধাপ ৩: CodeMender এনভায়রনমেন্ট কনফার্ম করা

cm init --verify

CodeMender CLI Basics-এ কনফিগার করা অনুযায়ী, এটি VCS টাইপ হিসেবে git এবং বিল্ড কমান্ড হিসেবে python3 -m compileall -q . রিপোর্ট করবে।

ধাপ ৪: পুরনো ফলাফল মুছে ফেলা

cm clean

প্রম্পট করা হলে y টাইপ করুন। এটি ~/.codemender/state.db খালি করে।

ধাপ ৫: পুশ করার জন্য রিমোট তৈরি করা

হুক আপনার ওয়ার্কিং ট্রিকে origin/main-এর সাথে তুলনা করে এবং git push-এর কোথাও যাওয়ার প্রয়োজন হয়। একই মেশিনে একটি বেয়ার রিপোজিটরিই যথেষ্ট:

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

এই পুশ আপনার নিজের টার্মিনালে হয়, তাই Antigravity হুক এটি দেখতে পায় না। এখান থেকে, এজেন্ট পুশ করে।

৩. CodeMender Headless রান করা হচ্ছে

ধাপ ১: কনফার্মেশন প্রম্পট বন্ধ করা

সাধারণত, 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

ধাপ ২: যাচাই করুন

cm init --verify

৪. ফিচার রিকোয়েস্ট

ধাপ ১: ওয়ার্কস্পেস খুলুন

Antigravity-তে ফোল্ডার হিসেবে ~/codemender-lab খুলুন এবং নতুন কথোপকথন শুরু করুন। সবসময় চালু থাকা secure_tdd_workflow নিয়ম এখন প্রতিটি টার্নের অংশ এবং ছয়টি স্কিল উপলভ্য।

ধাপ ২: এজেন্টকে টাস্ক দিন

নিম্নলিখিত প্রম্পট পেস্ট করুন। লক্ষ্য করুন যে এটি নিরাপত্তা সম্পর্কে কিছুই বলে না। এজেন্টকে থ্রেট মডেলিং, টেস্ট-ফার্স্ট ডেভেলপমেন্ট ও ডিফেন্সিভ প্যাটার্নের দিকে চালিত করা হল নিয়ম ও দক্ষতার কাজ, প্রম্পটের নয়:

sample_app/data_service.py-এ একটি POST /update_profile এন্ডপয়েন্ট যোগ করুন যা ব্যবহারকারীকে তার ইমেল আইডি আপডেট করতে দেয়। এটি ফর্ম ফিল্ড হিসেবে user_id এবং email গ্রহণ করে। পরিবর্তন সম্পূর্ণ হলে এবং টেস্ট স্যুট পাস করলে, এটি কমিট করুন এবং origin main-এ পুশ করুন।

ধাপ ৩: এজেন্টের কমান্ড অনুমোদন করা

হুক টুল-অনুমতি সিস্টেমের বাইরে চলে, কিন্তু এজেন্টের নিজস্ব কমান্ড তা করে না। python3 -m unittest ..., git add/commit/push এবং এটি রান করার সিদ্ধান্ত নেয় এমন যেকোনও cm কমান্ডের জন্য প্রম্পট আশা করুন। সেগুলি অনুমোদন করুন; Antigravity যদি সিদ্ধান্ত মনে রাখার অফার দেয়, তাহলে সেটি গ্রহণ করুন যাতে এই কোডল্যাবের পরবর্তী ফিক্স লুপ বারবার প্রম্পট দেখানোর কারণে আটকে না যায়।

৫. এজেন্ট কী করে: প্ল্যান, লাল, সবুজ, রিফ্যাক্টর

এর পরে একটি প্রতিনিধিত্বমূলক রান দেখানো হয়েছে। আপনার এজেন্টের শব্দ, ফাইলের নাম এবং সঠিক কোড আলাদা হবে এবং এটি ঠিক আছে। যেটি আলাদা হওয়া উচিত নয় তা হল ক্রম: টেস্টের আগে থ্রেট মডেল, কোডের আগে টেস্টে সমস্যা, কমিটের আগে গ্রিন স্যুট।

ফেজ A: প্ল্যান ও থ্রেট মডেল

এজেন্ট ওয়ার্কস্পেস রুটে 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-এর সাথে ব্যর্থ হয়েছে, কারণ রুট এখনও নেই। এটিই হল ব্যর্থ হওয়ার সঠিক কারণ। ইমপোর্ট সংক্রান্ত সমস্যা বা টাইপোর কারণে ব্যর্থতা RED নয়; এটি একটি ব্রোকেন টেস্ট।

ফেজ 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

স্যুটটি এখন পাস করে: চারটি নতুন টেস্ট এবং আগের কোডল্যাব থেকে ক্যারেক্টারাইজেশন টেস্ট।

ফেজ D: রিফ্যাক্টর, স্ক্যান, কমিট

এজেন্ট 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"

৬. git push -এ সিকিউরিটি গেট

প্রথম পুশ বাতিল করা হয়েছে

এজেন্ট git push origin main রান করে। Antigravity, hooks.json-এর সাথে run_command টুল ম্যাচ করে এবং security_gate_hook.sh রান করে, যা পুশ শনাক্ত করে এবং পরিবর্তিত ফাইল স্ক্যান করে: sample_app/data_service.py, tests/test_update_profile.py এবং threat_model.md (CodeMender, .md ফাইল স্কিপ করে; ওই পাথে cm find কোনও সোর্স ফাইল খুঁজে পায় না এবং হুক এগিয়ে যায়)।

এতে কয়েক মিনিট সময় লাগে। হুকটি সিঙ্ক্রোনাস, তাই 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 ইভেন্ট লগ করেছে।

এজেন্ট প্রথমে ফাইন্ডিং টেস্ট পাস করে

প্রত্যাখ্যানের কারণ হিসেবে ব্যবহার করার জন্য দক্ষতার নাম উল্লেখ করা হয়েছে এবং নিয়ম আইটেম ৬ (মাল্টি-ফাইন্ডিং রেমিডিয়েশন প্রোটোকল) অনুযায়ী একবারে একটি ফাইন্ডিং নিতে হবে। /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)

ন্যূনতম সমাধান, CONTEXT.md-এর প্রয়োজন অনুযায়ী os.system-কে তালিকা-ফর্ম subprocess.run দিয়ে পরিবর্তন করা, সাথে স্ট্রিং চেকের পরিবর্তে অনুমোদিত-তালিকা পার্সার:

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) কমিট করেছে।

৭. অডিট ট্রেল পর্যালোচনা করা

গেট যা সিদ্ধান্ত নিয়েছে তার সবকিছুই প্লেন টেক্সট হিসেবে ডিস্কে রয়েছে। ~/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 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
...

The evolved context

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 স্কিলটি মানুষের লেখার মতো করে নিয়মটি তৈরি করার জন্য রয়েছে। এজেন্টকে বলুন: "CONTEXT.md-এ শেষ অটো-ইভলভড কনভেনশনকে প্রোজেক্ট রুল হিসেবে আবার লিখতে skill-evolution-updater ব্যবহার করো এবং এর ফলে SKILL.md-এ কোনও পরিবর্তন করতে হলে তা সাজেস্ট করো।"

CodeMender-এর ভিউ

cm report

কমান্ড ইনজেকশন ফাইন্ডিং এখন OPEN স্ট্যাটাস দেখাবে না এবং threat_model.md সহ দুটি নতুন টেস্ট ফাইল পরবর্তী ফিচার তৈরি করার জন্য রিপোজিটরিতে রয়েছে।

৮. ক্লিন-আপ করা হচ্ছে

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 প্রম্পট ছাড়াই ব্লক, ফিক্স, লগ এবং অনুমোদিত পুশের মাধ্যমে কাজ করা এজেন্ট দেখেছেন।