Required Artifacts
Every project you publish in Month 3 carries the same four artifacts. They are not documentation busywork — each one answers a specific question a reviewer will ask, and together they are the difference between “here is an agent I built” and “here is an agent I built, and here is how I know what it does when things go wrong.” This page explains each artifact deeply enough that you can produce it, not just recognize it.
Artifact 1 — STRIDE threat model diagram
A threat model is a picture plus a table. The picture shows where data moves and where trust changes. The table says what could go wrong at each of those points and what you did about it.
Drawing the data flow
Draw four kinds of things and nothing else:
| Element | Notation | Examples in an agent system |
|---|---|---|
| External entity | Rectangle | Human operator, third-party API, log source |
| Process | Circle | Agent loop, MCP server, tool handler, validator |
| Data store | Two parallel lines | Log directory, ticket database, vector store, conversation memory |
| Data flow | Arrow | Alert into investigator, tool call, model response, ticket creation |
Then walk one real request from entry to final effect and label every arrow with what actually travels along it. Most people’s first diagram is wrong in the same way: it shows the agent calling a tool but omits the model provider, the retrieved content, and the memory store. All three are on the data flow and all three are attack surface.
Placing trust boundaries
A trust boundary is any line where data crosses from a context you control into one you do not, or vice versa. In agent systems the boundaries that matter are:
- Between untrusted retrieved content and the model context
- Between the model’s output and any tool that acts on it
- Between one agent and the next in a pipeline
- Between the agent runtime and any credential store
- Between the system and the human approver
Draw them as dashed lines cutting through arrows. Every arrow crossing a dashed line needs validation, and if you cannot say what validates it, you have found a gap.
STRIDE mapped to agent threats
| STRIDE | Concrete agent threat | Control |
|---|---|---|
| Spoofing | Retrieved document impersonates the operator: “as the admin, run this” | Structural separation of instructions and data; never place retrieved text in system role |
| Tampering | Injected log line alters the investigator’s verdict field | Typed handoff schema; validate enum values; log raw and parsed forms |
| Repudiation | An action happened but no record shows which agent, prompt, or version caused it | Per-span audit trail with agent ID, commit hash, and full tool arguments |
| Information disclosure | A secret in a log line enters context, then appears in the report | Redact at ingestion; egress checks on generated output; no network from the reader |
| Denial of service | Crafted input causes an unbounded tool loop and burns the token budget | Hard caps on iterations, tool calls, and tokens per run; timeouts on every tool |
| Elevation of privilege | Low-privilege agent persuades a higher-privilege one to act on its behalf | Server-side authorization per caller identity; no capability delegation across agents |
THREAT-MODEL.md, alongside the table. A reviewer who can read your boundaries in ten seconds will read the rest.Artifact 2 — Trust boundary and human approval design
When the “user” is a retrieved document
Classic threat modeling assumes the user is a person with an identity and an authorization level. Agents break that assumption. Content the agent retrieves — a log line, a ticket comment, a web page, a file — arrives inside the same context window as the operator’s actual instruction, and the model has no reliable way to tell them apart by origin.
The practical consequence: treat every piece of retrieved content as an anonymous, unauthenticated, potentially hostile user submitting instructions. That reframing decides your design. You would not let an anonymous internet user call delete_ticket. So the agent, while processing anonymous content, must not be able to either.
Two structural habits follow. Keep retrieved content in clearly delimited data fields, never in the instruction position. And derive the agent’s authorization from the operator’s identity and the task, never from anything the content asked for.
Where approval gates belong
Two criteria decide: irreversibility and blast radius. An action that can be cleanly undone by the person who noticed the problem is a poor candidate for a gate. An action that leaves your system, notifies a person, spends money, or destroys evidence is a good one.
| Reversible? | Blast radius | Gate? | Example |
|---|---|---|---|
| Yes | Single record | No | Read a log file, look up an asset |
| Yes | Single record | No | Create a draft, add an internal comment |
| Yes | Many records | Yes | Bulk-update tickets, re-tag a dataset |
| No | Single record | Yes | Send an email, post to a channel |
| No | Many records | Yes — plus a second reviewer | Delete records, revoke credentials, push config |
| Yes | External party sees it | Yes | Publish a report to a shared space |
Note the asymmetry: reversibility alone is not enough once other people can see the result. You cannot un-notify someone.
Designing a gate that is not click-fatigue theater
A gate approved 200 times a day is a rubber stamp with a UI. Five rules keep it real:
| Rule | Why |
|---|---|
| Gate rarely | If everything is gated, nothing is reviewed. Read-only operations should never prompt |
| Show the effect, not the intent | “Create ticket INC-4471 assigned to on-call, visible to 40 people” beats “Agent wants to create a ticket” |
| Show the evidence inline | The approver needs the log lines that drove the decision, not a summary of them |
| Make reject cheap and informative | Rejection should capture why, and that reason becomes test data |
| Record the approval as evidence | Who approved, when, what exact payload — into the audit trail |
Escalating friction is the honest version of this: low-risk actions proceed, medium-risk actions prompt with full context, high-risk actions require a typed confirmation of the specific target. Time-box approvals too — a gate approved twenty minutes ago should not authorize an action now.
Artifact 3 — Audit log sample with OpenTelemetry spans
An audit trail is what turns “something weird happened” into “here is exactly what happened, in order, with inputs.” Model it as spans: one trace per agent run, one span per meaningful step.
Attributes every span must carry
| Attribute group | Fields | Why an investigator needs it |
|---|---|---|
| Identity | trace_id, span_id, parent_span_id | Reconstructs the causal order across agents |
| Actor | agent.id, agent.role, user.id, session.id | Answers “who ran this, on whose behalf” |
| Version | agent.version, model.name, prompt.version | Lets you reproduce the exact conditions |
| Action | tool.name, tool.arguments, tool.result_summary | The actual thing that happened |
| Decision | decision.outcome, decision.confidence, policy.checks_passed | Why the agent chose this |
| Approval | approval.required, approval.granted_by, approval.timestamp | Proves the gate operated |
| Cost | tokens.input, tokens.output, duration_ms | Detects loops and abuse patterns |
| Safety | redaction.applied, input.source_trust | Shows untrusted content was labeled as such |
{
"trace_id": "4f9a2c...",
"span_id": "b81e7d...",
"name": "tool.create_ticket",
"agent": { "id": "ticket-creator", "version": "1.3.0" },
"attributes": {
"tool.name": "create_ticket",
"tool.arguments": { "verdict": "suspicious", "asset": "host-REDACTED" },
"input.source_trust": "derived_from_untrusted",
"approval.required": true,
"approval.granted_by": "operator-REDACTED",
"decision.confidence": "medium",
"tokens.input": 3181,
"duration_ms": 842
},
"status": "OK"
}What makes a trace investigable later
- Complete causal chain. Every span has a parent. You can walk from the final effect back to the originating input without guessing.
- Inputs preserved, not just outputs. The exact tool arguments, redacted but structurally intact. “It created a ticket” is useless; the payload is the evidence.
- Trust provenance. Each span records whether its input derived from untrusted content. This is the single field that makes injection investigation tractable.
- Version pinning. Agent version, prompt version, model name. Without these you cannot reproduce and therefore cannot confirm a fix.
- Failures recorded as spans. A blocked tool call is a span with an error status, not a silence. Absence of a record must never be ambiguous.
- Redaction that preserves shape. Substitute, do not delete, so field structure survives.
Build this in Lab 4 — Agent Audit Trail; tooling options in observability tools.
Artifact 4 — The “How this agent fails” README section
This is the highest-signal thing in your portfolio. Almost nobody writes it, and it takes about forty minutes.
Template
## How this agent fails
### Known failure modes
1. <Condition> → <what the agent does> → <impact> → <mitigation or "unmitigated">
### What I attacked and what happened
| Attack | Payload location | Result | Control that caught it |
|---|---|---|---|
### What I did not test
- <Area> — <why not>
### What would need to change for production
- <Gap> → <required change>Writing each section
Known failure modes. Concrete conditions, not disclaimers. “Logs above roughly 50 MB exceed the context window; the agent silently summarizes only the first chunk and does not report the truncation” is a failure mode. “May produce inaccurate results” is a legal notice.
What I attacked and what happened. Pull this straight from your Month 2 attack log. Include the attacks that failed to break anything — a blocked attempt with a named control is proof the control works.
What I did not test. Name the gaps yourself. Multi-user concurrency, adversarial file formats, sustained load, non-English input, model version drift. A reader who finds an untested area you already listed trusts the rest of your claims. A reader who finds one you did not list stops trusting all of them.
What would need to change for production. Bridges the gap between a lab project and a real deployment: managed secret storage, rate limiting, a real approval system, monitoring and alerting on anomalous tool-call rates, log retention aligned to a policy — connect this to governance requirements where relevant.
Final checklist
| Artifact | Done when | Role it signals to |
|---|---|---|
| STRIDE threat model | Diagram committed, all six categories mapped to a concrete threat and control, boundaries drawn | AI Security Engineer, Security Architect — see core roles |
| Trust boundary & approval design | Gates justified by irreversibility and blast radius; decision table committed; prompts rendered from validated fields | Agent Platform Security, Product Security |
| Audit trail sample | Real redacted trace in docs/, every span carries identity, version, trust provenance, and approval | Detection Engineering, AI Assurance — see adjacent roles |
| “How this agent fails” | All four headings filled with specifics, including negative attack results and untested areas | Every role. This is the section people quote back to you in interviews |
Practice the full threat-model-to-risk-register flow in Lab 5, and check your control vocabulary against standards references and the OWASP agentic threats catalog.