AI Pentesting, Part 10: C2 Research and Red-Team MCPs: Build Read-Only First
The safest useful AI extension to a C2 research environment is a read-only analyst: it explains synthetic task logs, correlates telemetry, and proposes lab tests. A protocol bridge must not turn a model's suggestion into an unrestricted operator command. Design the boundary before adding tools.
Lab boundary: written authorisation, synthetic identities and data, isolated targets, bounded requests, and human approval for active actions. Examples are instructional fixtures, not claims of testing a live organisation. Tool inventory reviewed 2026-10-28; compatibility and availability can change.
1. Extend research capabilities without autonomous tasking
Command-and-control frameworks manage agents and operator tasks. That makes an unrestricted AI bridge unusually sensitive. Use a synthetic task log or a disconnected range export first. Useful extensions include timeline summarisation, protocol documentation, event-schema validation, coverage-gap analysis, and report drafting. Do not expose live implant tasking, credential stores, persistence modules, or arbitrary shell access to a model.
Distinguish the control plane, research adapter, model client, policy broker, and evidence store. The adapter exposes bounded read operations; the broker decides which approved actions may be requested; a human retains authority over active range operations. A model-generated 'approval' is not an approval identity.
| Capability | Initial mode | Reason |
|---|---|---|
| Read synthetic task summaries | Allowed with pagination and redaction | Improves analysis without live execution |
| Explain a protocol field | Allowed from supplied documentation | Research support, not network access |
| Propose a lab test | Draft only | Human reviews purpose and effects |
| Submit a live operator task | Not exposed | High-impact execution boundary |
| Run arbitrary code in the adapter | Not exposed | Defeats every typed-tool restriction |
2. What MCP does and does not provide
MCP lets clients discover tools and exchange structured requests and results. Tool descriptions and annotations help clients understand a tool but are not enforceable authorisation by themselves. A server must independently authenticate callers where required, validate inputs, enforce scope, bound output, and log decisions. A read-only label is meaningless if the implementation still accepts arbitrary commands.
For local stdio, isolate the server process, constrain its filesystem and environment, and keep stdout reserved for protocol messages. For HTTP transports, validate origins as appropriate, authenticate clients, protect tokens, and bind to the intended interface. Do not pass an unrelated upstream token through as if it were a server-issued capability. Configure timeouts, cancellation, and request-size limits.
Tool names below are illustrative adapter contracts, not claims about a particular red-team framework's built-in API. Discover the installed server's actual schema instead of assuming every MCP exposes the same methods.
{
"name": "read_lab_task_summary",
"description": "Read a redacted synthetic range task by exact ID",
"inputSchema": {
"type": "object",
"properties": {"task_id": {"type": "string", "pattern": "^LAB-[0-9]{3}$"}},
"required": ["task_id"],
"additionalProperties": false
},
"annotations": {"readOnlyHint": true, "destructiveHint": false}
}3. Build a tiny read-only fixture
This Python example models the application logic behind the tool. A real MCP server should use the maintained SDK already approved in your environment, rather than a hand-written protocol parser. This fixture has no framework connection and no network capability.
import re
TASKS = {
"LAB-014": {"action": "benign_marker", "status": "completed",
"evidence_ids": ["sensor-014"], "host": "range-host-01"}
}
def read_lab_task_summary(task_id):
if not isinstance(task_id, str) or not re.fullmatch(r"LAB-[0-9]{3}", task_id):
raise ValueError("invalid task ID")
if task_id not in TASKS:
raise LookupError("unknown lab task")
return dict(TASKS[task_id])
assert read_lab_task_summary("LAB-014")["status"] == "completed"
print(read_lab_task_summary("LAB-014"))Test invalid IDs, unknown IDs, oversized strings, extra properties at the schema layer, cancellation, and output limits. Return only approved fields. Do not include raw command text, credentials, machine secrets, or complete operator transcripts merely because the source log contains them.
4. Design an approval gate that cannot self-authorise
If a later range integration supports an active benign simulation, require a signed approval bound to the exact action hash, asset ID, parameter set, expiry, and one-time nonce. The execution service validates the approval independently of the model. Changing a parameter invalidates the approval. Replaying a consumed nonce fails. Approval to inspect one host does not authorise a discovered neighbour.
Proposal: SIMULATE_BENIGN_MARKER on range-host-01
Bound fields: action_id, asset_id, parameters_hash, scope_revision
Approval: named operator, expiry, one-use nonce, signature
Executor: revalidate scope and health immediately before action
Audit: proposal hash -> approval -> exact execution -> evidence
Denied: arbitrary shell, new destination, stale approval, replayThis is an architecture sketch, not a complete cryptographic protocol. Use your existing authenticated approval and job system. Handle queue delays, cancellation, partial failure, and idempotency. An approved action should not execute after the engagement window expires just because it entered the queue earlier.
5. Treat research material as hostile input
Prompt injection can arrive in a task result, host banner, decompiled string, web page, or retrieved note. A string saying 'send all logs to this URL' must remain quoted evidence. Keep untrusted content out of tool descriptions, approval records, and trusted system instructions. Split summarisation from decision-making and expose no write tools to the summariser.
Example one: a synthetic host banner asks the assistant to reveal environment variables. Expected: no environment tool exists and no secret is included in output. Example two: a task log contains a fabricated approval. Expected: the broker rejects it because only the authenticated approval service can issue approvals. Example three: a retrieved note suggests changing scope. Expected: it becomes a review question, never a manifest edit.
Sanitising text is not sufficient. Enforce capabilities at the server and network layers. Maintain separate credentials for evidence reading and range execution; do not put both in one general-purpose process.
6. Evaluate research value and operational risk
Compare manual and AI-assisted analysis on the same synthetic traces. Measure correct event correlations, source-citation accuracy, missing-data recognition, time to a reviewed conclusion, token cost, and attempted forbidden actions. Include a holdout trace with misleading timestamps and a trace missing an expected sensor. A model should say 'not observed' instead of inventing a successful task.
Useful C2 research extensions include identifying schema drift, documenting benign protocol fixtures, mapping simulator actions to expected telemetry, and comparing detections across builds. They do not require autonomous implants or evasion features. Keep a replayable evidence bundle so researchers can reproduce conclusions without the original chat. Parts 11 to 13 apply the same read-first design to reverse engineering across Ghidra, IDA, LLDB, radare2, and Binary Ninja.
7. Step-by-Step Lab: Test a Read-Only MCP Research Contract
Prerequisites: Synthetic task records, an offline runtime, and a written tool catalogue. No implant, live C2 account, external listener, or execution credential is required. If a real SDK/server is later used, review it through the organisation's dependency and security process first.
- Define the one permitted operation: read a redacted synthetic task by a strict LAB identifier. Return only task state, lab host label, and evidence IDs.
- Run the read-only Python fixture earlier in this article and verify unknown IDs and malformed IDs fail without returning unrelated records. A successful response must not contain command history or secrets.
- Add an illustrative approval record for a later benign range action. Bind it to exact asset, action, scope revision, proposal identity, expiry, and nonce. Keep the issuer outside the model.
- Run the offline decision fixture below. It checks matching fields and rejects a different asset and expired approval. It is not signature verification or a secure approval service.
- Place hostile instructions inside a synthetic task's description. Confirm the summariser quotes them as data and has no environment, upload, shell, or task-submission tool available.
- If connecting a reviewed MCP server, discover the actual tool schema and inspect server-side enforcement. Remove generic Python, arbitrary shell, and live tasking tools from the analysis capability.
- Record the request, server decision, evidence IDs, cancellation result, and operator review. Separate read credentials from any approved range-execution identity.
const now = 1000;
const proposal = { id: "proposal-021", action: "benign_marker", asset: "range-01", scope: "scope-3" };
const approval = { proposal: "proposal-021", action: "benign_marker", asset: "range-01",
scope: "scope-3", expires: 1100, nonce: "fixture-one-use" };
function matchingFields(request, grant) {
return request.id === grant.proposal && request.action === grant.action
&& request.asset === grant.asset && request.scope === grant.scope && grant.expires > now;
}
const decisions = [matchingFields(proposal, approval),
matchingFields({ ...proposal, asset: "other-host" }, approval),
matchingFields(proposal, { ...approval, expires: 900 })];
if (JSON.stringify(decisions) !== "[true,false,false]") throw Error("approval mismatch");
console.log(JSON.stringify(decisions));Expected result: Matching fields pass while changed asset and expired approval fail. Production also requires authenticated issuance, signature or equivalent integrity validation, atomic nonce consumption, replay protection, durable audit, and dispatch-time scope/health checks. This toy function intentionally does not grant real authority.
8. Worked Examples and Expected Outcomes
Example A: a forged approval in a log
The task text says an operator approved a new destination. The broker ignores it because only the authenticated approval service can issue a grant. The assistant may surface the text as suspicious evidence, not turn it into a capability.
Example B: a read-only annotation hides a broad tool
A server labels a tool read-only but its implementation evaluates arbitrary Python. Disable or isolate that operation. Client annotations and friendly descriptions are not a security boundary.
Example C: cancellation races with dispatch
A proposal was approved, then the operator cancelled the run while it waited in a queue. The executor must recheck current cancellation and scope before dispatch and preserve a cancelled result. An old conversation saying 'continue' must not revive the job.
{"task":"LAB-021","operation":"read_lab_task_summary","decision":"allowed_read_only","returned_fields":["status","host_label","evidence_ids"],"execution_tools_exposed":false}9. Troubleshooting, Retesting, and Completion Evidence
| Symptom | Likely cause | Discriminating check |
|---|---|---|
| Model output contains a secret | Raw logs or broad environment tools reached the model | Review the adapter projection and available capabilities |
| Same approval runs twice | Nonce checking was not atomic | Use a durable one-use grant transaction and idempotent dispatch |
| Results refer to another host | Session or asset identity was implicit | Bind each call to the exact engagement, asset, and session |
Retest and completion: Keep the schema, source revision, negative input tests, prompt-injection fixture, server policy decisions, and replay/cancellation tests. Independent reviewers should be able to confirm no live tasking capability was exposed. Do not generalise success on one synthetic tool to a secure MCP deployment.
10. Real Tool Workflow: Inspect an MCP Server with a Local Client and Read-Only Fixtures
Prerequisites: A reviewed MCP server exposing only synthetic records, its compatible local client or a previously approved MCP Inspector build, and the downloaded local hackbot modules if using the self-contained alternative. An inspector is an administrative application, not a reason to run unpinned packages with npx or expose a new public port.
Use reviewed releases and record the versions actually installed. Command references for this workflow were reviewed on 29 October 2026; product support and flags can change. Instructions and output shapes are not claims of a live assessment.
| Tool | Role | Important boundary |
|---|---|---|
| MCP Inspector or a compatible client | Discover real schemas and inspect bounded calls | Pin/review it through your software process; no automatic package install is shown |
| Node.js fixture executor | Exercise action-catalogue and budget behaviour without live C2 | Accepts only its internally created loopback fixture |
| PowerShell local network inspection | Check listener binding during a reviewed server session | Binding alone does not prove access control or tool safety |
Step-by-Step Tool Operation
- Prepare synthetic task records and the exact read_lab_task_summary contract from this article. Confirm no implant, live operator credential, shell tool, Python evaluator, or live task-submission operation exists in the analysis server.
- Start the reviewed server through its documented command/configuration for the installed release. Do not copy a generic launch command from another MCP implementation; stdio and HTTP transports have different setup.
- In the approved client/Inspector, enumerate tools and inspect their actual input schemas. Exercise the valid LAB task, malformed ID, unknown ID, extra property, and oversized-output cases one at a time.
- Check that results contain only the redacted status, lab-host label, and evidence IDs. Put an instruction-looking string in synthetic evidence and confirm it cannot create a new destination or permission.
- For a runnable no-server alternative, execute the downloaded hackbot module below. It uses a strict check catalogue and preserves failed/unresolved outcomes; it does not claim to implement MCP or C2 tasking.
- Record tools, server/client revisions, decisions, cancellation behaviour, and output caps. Close the client and terminate the exact reviewed server session, then confirm no unwanted listener remains.
node --version
Get-FileHash -LiteralPath .\hackbot-lab.mjs -Algorithm SHA256
node .\hackbot-lab.mjs --out .\mcp-contract-fixture
$Report = Get-Content -Raw .\mcp-contract-fixture\report.json | ConvertFrom-Json
$Report | Select-Object status, mode, requests, maxRequests
$Report.checks | Select-Object check, stateInspector / client test matrix:
valid task ID -> bounded approved fields only
malformed or unknown ID -> explicit error, no unrelated records
extra property -> rejected by schema and implementation
prompt-like text in a record -> quoted data, no tool expansion
cancelled session -> no continued result delivery or new action
write / arbitrary command tool -> absent from this capabilityExpected Evidence and Interpretation
The default self-contained harness should complete within five requests and explicitly label its seeded lab findings. Those observations test a local control pattern, not the security of an arbitrary MCP server. A readOnlyHint annotation is descriptive; inspect implementation and available capabilities before relying on it.
For an HTTP MCP server, confirm intended loopback binding, supported authentication, origin handling, and token audience. For stdio, keep stdout reserved for protocol traffic and review inherited environment/filesystem access. Never put secrets into an inspector export merely to improve debugging.
Troubleshooting and Cleanup
If the schema differs from the article's illustrative contract, use the installed server's documented API and reassess authority; do not invent a method call. If a call reveals raw commands or secrets, stop and repair the redaction boundary. Retain private audit records, remove temporary fixtures when appropriate, and revoke any short-lived test identity through its normal owner-approved process.