# MAESTRO threat model — workbook template

A fill-in-the-blanks workbook for running a MAESTRO threat model and, more importantly,
for leaving behind something that survives the session.

Companion to: https://bluesquadron.dev/posts/maestro-threat-modeling-for-agentic-systems/
MAESTRO is published by the Cloud Security Alliance. This template is not a CSA artefact.

Licence: CC BY 4.0 — use it, change it, ship it. Attribution appreciated, not required.

How to use it:
  1. Copy this file into the repository that holds the system, as
     `docs/security/threat-model.md`. Not into a wiki, not into a slide deck.
  2. Delete the worked examples marked `e.g.` and replace them with your own.
  3. Review it in a pull request like any other change.

The examples throughout are from the invoice exception desk described in the post.

---

## 0. Header

| Field | Value |
|---|---|
| System | e.g. Invoice Exception Desk |
| Version | e.g. 1.2 |
| Date of session | e.g. 2026-09-12 |
| Facilitator | |
| Participants | e.g. tool engineer, data owner, platform, business owner, scribe |
| Next scheduled review | e.g. 2027-03-12 |
| Reopen triggers | see section 6 |

Keep the participant list. Six months from now the most common question about this
document is "who agreed to this?", and the second is "were the right people there?".

---

## 1. Layer inventory

Every component, tagged with its layer and its owner. Empty layers are fine — say so
explicitly rather than leaving a blank, so a reader can tell the difference between
"nothing here" and "nobody looked".

| Layer | Component | Owner | In scope | Notes |
|---|---|---|---|---|
| L1 Foundation Models | e.g. hosted model, version pinned | e.g. Platform | yes | no fine-tune |
| L2 Data Operations | e.g. contract vector store | e.g. Data Eng | yes | ingested from DMS |
| L2 Data Operations | e.g. read-only ERP replica | e.g. ERP team | yes | |
| L3 Agent Frameworks | e.g. orchestrator + 3 agents | e.g. App team | yes | tool schemas in repo |
| L4 Deployment & Infrastructure | e.g. EKS, MCP pods | e.g. Platform | inherited | see platform threat model |
| L5 Evaluation & Observability | e.g. OTel traces, action log | e.g. App team | yes | |
| L6 Security & Compliance | e.g. policy gate, IAM | e.g. Security | yes | |
| L7 Agent Ecosystem | e.g. supplier portal, payment run | e.g. Finance IT | yes | external counterparties |

**Inherited** is a legitimate value. It means the risk is real and owned somewhere else;
name where.

---

## 2. Threat register

One row per threat. The rule that makes this useful: **write the threat as a failure in
this system**, never as a category name. "Tool misuse" teaches nobody anything.

Reference column: map to a catalogue so you inherit its mitigations — OWASP Agentic
Security Initiative T1–T15, MITRE ATLAS technique IDs, or your own.

| ID | Layer | Threat, as a failure in this system | Ref | L | I | E | Score | Status | Owner |
|---|---|---|---|---|---|---|---|---|---|
| T-01 | L2 | Contract chunks carry no provenance, so an attacker-supplied clause is indistinguishable from the signed master agreement | OWASP T1 | 2 | 3 | 3 | 18 | open | Data Eng |
| T-02 | L3 | The planner concatenates retrieved contract text into the instruction channel, so a document can issue commands | OWASP T6 | 2 | 3 | 3 | 18 | open | App team |
| T-03 | L4 | All MCP server pods share one IAM role, so the cheapest tool yields the most privileged | OWASP T3 | 2 | 3 | 2 | 12 | open | Platform |
| T-04 | L5 | The action log records the tool call but not the authority for it | OWASP T8 | 3 | 2 | 1 | 6 | open | App team |
| T-05 | L6 | The gate threshold is per invoice with no cumulative term | — | 3 | 3 | 2 | 18 | open | Security |
| T-06 | L1 | Price-variance reasoning is unstable across model versions, so a routine upgrade changes what auto-posts | — | 3 | 2 | 1 | 6 | open | Platform |

Scoring scale in the appendix. Read the caveat there before you quote a number.

---

## 3. Chain register

**The artefact only MAESTRO produces, and the reason to run it at all.**

A chain is an ordered set of hops across layers where every individual step is permitted.
If every hop in your chain is a control failure, you have found a set of bugs, not a chain.
The interesting ones are the chains where nothing failed.

### Chain C-01 — Supplier-supplied clause reaches payment

| Hop | Layer | What happens | Why it is permitted |
|---|---|---|---|
| 1 | L7 | Supplier uploads a contract amendment via the portal | Valid PDF from an authenticated supplier |
| 2 | L2 | Ingestion chunks and embeds it into the contract store | The pipeline has no concept of who wrote a clause |
| 3 | L2→L3 | Contract agent retrieves it as governing text | Retrieval ranks by relevance; relevance is not provenance |
| 4 | L3 | Planner reads it as instruction, not data | Clause states a 40% permitted price variance |
| 5 | L6 | Gate checks: under threshold, clause cited, variance within cited limit | All three checks green |
| 6 | L7→L5 | Payment posts; action log records call and amount | Provenance of the authorising clause is not recorded |

- **Threats involved:** T-01, T-02, T-05, T-04
- **Impact if realised:** unauthorised payment within per-transaction limits; no post-hoc attribution
- **Breaks at hop:** 3 (provenance filter), 4 (evidence envelope), 5 (provenance-aware gate)
- **Cheapest break:** hop 3
- **Status:** open

### Chain C-02 — [your next chain]

| Hop | Layer | What happens | Why it is permitted |
|---|---|---|---|
| 1 | | | |

---

## 4. Control map

A mitigation with no test is an intention. The last column is the one that matters, and
it is the one that gets left blank.

| Control | Layer | Threats / chains | Where implemented | Test that proves it | Status |
|---|---|---|---|---|---|
| Provenance as a first-class field on every chunk | L2 | T-01, C-01 | ingestion pipeline | integration test: unsigned doc is not retrievable as governing | planned |
| Retrieved content never enters the instruction channel | L3 | T-02, C-01 | planner prompt assembly | eval case: injected clause does not change proposal | planned |
| Gate reads provenance and carries a cumulative term | L6 | T-05, C-01 | policy gate | unit tests: unsigned clause rejected; period cap enforced | planned |
| Action log records the authorising clause and version | L5 | T-04 | action logger | assertion: every posting has a resolvable authority ref | planned |
| One IAM role per MCP server | L4 | T-03 | terraform | policy test: no shared role across pods | planned |
| Model version pinned, upgrades gated on eval set | L1 | T-06 | CI | eval gate on 200 historical exceptions | in place |

---

## 5. Decision log

Accepted risk is a decision with a date on it, not a permanent state. Every row here
expires; on expiry it returns to the register.

| Date | Decision | Rationale | Accepted by | Expires | Linked |
|---|---|---|---|---|---|
| e.g. 2026-09-12 | Accept L4 platform risks as inherited | Covered by platform threat model v4 | e.g. CISO delegate | 2027-09-12 | L4 rows |
| e.g. 2026-09-12 | Ship with per-invoice gate; cumulative term follows in the next release | Business needs the desk live for quarter end | e.g. Finance Director | 2026-12-31 | T-05 |

An acceptance with no expiry date is not an acceptance. It is a way of ending the
conversation.

---

## 6. Reopen triggers

This model is stale the moment any of these happens. Wire them to something that fires —
a CODEOWNERS rule, a pipeline check, a ticket template — rather than to a calendar.

- [ ] A new tool is added to any agent
- [ ] A new agent joins the system
- [ ] The foundation model version changes
- [ ] A new external counterparty can put data into the system
- [ ] A data store gains a new writer
- [ ] The policy gate's rules change
- [ ] An incident touches any component in section 1

---

## Appendix — scoring scale

| Assessment | Value |
|---|---|
| Low | 1 |
| Medium | 2 |
| High | 3 |

`Risk = Likelihood × Impact × Exploitability`, so the range is 1–27.

Scale after Zambare, Thanikella & Liu, *Securing Agentic AI: Threat Modeling and Risk
Analysis for Network Monitoring Agentic AI System*, arXiv:2508.10043 (CC BY 4.0).

**Read this before quoting a score.** Multiplying ordinals is not arithmetic. The distance
from Low to Medium is not the distance from Medium to High, and nothing entitles you to
multiply them. The number is a sorting device for a conversation, not a measurement. Use
it to order the register, and never put it in a business case on its own.
