Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions docs/ASI_MAPPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ REAP cites [OWASP Top 10 for Agentic Applications (2026)](https://genai.owasp.or
| `mcp-redirect-uri-laxity` | ASI03 | Broad redirect registration enables confused-deputy and code-interception attacks against the agent's identity. |
| `mcp-session-id-entropy` | ASI03 | A guessable session ID lets an attacker assume another caller's identity directly. |
| `mcp-host-header-validation` | ASI03 | DNS rebinding lets a web page reach an agent endpoint and act with the victim's network position. |
| `transport-plaintext` | ASI04 | Agent traffic, tool arguments, and bearer tokens readable on the wire. |
| `transport-downgrade` | ASI04 | A plaintext listener on the same host defeats the TLS the endpoint otherwise offers. |
| `transport-plaintext` | ASI07 | Agent traffic, tool arguments, and bearer tokens readable on the wire: insecure communication on the agent's own channel. |
| `transport-downgrade` | ASI07 | A plaintext listener on the same host defeats the TLS the endpoint otherwise offers. |
| `tls-cert-health` | ASI09 | Certificate and cipher problems undermine the assurance the transport is supposed to provide. |
| `http-cors-wildcard` | ASI03 | Permissive CORS lets an arbitrary web origin act as the agent from a victim's browser. |
| `http-rate-limit-absence` | ASI06 | No advertised limiting means a caller can drive the agent (and everything downstream of it) without backpressure. |
| `http-rate-limit-absence` | ASI08 | No advertised limiting means a caller can drive the agent (and everything downstream of it) without backpressure: a cascading-failure concern. |
| `mcp-tmpl-high-risk-tool-names` (template) | ASI02, ASI05 | Tool names suggesting shell execution or raw filesystem access are the unexpected-code-execution case; the inventory itself is the tool-misuse reconnaissance step. |

## Two corrections
## Three corrections

**`mcp-host-header-validation` cited ASI05 (Memory & Context Poisoning).** DNS rebinding has nothing to do with an agent's memory or context. It is an identity and privilege problem: the attacker borrows the victim's network position to reach an endpoint that trusts it. Now ASI03.
**The reference table followed an earlier draft of the numbering.** `ASITitles` in `internal/report/report.go` carried pre-v1.0 titles for ASI04 to ASI09 (Insecure Inter-Agent Communication, Memory & Context Poisoning, Cascading Failures, Excessive Agency, Supply Chain & Dependency Risk, Observability & Auditability Gaps), and every `asi_refs` was coded against it. The table now matches the published v1.0 list and is pinned by `TestASITitles_MatchPublishedV1`; the codes below were renumbered to keep the meaning each check was given: `http-rate-limit-absence` ASI06 to ASI08, `transport-plaintext` and `transport-downgrade` ASI04 to ASI07, the high-risk-tool-names template ASI07 to ASI05. The four checks still citing ASI09 (Human-Agent Trust Exploitation in v1.0) record observability and disclosure facts that the draft filed under a category the final list dropped; they are left as they are pending a decision on whether to uncite them like `mcp-auth-posture`.

**`http-rate-limit-absence` cited ASI08 (Supply Chain & Dependency Risk).** Missing rate-limit headers say nothing about dependencies. Unbounded call volume against an agent is a cascading-failure and availability concern. Now ASI06.
**`mcp-host-header-validation` cited ASI05 (Memory & Context Poisoning in the draft numbering; ASI06 in v1.0).** DNS rebinding has nothing to do with an agent's memory or context. It is an identity and privilege problem: the attacker borrows the victim's network position to reach an endpoint that trusts it. Now ASI03.

**`http-rate-limit-absence` cited ASI08 (Supply Chain & Dependency Risk in the draft numbering; ASI04 in v1.0).** Missing rate-limit headers say nothing about dependencies. Unbounded call volume against an agent is a cascading-failure and availability concern, which is ASI08 in v1.0.

## Severity calibration

Expand Down
11 changes: 5 additions & 6 deletions internal/probe/transport/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (p *plaintextProbe) Run(ctx context.Context, s probe.Session, r *report.Rep
Title: "Agent endpoint served over plaintext HTTP",
Severity: report.SeverityMedium,
Protocol: "*",
ASI: []string{"ASI04"},
ASI: []string{"ASI07"},
Description: "Target URL uses http:// rather than https://. Tool calls, arguments, and any auth tokens are visible to on-path observers.",
Evidence: map[string]any{"url": s.TargetURL()},
Remediation: "Serve agent endpoints over TLS only; redirect or refuse plaintext connections.",
Expand Down Expand Up @@ -213,7 +213,7 @@ func (p *downgradeProbe) Run(ctx context.Context, s probe.Session, r *report.Rep
Title: "Endpoint also responds over plaintext HTTP",
Severity: report.SeverityMedium,
Protocol: "*",
ASI: []string{"ASI04"},
ASI: []string{"ASI07"},
Description: "The target host also accepted at least one plaintext HTTP path for agent traffic, which undermines TLS protections.",
Evidence: map[string]any{"fallbacks": evidence},
Remediation: "Disable plaintext HTTP listeners for agent endpoints and accept traffic only over TLS.",
Expand Down Expand Up @@ -410,10 +410,9 @@ func (p *rateLimitProbe) Run(ctx context.Context, s probe.Session, r *report.Rep
Title: "No standard rate-limit headers observed",
Severity: report.SeverityLow,
Protocol: "*",
// ASI06 (Cascading Failures) rather than ASI08 (Supply Chain), which
// this has nothing to do with. Missing rate limiting is a
// cascading-failure and availability concern.
ASI: []string{"ASI06"},
// Missing rate limiting is a cascading-failure and availability
// concern: ASI08 (Cascading Failures) in the published v1.0 numbering.
ASI: []string{"ASI08"},
Description: "The endpoint answered requests but sent no standard rate-limit response headers. This is a reconnaissance signal that the service may not be advertising rate limiting to clients; it is not proof that no limiting exists.",
Evidence: map[string]any{"observations": observed},
Remediation: "Expose standard rate-limit headers such as Retry-After, RateLimit-Remaining, and RateLimit-Limit, or document the expected client behavior when limits are reached.",
Expand Down
10 changes: 5 additions & 5 deletions internal/probe/transport/checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func TestRateLimitProbe(t *testing.T) {
}
})

t.Run("headers absent: reports at ASI06", func(t *testing.T) {
t.Run("headers absent: reports at ASI08", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
Expand All @@ -190,10 +190,10 @@ func TestRateLimitProbe(t *testing.T) {
if len(rep.Findings) != 1 {
t.Fatalf("expected 1 finding, got %v", rep.Findings)
}
// ASI08 (Supply Chain) was simply the wrong category for missing rate
// limiting; it is a cascading-failure concern.
if got := rep.Findings[0].ASI; len(got) != 1 || got[0] != "ASI06" {
t.Fatalf("expected ASI06, got %v", got)
// Missing rate limiting is a cascading-failure concern: ASI08 in the
// published v1.0 numbering (the draft table had it at ASI06).
if got := rep.Findings[0].ASI; len(got) != 1 || got[0] != "ASI08" {
t.Fatalf("expected ASI08, got %v", got)
}
})
}
19 changes: 11 additions & 8 deletions internal/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,15 +544,18 @@ func joinASITitled(refs []string) string {
// Kept here so probes/templates can cite by code without hardcoding titles
// everywhere, and so this list has exactly one place to update.
var ASITitles = map[string]string{
// Published v1.0 numbering (December 2025). The previous table followed an
// earlier draft in which ASI04 to ASI09 carried different titles, and every
// probe's asi_refs was coded against that draft; see docs/ASI_MAPPING.md.
"ASI01": "Agent Goal Hijack",
"ASI02": "Tool Misuse & Exploitation",
"ASI03": "Agent Identity & Privilege Abuse",
"ASI04": "Insecure Inter-Agent Communication",
"ASI05": "Memory & Context Poisoning",
"ASI06": "Cascading Failures",
"ASI07": "Excessive Agency",
"ASI08": "Supply Chain & Dependency Risk",
"ASI09": "Observability & Auditability Gaps",
"ASI02": "Tool Misuse and Exploitation",
"ASI03": "Identity and Privilege Abuse",
"ASI04": "Agentic Supply Chain Vulnerabilities",
"ASI05": "Unexpected Code Execution (RCE)",
"ASI06": "Memory & Context Poisoning",
"ASI07": "Insecure Inter-Agent Communication",
"ASI08": "Cascading Failures",
"ASI09": "Human-Agent Trust Exploitation",
"ASI10": "Rogue Agents",
}

Expand Down
29 changes: 29 additions & 0 deletions internal/report/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,35 @@ func TestASITitles_AllTenPresent(t *testing.T) {
}
}

// TestASITitles_MatchPublishedV1 pins the table to the published OWASP Top 10
// for Agentic Applications 2026 v1.0. The table previously followed an earlier
// draft whose ASI04 to ASI09 carried different titles, and every asi_refs in
// the probes was coded against that draft, so a drift here silently mislabels
// every finding. A title change in the standard must change this test on
// purpose, not the table by accident.
func TestASITitles_MatchPublishedV1(t *testing.T) {
want := map[string]string{
"ASI01": "Agent Goal Hijack",
"ASI02": "Tool Misuse and Exploitation",
"ASI03": "Identity and Privilege Abuse",
"ASI04": "Agentic Supply Chain Vulnerabilities",
"ASI05": "Unexpected Code Execution (RCE)",
"ASI06": "Memory & Context Poisoning",
"ASI07": "Insecure Inter-Agent Communication",
"ASI08": "Cascading Failures",
"ASI09": "Human-Agent Trust Exploitation",
"ASI10": "Rogue Agents",
}
for id, title := range want {
if got := ASITitles[id]; got != title {
t.Errorf("%s: got %q, want %q (published v1.0)", id, got, title)
}
}
if len(ASITitles) != len(want) {
t.Errorf("ASITitles has %d entries, want %d", len(ASITitles), len(want))
}
}

func findingIDs(fs []Finding) []string {
out := make([]string, len(fs))
for i, f := range fs {
Expand Down
2 changes: 1 addition & 1 deletion templates/mcp/high-risk-tool-names.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"info": {
"title": "Tool list includes names suggesting code execution or filesystem access",
"severity": "medium",
"asi_refs": ["ASI02", "ASI07"],
"asi_refs": ["ASI02", "ASI05"],
"description": "The tool list (requested with credentials, if any were supplied) includes a tool name matching patterns associated with code-execution or raw-filesystem primitives. This is a triage signal for closer review; verify the tool's actual capabilities and authorization model.",
"remediation": "Review tool capabilities in context. If the tool performs code execution or unrestricted filesystem access, scope it behind explicit, auditable authorization separate from general tool listing. Consider narrower alternatives.",
"tags": ["tool-misuse", "excessive-agency"],
Expand Down
Loading