@sponsio/sdk 0.1.0-alpha.3 → 0.2.0-alpha.10

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.
Files changed (243) hide show
  1. package/bin/sponsio.cjs +11 -0
  2. package/contracts/benchmark/agentdojo.yaml +551 -0
  3. package/contracts/benchmark/odcv_bench.yaml +393 -0
  4. package/contracts/benchmark/redcode_exec.yaml +488 -0
  5. package/contracts/benchmark/swebench.yaml +176 -0
  6. package/contracts/benchmark/tau2_bench.yaml +984 -0
  7. package/contracts/capability/credentials.yaml +8 -8
  8. package/contracts/capability/database.yaml +6 -6
  9. package/contracts/capability/destructive.yaml +213 -0
  10. package/contracts/capability/factual-claims.yaml +79 -0
  11. package/contracts/capability/filesystem-strict.yaml +4 -4
  12. package/contracts/capability/filesystem.yaml +9 -9
  13. package/contracts/capability/host-config-integrity.yaml +318 -0
  14. package/contracts/capability/self-modify.yaml +260 -9
  15. package/contracts/capability/shell.yaml +43 -41
  16. package/contracts/capability/subagent.yaml +5 -5
  17. package/contracts/incident/claude-code-secret-bypass.yaml +57 -26
  18. package/contracts/incident/cursor-railway-wipe.yaml +63 -7
  19. package/contracts/incident/mcp-composition.yaml +12 -12
  20. package/contracts/incident/openclaw.yaml +47 -92
  21. package/contracts/incident/subagent-escape.yaml +4 -4
  22. package/contracts/premium/.gitkeep +5 -6
  23. package/dist/cli/check.d.ts +2 -0
  24. package/dist/cli/check.d.ts.map +1 -0
  25. package/dist/cli/check.js +283 -0
  26. package/dist/cli/check.js.map +1 -0
  27. package/dist/cli/cli.d.ts +25 -0
  28. package/dist/cli/cli.d.ts.map +1 -0
  29. package/dist/cli/cli.js +491 -0
  30. package/dist/cli/cli.js.map +1 -0
  31. package/dist/cli/config.d.ts +88 -0
  32. package/dist/cli/config.d.ts.map +1 -0
  33. package/dist/cli/config.js +243 -0
  34. package/dist/cli/config.js.map +1 -0
  35. package/dist/cli/demo.d.ts +12 -0
  36. package/dist/cli/demo.d.ts.map +1 -0
  37. package/dist/cli/demo.js +350 -0
  38. package/dist/cli/demo.js.map +1 -0
  39. package/dist/cli/doctor.d.ts +11 -0
  40. package/dist/cli/doctor.d.ts.map +1 -0
  41. package/dist/cli/doctor.js +201 -0
  42. package/dist/cli/doctor.js.map +1 -0
  43. package/dist/cli/eval.d.ts +2 -0
  44. package/dist/cli/eval.d.ts.map +1 -0
  45. package/dist/cli/eval.js +361 -0
  46. package/dist/cli/eval.js.map +1 -0
  47. package/dist/cli/explain.d.ts +2 -0
  48. package/dist/cli/explain.d.ts.map +1 -0
  49. package/dist/cli/explain.js +266 -0
  50. package/dist/cli/explain.js.map +1 -0
  51. package/dist/cli/export-sessions.d.ts +2 -0
  52. package/dist/cli/export-sessions.d.ts.map +1 -0
  53. package/dist/cli/export-sessions.js +182 -0
  54. package/dist/cli/export-sessions.js.map +1 -0
  55. package/dist/cli/export.d.ts +2 -0
  56. package/dist/cli/export.d.ts.map +1 -0
  57. package/dist/cli/export.js +222 -0
  58. package/dist/cli/export.js.map +1 -0
  59. package/dist/cli/extractors/common.d.ts +27 -0
  60. package/dist/cli/extractors/common.d.ts.map +1 -0
  61. package/dist/cli/extractors/common.js +107 -0
  62. package/dist/cli/extractors/common.js.map +1 -0
  63. package/dist/cli/extractors/generic.d.ts +37 -0
  64. package/dist/cli/extractors/generic.d.ts.map +1 -0
  65. package/dist/cli/extractors/generic.js +185 -0
  66. package/dist/cli/extractors/generic.js.map +1 -0
  67. package/dist/cli/extractors/langchain.d.ts +18 -0
  68. package/dist/cli/extractors/langchain.d.ts.map +1 -0
  69. package/dist/cli/extractors/langchain.js +86 -0
  70. package/dist/cli/extractors/langchain.js.map +1 -0
  71. package/dist/cli/extractors/vercel.d.ts +17 -0
  72. package/dist/cli/extractors/vercel.d.ts.map +1 -0
  73. package/dist/cli/extractors/vercel.js +46 -0
  74. package/dist/cli/extractors/vercel.js.map +1 -0
  75. package/dist/cli/index.d.ts +38 -0
  76. package/dist/cli/index.d.ts.map +1 -0
  77. package/dist/cli/index.js +146 -0
  78. package/dist/cli/index.js.map +1 -0
  79. package/dist/cli/init.d.ts +2 -0
  80. package/dist/cli/init.d.ts.map +1 -0
  81. package/dist/cli/init.js +177 -0
  82. package/dist/cli/init.js.map +1 -0
  83. package/dist/cli/mode.d.ts +2 -0
  84. package/dist/cli/mode.d.ts.map +1 -0
  85. package/dist/cli/mode.js +71 -0
  86. package/dist/cli/mode.js.map +1 -0
  87. package/dist/cli/onboard.d.ts +88 -0
  88. package/dist/cli/onboard.d.ts.map +1 -0
  89. package/dist/cli/onboard.js +758 -0
  90. package/dist/cli/onboard.js.map +1 -0
  91. package/dist/cli/otlp.d.ts +27 -0
  92. package/dist/cli/otlp.d.ts.map +1 -0
  93. package/dist/cli/otlp.js +79 -0
  94. package/dist/cli/otlp.js.map +1 -0
  95. package/dist/cli/package.json +1 -0
  96. package/dist/cli/packs.d.ts +16 -0
  97. package/dist/cli/packs.d.ts.map +1 -0
  98. package/dist/cli/packs.js +190 -0
  99. package/dist/cli/packs.js.map +1 -0
  100. package/dist/cli/patterns.d.ts +16 -0
  101. package/dist/cli/patterns.d.ts.map +1 -0
  102. package/dist/cli/patterns.js +136 -0
  103. package/dist/cli/patterns.js.map +1 -0
  104. package/dist/cli/prompt.d.ts +2 -0
  105. package/dist/cli/prompt.d.ts.map +1 -0
  106. package/dist/cli/prompt.js +54 -0
  107. package/dist/cli/prompt.js.map +1 -0
  108. package/dist/cli/replay.d.ts +2 -0
  109. package/dist/cli/replay.d.ts.map +1 -0
  110. package/dist/cli/replay.js +196 -0
  111. package/dist/cli/replay.js.map +1 -0
  112. package/dist/cli/report.d.ts +57 -0
  113. package/dist/cli/report.d.ts.map +1 -0
  114. package/dist/cli/report.js +298 -0
  115. package/dist/cli/report.js.map +1 -0
  116. package/dist/cli/scan.d.ts +2 -0
  117. package/dist/cli/scan.d.ts.map +1 -0
  118. package/dist/cli/scan.js +172 -0
  119. package/dist/cli/scan.js.map +1 -0
  120. package/dist/cli/skill.d.ts +15 -0
  121. package/dist/cli/skill.d.ts.map +1 -0
  122. package/dist/cli/skill.js +131 -0
  123. package/dist/cli/skill.js.map +1 -0
  124. package/dist/cli/tokens.d.ts +31 -0
  125. package/dist/cli/tokens.d.ts.map +1 -0
  126. package/dist/cli/tokens.js +73 -0
  127. package/dist/cli/tokens.js.map +1 -0
  128. package/dist/cli/types.d.ts +46 -0
  129. package/dist/cli/types.d.ts.map +1 -0
  130. package/dist/cli/types.js +3 -0
  131. package/dist/cli/types.js.map +1 -0
  132. package/dist/cli/validate.d.ts +19 -0
  133. package/dist/cli/validate.d.ts.map +1 -0
  134. package/dist/cli/validate.js +205 -0
  135. package/dist/cli/validate.js.map +1 -0
  136. package/dist/cli/zod.d.ts +36 -0
  137. package/dist/cli/zod.d.ts.map +1 -0
  138. package/dist/cli/zod.js +226 -0
  139. package/dist/cli/zod.js.map +1 -0
  140. package/dist/cloud/client.d.ts +76 -0
  141. package/dist/cloud/client.d.ts.map +1 -0
  142. package/dist/cloud/client.js +204 -0
  143. package/dist/cloud/client.js.map +1 -0
  144. package/dist/cloud/evidence.d.ts +108 -0
  145. package/dist/cloud/evidence.d.ts.map +1 -0
  146. package/dist/cloud/evidence.js +184 -0
  147. package/dist/cloud/evidence.js.map +1 -0
  148. package/dist/cloud/privacy.d.ts +67 -0
  149. package/dist/cloud/privacy.d.ts.map +1 -0
  150. package/dist/cloud/privacy.js +170 -0
  151. package/dist/cloud/privacy.js.map +1 -0
  152. package/dist/cloud/view-model.d.ts +49 -0
  153. package/dist/cloud/view-model.d.ts.map +1 -0
  154. package/dist/cloud/view-model.js +172 -0
  155. package/dist/cloud/view-model.js.map +1 -0
  156. package/dist/contract.d.ts +13 -13
  157. package/dist/contract.d.ts.map +1 -1
  158. package/dist/contract.js +21 -21
  159. package/dist/contract.js.map +1 -1
  160. package/dist/core/config-loader.d.ts +1 -1
  161. package/dist/core/config-loader.d.ts.map +1 -1
  162. package/dist/core/config-loader.js +116 -26
  163. package/dist/core/config-loader.js.map +1 -1
  164. package/dist/core/evaluator.d.ts +11 -1
  165. package/dist/core/evaluator.d.ts.map +1 -1
  166. package/dist/core/evaluator.js +275 -13
  167. package/dist/core/evaluator.js.map +1 -1
  168. package/dist/core/formula.d.ts +76 -17
  169. package/dist/core/formula.d.ts.map +1 -1
  170. package/dist/core/formula.js +95 -1
  171. package/dist/core/formula.js.map +1 -1
  172. package/dist/core/grounding.d.ts +86 -3
  173. package/dist/core/grounding.d.ts.map +1 -1
  174. package/dist/core/grounding.js +485 -143
  175. package/dist/core/grounding.js.map +1 -1
  176. package/dist/core/nl-parser.d.ts +2 -2
  177. package/dist/core/nl-parser.d.ts.map +1 -1
  178. package/dist/core/nl-parser.js +690 -42
  179. package/dist/core/nl-parser.js.map +1 -1
  180. package/dist/core/pack-loader.d.ts.map +1 -1
  181. package/dist/core/pack-loader.js +14 -2
  182. package/dist/core/pack-loader.js.map +1 -1
  183. package/dist/core/parser.d.ts.map +1 -1
  184. package/dist/core/parser.js +30 -1
  185. package/dist/core/parser.js.map +1 -1
  186. package/dist/core/pattern-factory.d.ts +4 -4
  187. package/dist/core/pattern-factory.d.ts.map +1 -1
  188. package/dist/core/pattern-factory.js +59 -4
  189. package/dist/core/pattern-factory.js.map +1 -1
  190. package/dist/core/patterns.d.ts +196 -5
  191. package/dist/core/patterns.d.ts.map +1 -1
  192. package/dist/core/patterns.js +400 -13
  193. package/dist/core/patterns.js.map +1 -1
  194. package/dist/core/session-log.d.ts.map +1 -1
  195. package/dist/core/session-log.js +20 -1
  196. package/dist/core/session-log.js.map +1 -1
  197. package/dist/core/spans.d.ts +1 -1
  198. package/dist/core/spans.d.ts.map +1 -1
  199. package/dist/core/spans.js.map +1 -1
  200. package/dist/core/sto.d.ts +30 -66
  201. package/dist/core/sto.d.ts.map +1 -1
  202. package/dist/core/sto.js +39 -100
  203. package/dist/core/sto.js.map +1 -1
  204. package/dist/core/tool-names.d.ts +59 -0
  205. package/dist/core/tool-names.d.ts.map +1 -0
  206. package/dist/core/tool-names.js +128 -0
  207. package/dist/core/tool-names.js.map +1 -0
  208. package/dist/index.d.ts +227 -33
  209. package/dist/index.d.ts.map +1 -1
  210. package/dist/index.js +539 -152
  211. package/dist/index.js.map +1 -1
  212. package/dist/integrations/claude-agent.d.ts +5 -0
  213. package/dist/integrations/claude-agent.d.ts.map +1 -1
  214. package/dist/integrations/claude-agent.js +42 -13
  215. package/dist/integrations/claude-agent.js.map +1 -1
  216. package/dist/integrations/google-adk.d.ts +5 -3
  217. package/dist/integrations/google-adk.d.ts.map +1 -1
  218. package/dist/integrations/google-adk.js +9 -5
  219. package/dist/integrations/google-adk.js.map +1 -1
  220. package/dist/integrations/langchain.d.ts +7 -0
  221. package/dist/integrations/langchain.d.ts.map +1 -1
  222. package/dist/integrations/langchain.js +21 -3
  223. package/dist/integrations/langchain.js.map +1 -1
  224. package/dist/integrations/openai-agents.d.ts +22 -10
  225. package/dist/integrations/openai-agents.d.ts.map +1 -1
  226. package/dist/integrations/openai-agents.js +57 -16
  227. package/dist/integrations/openai-agents.js.map +1 -1
  228. package/dist/integrations/openai.d.ts +11 -0
  229. package/dist/integrations/openai.d.ts.map +1 -1
  230. package/dist/integrations/openai.js +130 -48
  231. package/dist/integrations/openai.js.map +1 -1
  232. package/dist/integrations/vercel-ai.d.ts +20 -9
  233. package/dist/integrations/vercel-ai.d.ts.map +1 -1
  234. package/dist/integrations/vercel-ai.js +131 -32
  235. package/dist/integrations/vercel-ai.js.map +1 -1
  236. package/dist/render/tokens.d.ts.map +1 -1
  237. package/dist/render/tokens.js +1 -0
  238. package/dist/render/tokens.js.map +1 -1
  239. package/package.json +41 -9
  240. package/prompts/onboard.md +259 -0
  241. package/prompts/scan.md +123 -0
  242. package/skills/SKILL.md +432 -0
  243. package/contracts/core/llm_safety.yaml +0 -130
@@ -0,0 +1,318 @@
1
+ # =============================================================================
2
+ # sponsio/contracts/capability/host-config-integrity.yaml
3
+ #
4
+ # Capability pack — IDE host config integrity protection.
5
+ # Include in your per-host bucket (``_host_cursor`` /
6
+ # ``_host_claude_code`` / ``_host_codex`` / ``_host_windsurf``)
7
+ # so the agent under guard cannot disable Sponsio by rewriting
8
+ # the IDE's hook registration files.
9
+ #
10
+ # Threat model:
11
+ #
12
+ # Sponsio's enforcement on IDE coding agents is mediated through
13
+ # the host IDE's hook system: Cursor's ``.cursor/hooks.json``,
14
+ # Claude Code's ``~/.claude/settings.json`` /
15
+ # ``.claude/settings.local.json``, the per-project ``.claude/hooks/``
16
+ # directory, etc. These files declare WHICH commands the IDE invokes
17
+ # at which hook events. If the agent can rewrite them, it doesn't
18
+ # need to defeat any individual rule — it just removes the
19
+ # ``preToolUse`` entry that calls Sponsio at all, and on the next IDE
20
+ # restart Sponsio is silently absent. Every rule in
21
+ # ``capability/self-modify`` and ``incident/subagent-escape`` keeps
22
+ # working perfectly, against an enforcer that no longer exists.
23
+ # This is sponsio:capability/self-modify's blind spot — that pack
24
+ # protects the rule files; this pack protects the wiring that
25
+ # routes calls to the rule files in the first place.
26
+ #
27
+ # Concrete attack shapes observed (or trivially derivable from §1
28
+ # of self-modify.yaml's threat model):
29
+ #
30
+ # 1. ``Write .cursor/hooks.json '{"version":1,"hooks":{}}'``
31
+ # — wipe all hook entries; next Cursor restart, no enforcement.
32
+ # 2. ``Edit .cursor/hooks.json``: change ``"failClosed": true``
33
+ # to ``"failClosed": false`` — Sponsio is still wired in but
34
+ # any spawn / parse / runtime error fails open.
35
+ # 3. ``Write merge_hooks.py`` whose content opens hooks.json in
36
+ # 'w' mode (the §4 self-modify bypass shape, instantiated
37
+ # against hook config instead of plugin yaml).
38
+ # 4. ``python -c "open('.cursor/hooks.json','w').write('...')"``
39
+ # — the §5 self-modify interpreter-bypass shape against hook
40
+ # config.
41
+ # 5. ``echo '{}' > .cursor/hooks.json`` / ``sed -i 's/true/false/'``
42
+ # — bash redirect / mutator forms that the self-modify pack
43
+ # doesn't currently match because its regex anchors on
44
+ # ``.sponsio/plugins/...`` paths only.
45
+ # 6. ``truncate -s0 ~/.claude/settings.json`` / ``ln -sf /dev/null
46
+ # ~/.claude/settings.json`` / ``chmod 000 ~/.claude/settings.json``
47
+ # / ``rm -rf ~/.claude`` — no write at all, but the IDE reads an
48
+ # empty, foreign, or unreadable file and loads no hooks.
49
+ #
50
+ # What this pack denies:
51
+ #
52
+ # - Edit / Write / MultiEdit on the IDE hook registration files.
53
+ # - Bash redirect / sed / awk / tee that targets the same files.
54
+ # - Script *creation* whose content references a hook config path
55
+ # AND uses an open-for-write idiom (mirror of self-modify §4).
56
+ # - Interpreter invocation whose command line carries a hook
57
+ # config path (mirror of self-modify §5).
58
+ #
59
+ # What this pack does NOT deny:
60
+ #
61
+ # - ``sponsio host install`` and ``sponsio host uninstall``
62
+ # write hooks.json via Python subprocess from the Sponsio
63
+ # binary itself, not through the agent's Edit / Write tool
64
+ # surface — the legitimate install path is unaffected.
65
+ # - The user hand-editing hooks.json in their text editor
66
+ # similarly does not pass through the IDE's Edit / Write
67
+ # tool; this pack is silent on that surface.
68
+ # - Reads of hook config files are not denied. The file format
69
+ # is documented public knowledge; recon defense here would
70
+ # give negligible benefit at significant false-positive cost
71
+ # (legitimate "what hooks are registered?" debug queries).
72
+ # The subagent-escape pack covers the recon angle for
73
+ # sub-agents specifically.
74
+ #
75
+ # Pair with: ``sponsio:capability/self-modify`` (rule-file integrity)
76
+ # and ``sponsio:incident/subagent-escape`` (sub-agent containment).
77
+ # Together the three packs form the control-plane defense surface
78
+ # documented in ``agent_docs/control_plane_threat_model.md``.
79
+ #
80
+ # Include via:
81
+ # include:
82
+ # - sponsio:capability/host-config-integrity
83
+ #
84
+ # Source tag convention:
85
+ # library:tier1.host-config-integrity
86
+ # =============================================================================
87
+
88
+ version: "1"
89
+
90
+ agents:
91
+ "*":
92
+ contracts:
93
+
94
+ # =================================================================
95
+ # § 1 — No agent-mediated Edit of IDE hook config
96
+ # =================================================================
97
+ #
98
+ # Path set covers the wiring files for every host Sponsio
99
+ # currently supports. Add new hosts here as ``sponsio host
100
+ # install`` learns to register them.
101
+ #
102
+ # Anchor pattern note:
103
+ #
104
+ # The ``(^|/)`` anchor at the start of each alternative
105
+ # matches both absolute paths (``/Users/x/.cursor/hooks.json``)
106
+ # and project-relative paths (``.cursor/hooks.json``). The
107
+ # ``$`` anchors at the end pin the match to exact filenames,
108
+ # so a file named ``hooks.json.bak`` or ``settings.json.tmpl``
109
+ # stays editable.
110
+ #
111
+ # The Cursor MCP config (``.cursor/mcp.json``) is included
112
+ # because it's the file that declares Sponsio's MCP server
113
+ # to Cursor — same disable-by-rewrite attack shape, different
114
+ # file. Same for Claude Code's ``hooks/`` directory.
115
+
116
+ - desc: "Host agent must not edit IDE hook / MCP wiring files — disabling Sponsio's call site is privilege escalation"
117
+ G:
118
+ pattern: arg_blacklist
119
+ args:
120
+ - Edit
121
+ - file_path
122
+ - - "(^|/)\\.cursor/hooks\\.json$"
123
+ - "(^|/)\\.cursor/mcp\\.json$"
124
+ - "(^|/)\\.claude/settings\\.json$"
125
+ - "(^|/)\\.claude/settings\\.local\\.json$"
126
+ - "(^|/)\\.claude/hooks/[^/]+\\.json$"
127
+ - "(^|/)\\.codex/hooks\\.json$"
128
+ - "(^|/)\\.windsurf/hooks\\.json$"
129
+ source: "library:tier1.host-config-integrity"
130
+
131
+ # =================================================================
132
+ # § 2 — No agent-mediated Write of IDE hook config
133
+ # =================================================================
134
+ #
135
+ # ``Write`` overwrites the whole file — closes the
136
+ # "delete-and-recreate-empty" workaround a pure ``Edit`` block
137
+ # would leave open.
138
+
139
+ - desc: "Host agent must not overwrite IDE hook / MCP wiring files — use `sponsio host install` / hand-edit"
140
+ G:
141
+ pattern: arg_blacklist
142
+ args:
143
+ - Write
144
+ - file_path
145
+ - - "(^|/)\\.cursor/hooks\\.json$"
146
+ - "(^|/)\\.cursor/mcp\\.json$"
147
+ - "(^|/)\\.claude/settings\\.json$"
148
+ - "(^|/)\\.claude/settings\\.local\\.json$"
149
+ - "(^|/)\\.claude/hooks/[^/]+\\.json$"
150
+ - "(^|/)\\.codex/hooks\\.json$"
151
+ - "(^|/)\\.windsurf/hooks\\.json$"
152
+ source: "library:tier1.host-config-integrity"
153
+
154
+ # =================================================================
155
+ # § 3 — No agent-mediated MultiEdit of IDE hook config
156
+ # =================================================================
157
+
158
+ - desc: "Host agent must not multi-edit IDE hook / MCP wiring files — use `sponsio host install` / hand-edit"
159
+ G:
160
+ pattern: arg_blacklist
161
+ args:
162
+ - MultiEdit
163
+ - file_path
164
+ - - "(^|/)\\.cursor/hooks\\.json$"
165
+ - "(^|/)\\.cursor/mcp\\.json$"
166
+ - "(^|/)\\.claude/settings\\.json$"
167
+ - "(^|/)\\.claude/settings\\.local\\.json$"
168
+ - "(^|/)\\.claude/hooks/[^/]+\\.json$"
169
+ - "(^|/)\\.codex/hooks\\.json$"
170
+ - "(^|/)\\.windsurf/hooks\\.json$"
171
+ source: "library:tier1.host-config-integrity"
172
+
173
+ # =================================================================
174
+ # § 4 — No script creation that mutates IDE hook config
175
+ # (mirror of self-modify §4, against hook paths)
176
+ # =================================================================
177
+ #
178
+ # The same lookahead-conjunction shape as self-modify §4: deny
179
+ # only if the content carries BOTH a hook-config path AND a
180
+ # write-shape idiom. Pure docs that mention the path stay
181
+ # editable — the rule is narrow on purpose.
182
+
183
+ - desc: "Host agent must not write a script that mutates IDE hook / MCP wiring files"
184
+ G:
185
+ pattern: arg_blacklist
186
+ args:
187
+ - Write
188
+ - content
189
+ - - "(?s)(?=.*\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json).*(\\.write_text\\s*\\(|\\.write_bytes\\s*\\(|\\bf\\.write\\s*\\(|\\bopen\\s*\\([^)]*['\\\"][aw][\\+bxt]*['\\\"]\\s*\\)|\\bfs\\.(writeFileSync|appendFileSync|createWriteStream|writeFile)\\s*\\(|\\bFile\\.(open|write)\\s*\\(|\\b(IO|File)\\.write\\b)"
190
+ source: "library:tier1.host-config-integrity"
191
+
192
+ - desc: "Host agent must not edit a script that mutates IDE hook / MCP wiring files"
193
+ G:
194
+ pattern: arg_blacklist
195
+ args:
196
+ - Edit
197
+ - new_string
198
+ - - "(?s)(?=.*\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json).*(\\.write_text\\s*\\(|\\.write_bytes\\s*\\(|\\bf\\.write\\s*\\(|\\bopen\\s*\\([^)]*['\\\"][aw][\\+bxt]*['\\\"]\\s*\\)|\\bfs\\.(writeFileSync|appendFileSync|createWriteStream|writeFile)\\s*\\(|\\bFile\\.(open|write)\\s*\\(|\\b(IO|File)\\.write\\b)"
199
+ source: "library:tier1.host-config-integrity"
200
+
201
+ # =================================================================
202
+ # § 5 — No interpreter invocation targeting IDE hook config
203
+ # (mirror of self-modify §5, against hook paths)
204
+ # =================================================================
205
+ #
206
+ # Catches both inline forms (``python -c "...hooks.json..."``)
207
+ # and path-as-argument forms (``python merge.py
208
+ # .cursor/hooks.json``). The ``[^|\n]*`` between the
209
+ # interpreter token and the path token excludes cross-pipeline
210
+ # matches, same idiom as self-modify §5.
211
+
212
+ - desc: "Host agent must not run an interpreter against IDE hook / MCP wiring files"
213
+ G:
214
+ pattern: arg_blacklist
215
+ args:
216
+ - Bash
217
+ - command
218
+ - - "\\b(python|python2|python3|node|deno|bun|ruby|perl|php|lua|tclsh|R|Rscript)\\b[^|\\n]*(\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json)"
219
+ source: "library:tier1.host-config-integrity"
220
+
221
+ # =================================================================
222
+ # § 6 — No bash redirect / mutator targeting IDE hook config
223
+ # =================================================================
224
+ #
225
+ # Catches the cheap shell shapes that don't go through any
226
+ # interpreter: ``echo '{}' > hooks.json``, ``cat /dev/null >
227
+ # settings.json``, ``: > hooks.json``, ``sed -i 's/true/false/'
228
+ # hooks.json``, ``tee hooks.json``, ``cp empty.json
229
+ # hooks.json``, ``mv x hooks.json``, ``rm hooks.json``,
230
+ # ``truncate -s0``, ``ln -sf /dev/null``, ``chmod 000``.
231
+ #
232
+ # The pattern is "a mutator verb, then anything up to the next
233
+ # pipe / separator, then a hook-config path on the same command
234
+ # line". The gap between verb and path is what ``~/``, ``$HOME/``,
235
+ # ``"${HOME}/``, and an absolute prefix sit in: an earlier version
236
+ # required the path to follow the redirect operator immediately,
237
+ # so ``> .claude/settings.json`` was denied and ``> ~/.claude/
238
+ # settings.json`` was not (reported by kta1kri, 2026-10).
239
+ # ``truncate``, ``ln``, ``chmod``, ``chown`` and ``chattr`` are
240
+ # mutators too: an emptied, replaced, or unreadable settings file
241
+ # removes the hook exactly as an edited one does.
242
+ #
243
+ # Read-only patterns (``cat hooks.json``, ``jq . hooks.json``,
244
+ # ``grep ... hooks.json``, ``jq . settings.json > /tmp/backup``)
245
+ # are NOT matched — they carry no mutator verb in front of the
246
+ # path, and a redirect to a different file stops at that file.
247
+ # ``cp`` with the file as the *source* does match; a backup is
248
+ # the price of catching ``cp empty.json settings.json``.
249
+ #
250
+ # Edge case: ``python ... > hooks.json`` is already caught
251
+ # by §5 via the interpreter alternation. No double-deny harm
252
+ # since this is the deny side of the contract anyway.
253
+
254
+ - desc: "Host agent must not redirect / sed / tee / mv / rm / truncate / ln / chmod an IDE hook / MCP wiring file"
255
+ G:
256
+ pattern: arg_blacklist
257
+ args:
258
+ - Bash
259
+ - command
260
+ - - ">{1,2}\\|?\\s*[^|&;<>\\n]*(\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/[^\\s|]+)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json)"
261
+ - "\\b(tee|cp|mv|rm|install|truncate|ln|chmod|chown|chattr|shred|unlink|rsync|dd|sponge|patch|ed|ex|vi|vim|nvim)\\b[^|&;\\n]*(\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/[^\\s|]+)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json)"
262
+ - "\\bsed\\b[^|&;\\n]*\\s-[a-zA-Z]*i[^|&;\\n]*(\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/[^\\s|]+)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json)"
263
+ - "\\bawk\\b[^|&;\\n]*\\s-i[^|&;\\n]*(\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/[^\\s|]+)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json)"
264
+ source: "library:tier1.host-config-integrity"
265
+
266
+ # The rules above name the wiring FILE. Removing, moving, or making
267
+ # unreadable the directory that holds it (``rm -rf ~/.claude``,
268
+ # ``chmod 000 ~/.claude``, ``mv .cursor /tmp``) takes the hook
269
+ # registration with it. The lookahead stops at the directory name
270
+ # so ``~/.claude-backup`` and ``.claude/commands/x.md`` stay open.
271
+ - desc: "Host agent must not remove, move, or lock the IDE config directory that holds the hook wiring"
272
+ G:
273
+ pattern: arg_blacklist
274
+ args:
275
+ - Bash
276
+ - command
277
+ - - "\\b(rm|rmdir|mv|chmod|chown|chattr|ln)\\b[^|&;\\n]*(\\.claude|\\.cursor|\\.codex|\\.windsurf)/?(?![\\w./-])"
278
+ source: "library:tier1.host-config-integrity"
279
+
280
+ # -----------------------------------------------------------------------------
281
+ # Integration requirements
282
+ # -----------------------------------------------------------------------------
283
+ #
284
+ # - This pack assumes the host agent's Edit / Write / MultiEdit
285
+ # tool exposes ``file_path`` (Claude Code, Cursor, Codex all
286
+ # currently do). Hosts using a different param name will need
287
+ # the per-host adjustment that ``self-modify.yaml`` already
288
+ # documents.
289
+ #
290
+ # - The path set is the union of every Sponsio-supported host's
291
+ # wiring files. A given per-host bucket only needs the entries
292
+ # matching ITS host, but keeping the union here means the pack
293
+ # stays a single drop-in include with no per-host customization
294
+ # required at the call site. False-positive cost is zero —
295
+ # ``.cursor/hooks.json`` is only present in Cursor projects, so
296
+ # a Claude Code agent's edits to a non-existent path will never
297
+ # match anyway.
298
+ #
299
+ # - The ``"*"`` agent scope means these rules apply to every agent
300
+ # in any library that includes this pack. Recommended include
301
+ # site is the per-host bucket (``_host_cursor`` /
302
+ # ``_host_claude_code``), so the rules fire on the host that
303
+ # actually has the wiring file in question.
304
+ #
305
+ # - The bash mutator regex in §6 is intentionally broader than
306
+ # self-modify §5's interpreter-only regex. That's correct: hook
307
+ # config files are JSON, so non-interpreter shell mutators
308
+ # (sed, tee, cp) are the natural attack surface and they don't
309
+ # apply to .sponsio/plugins/*.yaml the same way (yaml requires
310
+ # careful editing that bash one-liners typically don't produce
311
+ # correctly, so the attack-shape distribution differs).
312
+ #
313
+ # - Read of hook config files is permitted. If a future incident
314
+ # pack wants to deny reads (e.g. for a defense-in-depth recon
315
+ # block similar to subagent-escape §4), add it as a separate
316
+ # ``incident/host-config-recon.yaml`` rather than expanding
317
+ # this capability pack — capability packs stay narrowly scoped
318
+ # to a single privilege boundary.
@@ -19,12 +19,42 @@
19
19
  # time, but a sponsio-native rule is the right defense — it travels
20
20
  # with the user's config to any host (Cursor, Codex, custom MCP).
21
21
  #
22
+ # A second escalation vector lives one level up: an agent can
23
+ # ``import sponsio; print(sponsio.__file__)`` to locate the
24
+ # installed package, then edit the upstream capability bundle (or
25
+ # the evaluation engine itself) to delete the rule about to fire.
26
+ # This works on **any** user-writable Python install — editable
27
+ # (``pip install -e .``), ``pip install --user``, conda envs,
28
+ # venvs. Only system-wide ``/usr/lib/python*`` installs are safe
29
+ # by filesystem permission alone, and that's the minority of real
30
+ # deployments. This pack closes that vector.
31
+ #
22
32
  # What this pack denies:
23
33
  # - Edit / Write / MultiEdit on the legacy host guard config:
24
34
  # ``~/.sponsio/plugins/_host/sponsio.yaml``
25
35
  # - Edit / Write / MultiEdit on ANY per-plugin / per-MCP-server
26
36
  # library (``~/.sponsio/plugins/<plugin-id>/sponsio.yaml``
27
37
  # where ``<plugin-id>`` does NOT start with ``_host``).
38
+ # - Edit / Write / MultiEdit on the **upstream sponsio package** —
39
+ # specifically the contract bundles under
40
+ # ``sponsio/contracts/**/*.yaml`` and the evaluation-engine
41
+ # Python files (``guard_stdin``, ``config``, ``core``, plus
42
+ # anything under ``runtime/``, ``patterns/``, ``formulas/``,
43
+ # ``tracer/``, ``integrations/``, ``daemon/``, ``shim/``). Without
44
+ # this, an agent can ``import sponsio; print(sponsio.__file__)`` to locate
45
+ # the package, then edit a capability bundle (or monkey-patch
46
+ # the loader) to delete the rule about to fire — bypassing the
47
+ # per-bucket protection above.
48
+ # - Bash write operators (``>``, ``>>``, ``tee``, ``sed -i``,
49
+ # ``cp/mv/rm/dd``) targeting any of the above paths — the
50
+ # "shell shortcut" bypass of the structured Edit/Write block.
51
+ # - Bash interpreter invocations (``python``, ``node``, ``ruby``,
52
+ # ``perl``, ``deno``, ``bun``, etc.) targeting any protected
53
+ # path — the "run a script that does the write" bypass.
54
+ # - Write / Edit content that creates a script which itself
55
+ # mutates a protected path — the "stage a file then invoke it"
56
+ # two-step bypass.
57
+ #
28
58
  # The legitimate way to add or update these bundles is the
29
59
  # ``sponsio plugin install`` / ``sponsio plugin scan --apply``
30
60
  # CLI — those write via Python subprocess, not the agent's
@@ -34,6 +64,17 @@
34
64
  # own Edit/Write tools to weaken or rewrite a bundle that's
35
65
  # about to fire against it.
36
66
  #
67
+ # **Sponsio dev exception**: contributors working on the sponsio
68
+ # package itself routinely edit these files. Override in your
69
+ # project-level config:
70
+ #
71
+ # customized:
72
+ # - match: {source: "library:tier1.self-modify"}
73
+ # disabled: true
74
+ #
75
+ # This loses self-modify protection inside the sponsio repo —
76
+ # acceptable for the maintainer workflow, not for end users.
77
+ #
37
78
  # Per-host buckets (``_host_cursor`` / ``_host_claude_code`` / etc.)
38
79
  # carry their own targeted self-modify rules INSIDE their starter
39
80
  # yaml, scoped to that one bucket only. The split keeps the
@@ -56,6 +97,13 @@
56
97
  # ``sponsio onboard`` writes into the user's repo). That file
57
98
  # IS supposed to evolve — onboard / scan / refresh all rewrite
58
99
  # it. Locking it would break the core happy path.
100
+ # - Edits to non-engine sponsio package files (``cli.py``,
101
+ # ``discovery/``, ``reporting/``, ``scoring/``, ``demos/``,
102
+ # ``generation/``, ``models/``). These don't sit on the hook
103
+ # evaluation hot path — modifying them can't disable a rule
104
+ # against the agent's NEXT call. Sponsio contributors edit
105
+ # them frequently; locking them would make the dev workflow
106
+ # unworkable.
59
107
  # - Direct edits via the user's IDE, terminal, or
60
108
  # ``sponsio plugin install`` / ``plugin scan --apply`` CLI —
61
109
  # those don't go through the ``Edit`` / ``Write`` tool surface
@@ -82,11 +130,11 @@ agents:
82
130
  contracts:
83
131
 
84
132
  # =================================================================
85
- # § 1 — No agent-mediated Edit of own host guard config
133
+ # § 1 — No agent-mediated Edit of any sponsio config or engine
86
134
  # =================================================================
87
135
 
88
- - desc: "Host agent must not edit any sponsio guard config (host bucket or per-plugin bundle) — use `sponsio plugin install` / `plugin scan --apply`, or hand-edit"
89
- E:
136
+ - desc: "Host agent must not edit any sponsio guard config or engine file — use `sponsio plugin install` / `plugin scan --apply`, or hand-edit"
137
+ G:
90
138
  pattern: arg_blacklist
91
139
  args:
92
140
  - Edit
@@ -101,10 +149,17 @@ agents:
101
149
  # still manage downstream plugin policy.
102
150
  - - "(^|/)\\.sponsio/plugins/_host/sponsio\\.yaml$"
103
151
  - "(^|/)\\.sponsio/plugins/[^_/][^/]*/sponsio\\.yaml$"
152
+ # Upstream sponsio package — contract bundles (rule data)
153
+ # and evaluation-engine .py files. Anchored on
154
+ # ``(^|/)sponsio/...`` so it matches editable installs,
155
+ # site-packages, --user installs, conda/venv all the same.
156
+ - "(^|/)sponsio/contracts/[^/]+/[^/]+\\.yaml$"
157
+ - "(^|/)sponsio/(guard_stdin|config|core)\\.py$"
158
+ - "(^|/)sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py$"
104
159
  source: "library:tier1.self-modify"
105
160
 
106
161
  # =================================================================
107
- # § 2 — No agent-mediated Write of own host guard config
162
+ # § 2 — No agent-mediated Write of any sponsio config or engine
108
163
  # =================================================================
109
164
  #
110
165
  # ``Write`` is the "create or overwrite whole file" tool —
@@ -112,28 +167,219 @@ agents:
112
167
  # rules removed" workaround a pure ``Edit`` block would leave
113
168
  # open.
114
169
 
115
- - desc: "Host agent must not overwrite any sponsio guard config (host bucket or per-plugin bundle) — use the CLI or hand-edit"
116
- E:
170
+ - desc: "Host agent must not overwrite any sponsio guard config or engine file — use the CLI or hand-edit"
171
+ G:
117
172
  pattern: arg_blacklist
118
173
  args:
119
174
  - Write
120
175
  - file_path
121
176
  - - "(^|/)\\.sponsio/plugins/_host/sponsio\\.yaml$"
122
177
  - "(^|/)\\.sponsio/plugins/[^_/][^/]*/sponsio\\.yaml$"
178
+ - "(^|/)sponsio/contracts/[^/]+/[^/]+\\.yaml$"
179
+ - "(^|/)sponsio/(guard_stdin|config|core)\\.py$"
180
+ - "(^|/)sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py$"
123
181
  source: "library:tier1.self-modify"
124
182
 
125
183
  # =================================================================
126
- # § 3 — No agent-mediated MultiEdit of own host guard config
184
+ # § 3 — No agent-mediated MultiEdit of any sponsio config or engine
127
185
  # =================================================================
128
186
 
129
- - desc: "Host agent must not multi-edit any sponsio guard config (host bucket or per-plugin bundle) — use the CLI or hand-edit"
130
- E:
187
+ - desc: "Host agent must not multi-edit any sponsio guard config or engine file — use the CLI or hand-edit"
188
+ G:
131
189
  pattern: arg_blacklist
132
190
  args:
133
191
  - MultiEdit
134
192
  - file_path
135
193
  - - "(^|/)\\.sponsio/plugins/_host/sponsio\\.yaml$"
136
194
  - "(^|/)\\.sponsio/plugins/[^_/][^/]*/sponsio\\.yaml$"
195
+ - "(^|/)sponsio/contracts/[^/]+/[^/]+\\.yaml$"
196
+ - "(^|/)sponsio/(guard_stdin|config|core)\\.py$"
197
+ - "(^|/)sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py$"
198
+ source: "library:tier1.self-modify"
199
+
200
+ # =================================================================
201
+ # § 4 — No agent-mediated creation of a *script* that writes to
202
+ # a protected path (the "Python file I/O bypass")
203
+ # =================================================================
204
+ #
205
+ # Observed bypass: agent's ``Write`` to the host bucket gets denied
206
+ # by §1–§3, agent recovers by writing a small ``merge_host_*.py``
207
+ # script in the workspace whose content opens the host bucket
208
+ # path in write mode and mutates it. The script's file_path is a
209
+ # normal repo path so §1–§3 don't fire; the Bash invocation
210
+ # ``python merge.py`` doesn't carry the host bucket path on the
211
+ # command line so §6 below also doesn't fire. The signal IS
212
+ # present: the script's *content* mentions a protected path
213
+ # AND uses an open-for-write idiom.
214
+ #
215
+ # The lookahead form ``(?s)(?=.*PATH).*(WRITE_SHAPE)`` requires
216
+ # both signals in the same content blob — pure docs that mention
217
+ # the path stay editable; only files that mention the path AND
218
+ # carry write-shaped code are denied.
219
+ #
220
+ # The PATH alternation covers all four protected families:
221
+ # - host buckets (~/.sponsio/plugins/_host*/...)
222
+ # - per-plugin libs (~/.sponsio/plugins/<id>/...)
223
+ # - upstream contract bundles (sponsio/contracts/.../*.yaml)
224
+ # - upstream engine .py (sponsio/{guard_stdin,config,core}.py
225
+ # and sponsio/{runtime,patterns,formulas,tracer,
226
+ # integrations}/*.py)
227
+ #
228
+ # Tools covered: Write.content, Edit.new_string,
229
+ # MultiEdit.edits[*].new_string (the ``new_string`` projection
230
+ # that grounding extracts from MultiEdit's edit list).
231
+
232
+ - desc: "Host agent must not write a script that mutates a sponsio guard config or engine file — use `sponsio plugin append`/`install`"
233
+ G:
234
+ pattern: arg_blacklist
235
+ args:
236
+ - Write
237
+ - content
238
+ - - "(?s)(?=.*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)).*(\\.write_text\\s*\\(|\\.write_bytes\\s*\\(|\\bf\\.write\\s*\\(|\\bopen\\s*\\([^)]*['\\\"][aw][\\+bxt]*['\\\"]\\s*\\)|\\bfs\\.(writeFileSync|appendFileSync|createWriteStream|writeFile)\\s*\\(|\\bFile\\.(open|write)\\s*\\(|\\b(IO|File)\\.write\\b)"
239
+ source: "library:tier1.self-modify"
240
+
241
+ - desc: "Host agent must not edit a script that mutates a sponsio guard config or engine file — use `sponsio plugin append`/`install`"
242
+ G:
243
+ pattern: arg_blacklist
244
+ args:
245
+ - Edit
246
+ - new_string
247
+ - - "(?s)(?=.*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)).*(\\.write_text\\s*\\(|\\.write_bytes\\s*\\(|\\bf\\.write\\s*\\(|\\bopen\\s*\\([^)]*['\\\"][aw][\\+bxt]*['\\\"]\\s*\\)|\\bfs\\.(writeFileSync|appendFileSync|createWriteStream|writeFile)\\s*\\(|\\bFile\\.(open|write)\\s*\\(|\\b(IO|File)\\.write\\b)"
248
+ source: "library:tier1.self-modify"
249
+
250
+ # =================================================================
251
+ # § 5 — No interpreter invocation that targets a protected path
252
+ # =================================================================
253
+ #
254
+ # Catches the inline form (``python -c "...sponsio/contracts/..."``)
255
+ # and the path-as-argument form (``python merge.py
256
+ # ~/.sponsio/plugins/...sponsio.yaml``). Pairs with §4 — that
257
+ # rule blocks the *creation* of bypass scripts, this rule blocks
258
+ # the *invocation* path that doesn't go through a separate file.
259
+ #
260
+ # The interpreter list is the common scripting set; add new ones
261
+ # via a project-local ``customized:`` extending the regex if your
262
+ # environment uses a less common runtime. The trailing path
263
+ # regex is the same shape as §1–§3 / §6 so the privilege boundary
264
+ # stays consistent across tools.
265
+ #
266
+ # Legitimate sponsio CLIs that read the path
267
+ # (``sponsio validate --config <path>``, ``sponsio plugin
268
+ # show <name>``) are not blocked because the leading verb is
269
+ # ``sponsio``, not an interpreter alias. ``sponsio plugin
270
+ # append --target <plugin-id>`` doesn't pass the path at all —
271
+ # it takes the plugin id and resolves the path internally.
272
+
273
+ - desc: "Host agent must not run an interpreter against a sponsio guard config or engine file — use `sponsio plugin append`/`install`"
274
+ G:
275
+ pattern: arg_blacklist
276
+ args:
277
+ - Bash
278
+ - command
279
+ # ``[^|\n]*`` excludes pipe + newline so we don't match
280
+ # across pipelines, but keeps ``;`` permitted because
281
+ # Python's ``-c "..."`` payloads commonly use it as a
282
+ # statement separator (``python -c "import x; x.do()"``).
283
+ - - "\\b(python|python2|python3|node|deno|bun|ruby|perl|php|lua|tclsh|R|Rscript)\\b[^|\\n]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)"
284
+ source: "library:tier1.self-modify"
285
+
286
+ # =================================================================
287
+ # § 6 — No Bash write operators against a protected path
288
+ # =================================================================
289
+ #
290
+ # The "shell shortcut" bypass: ``echo … > sponsio/contracts/…``,
291
+ # ``sed -i … sponsio/runtime/monitor.py``, ``cp /tmp/replacement
292
+ # sponsio/patterns/library.py``. These don't go through Edit /
293
+ # Write / MultiEdit so §1–§3 don't see them. Each operator
294
+ # gets one regex, applied against the same alternation of
295
+ # protected paths as §1–§3.
296
+ #
297
+ # Operator list: redirect (``>``, ``>>``), explicit writers
298
+ # (``tee``, ``sed -i``), file movers (``cp``, ``mv``, ``rm``,
299
+ # ``dd``). The ``[^|&;]*`` segment between operator and path
300
+ # keeps each match local to its own command (won't cross
301
+ # pipelines or statement separators).
302
+
303
+ - desc: "Host agent must not redirect Bash output (`>` / `>>`) into a sponsio guard config or engine file"
304
+ G:
305
+ pattern: arg_blacklist
306
+ args:
307
+ - Bash
308
+ - command
309
+ - - ">{1,2}\\|?\\s*[^|&;<>\\n]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)"
310
+ source: "library:tier1.self-modify"
311
+
312
+ - desc: "Host agent must not `tee` into a sponsio guard config or engine file"
313
+ G:
314
+ pattern: arg_blacklist
315
+ args:
316
+ - Bash
317
+ - command
318
+ - - "\\btee\\b\\s+[^|&;]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)"
319
+ source: "library:tier1.self-modify"
320
+
321
+ - desc: "Host agent must not `sed -i` a sponsio guard config or engine file"
322
+ G:
323
+ pattern: arg_blacklist
324
+ args:
325
+ - Bash
326
+ - command
327
+ - - "\\bsed\\b\\s+-i\\b[^|&;]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)"
328
+ source: "library:tier1.self-modify"
329
+
330
+ - desc: "Host agent must not `cp` / `mv` / `rm` / `dd` against a sponsio guard config or engine file"
331
+ G:
332
+ pattern: arg_blacklist
333
+ args:
334
+ - Bash
335
+ - command
336
+ - - "\\b(cp|mv|rm|dd)\\b\\s+[^|&;]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)"
337
+ source: "library:tier1.self-modify"
338
+
339
+ # A file can be emptied, replaced, or made unreadable without a
340
+ # write operator: ``truncate -s0``, ``ln -sf /dev/null``,
341
+ # ``chmod 000`` (reported by kta1kri, 2026-10, against the hook
342
+ # wiring; the rule file has the same exposure). The hook fails
343
+ # closed on an empty or unreadable library, so for the rule file
344
+ # these are denial of service rather than bypass, but they are
345
+ # the agent reaching for its own guard all the same.
346
+ - desc: "Host agent must not truncate / link over / lock a sponsio guard config or engine file"
347
+ G:
348
+ pattern: arg_blacklist
349
+ args:
350
+ - Bash
351
+ - command
352
+ - - "\\b(truncate|ln|chmod|chown|chattr|shred|unlink|install|rsync|sponge|patch|ed|ex|vi|vim|nvim)\\b\\s+[^|&;]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations|daemon|shim)/[^/]+\\.py)"
353
+ source: "library:tier1.self-modify"
354
+
355
+ # The rules above name the rule FILE. Removing or moving a directory
356
+ # that contains it (``rm -rf ~/.sponsio``, ``mv ~/.sponsio/plugins
357
+ # /tmp``) took every library with it, after which each tool was
358
+ # "unconfigured" and ran, and ``defaults.unconfigured: deny`` went
359
+ # too, since it lives in the host library. The lookahead stops at
360
+ # the directory name so ``~/.sponsio-backup`` stays allowed.
361
+ - desc: "Host agent must not remove, move, or lock the sponsio config directory or a library bucket"
362
+ G:
363
+ pattern: arg_blacklist
364
+ args:
365
+ - Bash
366
+ - command
367
+ - - "\\b(rm|mv|rmdir|chmod|chown|chattr|ln)\\b[^|&;\\n]*\\.sponsio(/plugins(/[\\w-]+)?)?/?(?![\\w./-])"
368
+ source: "library:tier1.self-modify"
369
+
370
+ # The daemon holds a rule change that removes a contract until
371
+ # someone runs `sponsio rules reload`. At Tier 1 that someone could
372
+ # be the agent, approving its own weakening; the same goes for
373
+ # starting a daemon that applies every change (`--watch`), switching
374
+ # a host to observe, or removing the hooks. These belong to the
375
+ # person at their own terminal.
376
+ - desc: "Host agent must not approve its own rule changes, or switch Sponsio to observe or off"
377
+ G:
378
+ pattern: arg_blacklist
379
+ args:
380
+ - Bash
381
+ - command
382
+ - - "\\bsponsio\\b[^|&;\\n]*\\b(rules\\s+reload|admin\\b|daemon\\s+run\\b[^|&;\\n]*--watch|host\\s+uninstall|host\\s+install\\b[^|&;\\n]*--mode[\\s=]+observe)"
137
383
  source: "library:tier1.self-modify"
138
384
 
139
385
  # -----------------------------------------------------------------------------
@@ -150,3 +396,8 @@ agents:
150
396
  # trying to touch this same file is covered by the broader
151
397
  # ``sponsio:incident/subagent-escape`` pack, which denies the
152
398
  # ENTIRE ``~/.sponsio/`` tree to sub-agents.
399
+ #
400
+ # - Upstream package paths are matched WITHOUT the install prefix —
401
+ # ``(^|/)sponsio/contracts/...`` matches editable installs, site-
402
+ # packages, --user installs, conda/venv all the same. The first
403
+ # ``sponsio/`` directory boundary anchors the match cleanly.