|
| 1 | +# Predicate type: Agent Threat Scan |
| 2 | + |
| 3 | +Type URI: https://in-toto.io/attestation/agent-threat-scan/v0.1 |
| 4 | + |
| 5 | +Version: 0.1 |
| 6 | + |
| 7 | +Authors: Adam Lin (@eeee2345) |
| 8 | + |
| 9 | +## Purpose |
| 10 | + |
| 11 | +The Agent Threat Scan predicate communicates that an AI agent artifact (such as |
| 12 | +a Model Context Protocol server, a Claude Code skill, or another agent |
| 13 | +configuration manifest) has been scanned against a named detection ruleset for |
| 14 | +agent-specific threats including prompt injection, tool poisoning, MCP request |
| 15 | +forgery, and skill compromise. It enables consumers of agent artifacts to |
| 16 | +verify which ruleset and ruleset version were applied, what the scan outcome |
| 17 | +was, and which rules (if any) matched, without needing to re-run the scan or |
| 18 | +read the full set of rule definitions. |
| 19 | + |
| 20 | +## Use Cases |
| 21 | + |
| 22 | +Existing predicates such as Vulnerabilities (vulns), Test Result, and Simple |
| 23 | +Verification Result do not capture the structure of agent-specific scanning. |
| 24 | +Vulnerability scanners are oriented around package CVEs in conventional |
| 25 | +software artifacts. Test Result is generic and does not carry the |
| 26 | +ruleset-versioning, rule-identifier, and threat-class taxonomy that are |
| 27 | +characteristic of agent threat detection. Simple Verification Result records |
| 28 | +that policies passed but does not enumerate which rules matched on a fail. |
| 29 | + |
| 30 | +Concrete use cases the Agent Threat Scan predicate enables include the |
| 31 | +following. A registry of MCP servers can require an attestation that an |
| 32 | +artifact was scanned with a named open ruleset at or above a minimum version |
| 33 | +before the artifact is listed. A CI pipeline producing agent skills can attach |
| 34 | +the predicate to released artifacts so downstream consumers can decide whether |
| 35 | +to install. A policy engine can answer the question, was this agent scanned |
| 36 | +with ruleset X version Y on date Z, and did any rules of severity high or |
| 37 | +critical match. |
| 38 | + |
| 39 | +## Prerequisites |
| 40 | + |
| 41 | +This predicate depends on the in-toto Attestation Framework and on the |
| 42 | +existence of a named detection ruleset that issues stable rule identifiers. |
| 43 | +One example of such a ruleset is Agent Threat Rules (ATR), an open detection |
| 44 | +standard for AI agent threats licensed Apache-2.0, available at |
| 45 | +https://github.com/Agent-Threat-Rule/agent-threat-rules. The predicate is not |
| 46 | +tied to ATR; any ruleset that issues stable identifiers and a version may be |
| 47 | +referenced through the ruleset.uri and ruleset.version fields. ATR is shipped |
| 48 | +in production at Cisco AI Defense and Microsoft agent-governance-toolkit. |
| 49 | + |
| 50 | +## Model |
| 51 | + |
| 52 | +An Agent Threat Scan attestation declares that a named scanner, applying a |
| 53 | +named ruleset at a specific version, evaluated the subject artifact at |
| 54 | +scannedAt and produced an outcome of pass, warn, or fail, optionally |
| 55 | +enumerating the rules that matched. Multiple Agent Threat Scan attestations |
| 56 | +can be produced for the same subject across rulesets or across time. The |
| 57 | +predicate does not include the rule definitions themselves; it references |
| 58 | +them by stable identifier and by ruleset version so that consumers can resolve |
| 59 | +them through the ruleset.uri. |
| 60 | + |
| 61 | +## Schema |
| 62 | + |
| 63 | +```jsonc |
| 64 | +{ |
| 65 | + // Standard attestation fields: |
| 66 | + "_type": "https://in-toto.io/Statement/v1", |
| 67 | + "subject": [{ ... }], |
| 68 | + |
| 69 | + // Predicate: |
| 70 | + "predicateType": "https://in-toto.io/attestation/agent-threat-scan/v0.1", |
| 71 | + "predicate": { |
| 72 | + "scanner": { |
| 73 | + "uri": "<URI>", |
| 74 | + "version": "<STRING>" |
| 75 | + }, |
| 76 | + "ruleset": { |
| 77 | + "uri": "<URI>", |
| 78 | + "version": "<STRING>" |
| 79 | + }, |
| 80 | + "scannedAt": "<TIMESTAMP>", |
| 81 | + "outcome": "pass|warn|fail", |
| 82 | + "matches": [ |
| 83 | + { |
| 84 | + "ruleId": "<STRING>", |
| 85 | + "severity": "low|medium|high|critical", |
| 86 | + "threatClass": "<STRING>", |
| 87 | + "evidence": "<STRING>" |
| 88 | + } |
| 89 | + ] |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +### Parsing Rules |
| 95 | + |
| 96 | +This predicate follows the |
| 97 | +[in-toto Attestation Framework's parsing rules](../v1/README.md#parsing-rules). |
| 98 | + |
| 99 | +### Fields |
| 100 | + |
| 101 | +**`scanner`, required** object |
| 102 | + |
| 103 | +> Identifies the tool that performed the scan. |
| 104 | +
|
| 105 | +**`scanner.uri`, required** string (ResourceURI) |
| 106 | + |
| 107 | +> URI identifying the scanner. May reference a package, repository, or service. |
| 108 | +
|
| 109 | +**`scanner.version`, optional** string |
| 110 | + |
| 111 | +> The version of the scanner. |
| 112 | +
|
| 113 | +**`ruleset`, required** object |
| 114 | + |
| 115 | +> Identifies the detection ruleset applied during the scan. |
| 116 | +
|
| 117 | +**`ruleset.uri`, required** string (ResourceURI) |
| 118 | + |
| 119 | +> URI identifying the ruleset. SHOULD resolve to a definition of the rules, |
| 120 | +> or to documentation describing how to resolve a rule identifier to its |
| 121 | +> rule definition. |
| 122 | +
|
| 123 | +**`ruleset.version`, required** string |
| 124 | + |
| 125 | +> The version of the ruleset that was applied. The version SHOULD be stable |
| 126 | +> and citable so that a consumer can re-resolve any ruleId in matches against |
| 127 | +> the exact ruleset version used. |
| 128 | +
|
| 129 | +**`scannedAt`, required** string (Timestamp) |
| 130 | + |
| 131 | +> RFC 3339 timestamp indicating when the scan completed. |
| 132 | +
|
| 133 | +**`outcome`, required** string |
| 134 | + |
| 135 | +> One of pass, warn, or fail. pass means no rules matched. warn means at most |
| 136 | +> rules below the producer's chosen blocking threshold matched. fail means at |
| 137 | +> least one rule at or above the blocking threshold matched. Producers SHOULD |
| 138 | +> document their threshold mapping where the predicate is consumed. |
| 139 | +
|
| 140 | +**`matches`, optional** array of object |
| 141 | + |
| 142 | +> The list of rules that matched on the subject. MAY be empty when outcome |
| 143 | +> is pass. SHOULD be present and non-empty when outcome is warn or fail. |
| 144 | +
|
| 145 | +**`matches[*].ruleId`, required** string |
| 146 | + |
| 147 | +> The stable identifier of the rule that matched, as issued by the ruleset. |
| 148 | +
|
| 149 | +**`matches[*].severity`, optional** string |
| 150 | + |
| 151 | +> One of low, medium, high, critical. The severity assigned by the ruleset |
| 152 | +> to the matched rule. |
| 153 | +
|
| 154 | +**`matches[*].threatClass`, optional** string |
| 155 | + |
| 156 | +> A short label describing the class of threat the rule covers, such as |
| 157 | +> prompt_injection, tool_poisoning, mcp_request_forgery, or skill_compromise. |
| 158 | +
|
| 159 | +**`matches[*].evidence`, optional** string |
| 160 | + |
| 161 | +> A free-form string containing scanner-specific evidence such as a snippet, |
| 162 | +> a path within the subject, or a hash. Producers SHOULD avoid including |
| 163 | +> sensitive content in this field. |
| 164 | +
|
| 165 | +## Example |
| 166 | + |
| 167 | +```jsonc |
| 168 | +{ |
| 169 | + "_type": "https://in-toto.io/Statement/v1", |
| 170 | + "subject": [ |
| 171 | + { |
| 172 | + "name": "example-mcp-server-1.2.3.tgz", |
| 173 | + "digest": {"sha256": "fe4fe40ac7250263c5dbe1cf3138912f3f416140aa248637a60d65fe22c47da4"} |
| 174 | + } |
| 175 | + ], |
| 176 | + "predicateType": "https://in-toto.io/attestation/agent-threat-scan/v0.1", |
| 177 | + "predicate": { |
| 178 | + "scanner": { |
| 179 | + "uri": "pkg:npm/%40panguard-ai/cli@1.4.13", |
| 180 | + "version": "1.4.13" |
| 181 | + }, |
| 182 | + "ruleset": { |
| 183 | + "uri": "https://github.com/Agent-Threat-Rule/agent-threat-rules", |
| 184 | + "version": "v2.0.17" |
| 185 | + }, |
| 186 | + "scannedAt": "2026-05-09T10:00:00Z", |
| 187 | + "outcome": "fail", |
| 188 | + "matches": [ |
| 189 | + { |
| 190 | + "ruleId": "ATR-2026-00081", |
| 191 | + "severity": "high", |
| 192 | + "threatClass": "prompt_injection", |
| 193 | + "evidence": "tools[2].description contained a hidden instruction directive" |
| 194 | + } |
| 195 | + ] |
| 196 | + } |
| 197 | +} |
| 198 | +``` |
| 199 | + |
| 200 | +## Changelog and Migrations |
| 201 | + |
| 202 | +Not applicable for this initial version. |
| 203 | + |
| 204 | +[Attestation]: ../README.md |
0 commit comments