@yolk-sdk/emulators 0.1.0-canary.96

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 (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2135 -0
  3. package/dist/anthropic.d.mts +269 -0
  4. package/dist/anthropic.d.mts.map +1 -0
  5. package/dist/anthropic.mjs +177 -0
  6. package/dist/anthropic.mjs.map +1 -0
  7. package/dist/chat-completions.d.mts +288 -0
  8. package/dist/chat-completions.d.mts.map +1 -0
  9. package/dist/chat-completions.mjs +451 -0
  10. package/dist/chat-completions.mjs.map +1 -0
  11. package/dist/codex.d.mts +265 -0
  12. package/dist/codex.d.mts.map +1 -0
  13. package/dist/codex.mjs +199 -0
  14. package/dist/codex.mjs.map +1 -0
  15. package/dist/dropbox/api.d.mts +63 -0
  16. package/dist/dropbox/api.d.mts.map +1 -0
  17. package/dist/dropbox/api.mjs +565 -0
  18. package/dist/dropbox/api.mjs.map +1 -0
  19. package/dist/dropbox/state.d.mts +136 -0
  20. package/dist/dropbox/state.d.mts.map +1 -0
  21. package/dist/dropbox/state.mjs +209 -0
  22. package/dist/dropbox/state.mjs.map +1 -0
  23. package/dist/dropbox.d.mts +79 -0
  24. package/dist/dropbox.d.mts.map +1 -0
  25. package/dist/dropbox.mjs +124 -0
  26. package/dist/dropbox.mjs.map +1 -0
  27. package/dist/email-fixtures.d.mts +36 -0
  28. package/dist/email-fixtures.d.mts.map +1 -0
  29. package/dist/email-fixtures.mjs +1080 -0
  30. package/dist/email-fixtures.mjs.map +1 -0
  31. package/dist/email.d.mts +160 -0
  32. package/dist/email.d.mts.map +1 -0
  33. package/dist/email.mjs +608 -0
  34. package/dist/email.mjs.map +1 -0
  35. package/dist/emulator-compose.d.mts +40 -0
  36. package/dist/emulator-compose.d.mts.map +1 -0
  37. package/dist/emulator-compose.mjs +91 -0
  38. package/dist/emulator-compose.mjs.map +1 -0
  39. package/dist/emulator-http.d.mts +53 -0
  40. package/dist/emulator-http.d.mts.map +1 -0
  41. package/dist/emulator-http.mjs +117 -0
  42. package/dist/emulator-http.mjs.map +1 -0
  43. package/dist/emulator-kernel.d.mts +176 -0
  44. package/dist/emulator-kernel.d.mts.map +1 -0
  45. package/dist/emulator-kernel.mjs +413 -0
  46. package/dist/emulator-kernel.mjs.map +1 -0
  47. package/dist/fixture-route.d.mts +129 -0
  48. package/dist/fixture-route.d.mts.map +1 -0
  49. package/dist/fixture-route.mjs +257 -0
  50. package/dist/fixture-route.mjs.map +1 -0
  51. package/dist/fortnox/api.d.mts +92 -0
  52. package/dist/fortnox/api.d.mts.map +1 -0
  53. package/dist/fortnox/api.mjs +750 -0
  54. package/dist/fortnox/api.mjs.map +1 -0
  55. package/dist/fortnox/state.d.mts +533 -0
  56. package/dist/fortnox/state.d.mts.map +1 -0
  57. package/dist/fortnox/state.mjs +612 -0
  58. package/dist/fortnox/state.mjs.map +1 -0
  59. package/dist/fortnox.d.mts +128 -0
  60. package/dist/fortnox.d.mts.map +1 -0
  61. package/dist/fortnox.mjs +403 -0
  62. package/dist/fortnox.mjs.map +1 -0
  63. package/dist/gateway-evaluate-recordings.d.mts +12 -0
  64. package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
  65. package/dist/gateway-evaluate-recordings.mjs +128 -0
  66. package/dist/gateway-evaluate-recordings.mjs.map +1 -0
  67. package/dist/gateway.d.mts +272 -0
  68. package/dist/gateway.d.mts.map +1 -0
  69. package/dist/gateway.mjs +400 -0
  70. package/dist/gateway.mjs.map +1 -0
  71. package/dist/github/api.d.mts +63 -0
  72. package/dist/github/api.d.mts.map +1 -0
  73. package/dist/github/api.mjs +527 -0
  74. package/dist/github/api.mjs.map +1 -0
  75. package/dist/github/state.d.mts +272 -0
  76. package/dist/github/state.d.mts.map +1 -0
  77. package/dist/github/state.mjs +303 -0
  78. package/dist/github/state.mjs.map +1 -0
  79. package/dist/github.d.mts +77 -0
  80. package/dist/github.d.mts.map +1 -0
  81. package/dist/github.mjs +180 -0
  82. package/dist/github.mjs.map +1 -0
  83. package/dist/google/calendar.d.mts +10 -0
  84. package/dist/google/calendar.d.mts.map +1 -0
  85. package/dist/google/calendar.mjs +252 -0
  86. package/dist/google/calendar.mjs.map +1 -0
  87. package/dist/google/drive.d.mts +8 -0
  88. package/dist/google/drive.d.mts.map +1 -0
  89. package/dist/google/drive.mjs +252 -0
  90. package/dist/google/drive.mjs.map +1 -0
  91. package/dist/google/gmail.d.mts +10 -0
  92. package/dist/google/gmail.d.mts.map +1 -0
  93. package/dist/google/gmail.mjs +591 -0
  94. package/dist/google/gmail.mjs.map +1 -0
  95. package/dist/google/shared.d.mts +107 -0
  96. package/dist/google/shared.d.mts.map +1 -0
  97. package/dist/google/shared.mjs +146 -0
  98. package/dist/google/shared.mjs.map +1 -0
  99. package/dist/google/state.d.mts +385 -0
  100. package/dist/google/state.d.mts.map +1 -0
  101. package/dist/google/state.mjs +496 -0
  102. package/dist/google/state.mjs.map +1 -0
  103. package/dist/google.d.mts +75 -0
  104. package/dist/google.d.mts.map +1 -0
  105. package/dist/google.mjs +210 -0
  106. package/dist/google.mjs.map +1 -0
  107. package/dist/linkedin-search/api.d.mts +45 -0
  108. package/dist/linkedin-search/api.d.mts.map +1 -0
  109. package/dist/linkedin-search/api.mjs +181 -0
  110. package/dist/linkedin-search/api.mjs.map +1 -0
  111. package/dist/linkedin-search/state.d.mts +123 -0
  112. package/dist/linkedin-search/state.d.mts.map +1 -0
  113. package/dist/linkedin-search/state.mjs +261 -0
  114. package/dist/linkedin-search/state.mjs.map +1 -0
  115. package/dist/linkedin-search.d.mts +69 -0
  116. package/dist/linkedin-search.d.mts.map +1 -0
  117. package/dist/linkedin-search.mjs +158 -0
  118. package/dist/linkedin-search.mjs.map +1 -0
  119. package/dist/mcp/api.d.mts +44 -0
  120. package/dist/mcp/api.d.mts.map +1 -0
  121. package/dist/mcp/api.mjs +557 -0
  122. package/dist/mcp/api.mjs.map +1 -0
  123. package/dist/mcp/recordings.d.mts +40 -0
  124. package/dist/mcp/recordings.d.mts.map +1 -0
  125. package/dist/mcp/recordings.mjs +2390 -0
  126. package/dist/mcp/recordings.mjs.map +1 -0
  127. package/dist/mcp/state.d.mts +71 -0
  128. package/dist/mcp/state.d.mts.map +1 -0
  129. package/dist/mcp/state.mjs +83 -0
  130. package/dist/mcp/state.mjs.map +1 -0
  131. package/dist/mcp.d.mts +84 -0
  132. package/dist/mcp.d.mts.map +1 -0
  133. package/dist/mcp.mjs +241 -0
  134. package/dist/mcp.mjs.map +1 -0
  135. package/dist/messages.d.mts +183 -0
  136. package/dist/messages.d.mts.map +1 -0
  137. package/dist/messages.mjs +532 -0
  138. package/dist/messages.mjs.map +1 -0
  139. package/dist/microsoft/api.d.mts +47 -0
  140. package/dist/microsoft/api.d.mts.map +1 -0
  141. package/dist/microsoft/api.mjs +178 -0
  142. package/dist/microsoft/api.mjs.map +1 -0
  143. package/dist/microsoft/calendar.d.mts +30 -0
  144. package/dist/microsoft/calendar.d.mts.map +1 -0
  145. package/dist/microsoft/calendar.mjs +271 -0
  146. package/dist/microsoft/calendar.mjs.map +1 -0
  147. package/dist/microsoft/drive.d.mts +36 -0
  148. package/dist/microsoft/drive.d.mts.map +1 -0
  149. package/dist/microsoft/drive.mjs +298 -0
  150. package/dist/microsoft/drive.mjs.map +1 -0
  151. package/dist/microsoft/graph.d.mts +198 -0
  152. package/dist/microsoft/graph.d.mts.map +1 -0
  153. package/dist/microsoft/graph.mjs +264 -0
  154. package/dist/microsoft/graph.mjs.map +1 -0
  155. package/dist/microsoft/mail.d.mts +48 -0
  156. package/dist/microsoft/mail.d.mts.map +1 -0
  157. package/dist/microsoft/mail.mjs +488 -0
  158. package/dist/microsoft/mail.mjs.map +1 -0
  159. package/dist/microsoft/state.d.mts +480 -0
  160. package/dist/microsoft/state.d.mts.map +1 -0
  161. package/dist/microsoft/state.mjs +625 -0
  162. package/dist/microsoft/state.mjs.map +1 -0
  163. package/dist/microsoft.d.mts +168 -0
  164. package/dist/microsoft.d.mts.map +1 -0
  165. package/dist/microsoft.mjs +483 -0
  166. package/dist/microsoft.mjs.map +1 -0
  167. package/dist/node.d.mts +47 -0
  168. package/dist/node.d.mts.map +1 -0
  169. package/dist/node.mjs +178 -0
  170. package/dist/node.mjs.map +1 -0
  171. package/dist/notion/api.d.mts +51 -0
  172. package/dist/notion/api.d.mts.map +1 -0
  173. package/dist/notion/api.mjs +507 -0
  174. package/dist/notion/api.mjs.map +1 -0
  175. package/dist/notion/state.d.mts +329 -0
  176. package/dist/notion/state.d.mts.map +1 -0
  177. package/dist/notion/state.mjs +432 -0
  178. package/dist/notion/state.mjs.map +1 -0
  179. package/dist/notion.d.mts +72 -0
  180. package/dist/notion.d.mts.map +1 -0
  181. package/dist/notion.mjs +127 -0
  182. package/dist/notion.mjs.map +1 -0
  183. package/dist/openai.d.mts +165 -0
  184. package/dist/openai.d.mts.map +1 -0
  185. package/dist/openai.mjs +141 -0
  186. package/dist/openai.mjs.map +1 -0
  187. package/dist/opencode-recordings.d.mts +7 -0
  188. package/dist/opencode-recordings.d.mts.map +1 -0
  189. package/dist/opencode-recordings.mjs +222 -0
  190. package/dist/opencode-recordings.mjs.map +1 -0
  191. package/dist/opencode.d.mts +70 -0
  192. package/dist/opencode.d.mts.map +1 -0
  193. package/dist/opencode.mjs +217 -0
  194. package/dist/opencode.mjs.map +1 -0
  195. package/dist/r2-fixtures.d.mts +36 -0
  196. package/dist/r2-fixtures.d.mts.map +1 -0
  197. package/dist/r2-fixtures.mjs +245 -0
  198. package/dist/r2-fixtures.mjs.map +1 -0
  199. package/dist/r2-guard.d.mts +43 -0
  200. package/dist/r2-guard.d.mts.map +1 -0
  201. package/dist/r2-guard.mjs +215 -0
  202. package/dist/r2-guard.mjs.map +1 -0
  203. package/dist/r2.d.mts +166 -0
  204. package/dist/r2.d.mts.map +1 -0
  205. package/dist/r2.mjs +517 -0
  206. package/dist/r2.mjs.map +1 -0
  207. package/dist/responses.d.mts +231 -0
  208. package/dist/responses.d.mts.map +1 -0
  209. package/dist/responses.mjs +556 -0
  210. package/dist/responses.mjs.map +1 -0
  211. package/dist/route-evidence.d.mts +45 -0
  212. package/dist/route-evidence.d.mts.map +1 -0
  213. package/dist/route-evidence.mjs +56 -0
  214. package/dist/route-evidence.mjs.map +1 -0
  215. package/dist/router.d.mts +93 -0
  216. package/dist/router.d.mts.map +1 -0
  217. package/dist/router.mjs +259 -0
  218. package/dist/router.mjs.map +1 -0
  219. package/dist/stateful-core.d.mts +17 -0
  220. package/dist/stateful-core.d.mts.map +1 -0
  221. package/dist/stateful-core.mjs +41 -0
  222. package/dist/stateful-core.mjs.map +1 -0
  223. package/dist/stateful-emulator.d.mts +565 -0
  224. package/dist/stateful-emulator.d.mts.map +1 -0
  225. package/dist/stateful-emulator.mjs +1228 -0
  226. package/dist/stateful-emulator.mjs.map +1 -0
  227. package/dist/stateful-secrets.d.mts +84 -0
  228. package/dist/stateful-secrets.d.mts.map +1 -0
  229. package/dist/stateful-secrets.mjs +216 -0
  230. package/dist/stateful-secrets.mjs.map +1 -0
  231. package/dist/subscription-usage-recordings.d.mts +9 -0
  232. package/dist/subscription-usage-recordings.d.mts.map +1 -0
  233. package/dist/subscription-usage-recordings.mjs +53 -0
  234. package/dist/subscription-usage-recordings.mjs.map +1 -0
  235. package/dist/subscription-usage.d.mts +53 -0
  236. package/dist/subscription-usage.d.mts.map +1 -0
  237. package/dist/subscription-usage.mjs +21 -0
  238. package/dist/subscription-usage.mjs.map +1 -0
  239. package/dist/telegram/api.d.mts +34 -0
  240. package/dist/telegram/api.d.mts.map +1 -0
  241. package/dist/telegram/api.mjs +257 -0
  242. package/dist/telegram/api.mjs.map +1 -0
  243. package/dist/telegram/state.d.mts +128 -0
  244. package/dist/telegram/state.d.mts.map +1 -0
  245. package/dist/telegram/state.mjs +154 -0
  246. package/dist/telegram/state.mjs.map +1 -0
  247. package/dist/telegram.d.mts +62 -0
  248. package/dist/telegram.d.mts.map +1 -0
  249. package/dist/telegram.mjs +127 -0
  250. package/dist/telegram.mjs.map +1 -0
  251. package/dist/todoist/api.d.mts +55 -0
  252. package/dist/todoist/api.d.mts.map +1 -0
  253. package/dist/todoist/api.mjs +415 -0
  254. package/dist/todoist/api.mjs.map +1 -0
  255. package/dist/todoist/state.d.mts +275 -0
  256. package/dist/todoist/state.d.mts.map +1 -0
  257. package/dist/todoist/state.mjs +345 -0
  258. package/dist/todoist/state.mjs.map +1 -0
  259. package/dist/todoist.d.mts +68 -0
  260. package/dist/todoist.d.mts.map +1 -0
  261. package/dist/todoist.mjs +181 -0
  262. package/dist/todoist.mjs.map +1 -0
  263. package/dist/xai.d.mts +267 -0
  264. package/dist/xai.d.mts.map +1 -0
  265. package/dist/xai.mjs +252 -0
  266. package/dist/xai.mjs.map +1 -0
  267. package/package.json +157 -0
  268. package/src/anthropic.ts +289 -0
  269. package/src/chat-completions.ts +924 -0
  270. package/src/codex.ts +296 -0
  271. package/src/dropbox/api.ts +1084 -0
  272. package/src/dropbox/state.ts +364 -0
  273. package/src/dropbox.ts +203 -0
  274. package/src/email-fixtures.ts +1297 -0
  275. package/src/email.ts +1108 -0
  276. package/src/emulator-compose.ts +147 -0
  277. package/src/emulator-http.ts +184 -0
  278. package/src/emulator-kernel.ts +844 -0
  279. package/src/fixture-route.ts +561 -0
  280. package/src/fortnox/api.ts +1352 -0
  281. package/src/fortnox/state.ts +798 -0
  282. package/src/fortnox.ts +801 -0
  283. package/src/gateway-evaluate-recordings.ts +153 -0
  284. package/src/gateway.ts +530 -0
  285. package/src/github/api.ts +986 -0
  286. package/src/github/state.ts +439 -0
  287. package/src/github.ts +271 -0
  288. package/src/google/calendar.ts +486 -0
  289. package/src/google/drive.ts +471 -0
  290. package/src/google/gmail.ts +1011 -0
  291. package/src/google/shared.ts +321 -0
  292. package/src/google/state.ts +686 -0
  293. package/src/google.ts +298 -0
  294. package/src/linkedin-search/api.ts +363 -0
  295. package/src/linkedin-search/state.ts +373 -0
  296. package/src/linkedin-search.ts +241 -0
  297. package/src/mcp/api.ts +995 -0
  298. package/src/mcp/recordings.ts +2665 -0
  299. package/src/mcp/state.ts +125 -0
  300. package/src/mcp.ts +319 -0
  301. package/src/messages.ts +844 -0
  302. package/src/microsoft/api.ts +426 -0
  303. package/src/microsoft/calendar.ts +491 -0
  304. package/src/microsoft/drive.ts +541 -0
  305. package/src/microsoft/graph.ts +550 -0
  306. package/src/microsoft/mail.ts +833 -0
  307. package/src/microsoft/state.ts +822 -0
  308. package/src/microsoft.ts +982 -0
  309. package/src/node.ts +293 -0
  310. package/src/notion/api.ts +976 -0
  311. package/src/notion/state.ts +512 -0
  312. package/src/notion.ts +210 -0
  313. package/src/openai.ts +198 -0
  314. package/src/opencode-recordings.ts +257 -0
  315. package/src/opencode.ts +271 -0
  316. package/src/r2-fixtures.ts +295 -0
  317. package/src/r2-guard.ts +323 -0
  318. package/src/r2.ts +890 -0
  319. package/src/responses.ts +901 -0
  320. package/src/route-evidence.ts +90 -0
  321. package/src/router.ts +490 -0
  322. package/src/stateful-core.ts +70 -0
  323. package/src/stateful-emulator.ts +2571 -0
  324. package/src/stateful-secrets.ts +299 -0
  325. package/src/subscription-usage-recordings.ts +77 -0
  326. package/src/subscription-usage.ts +68 -0
  327. package/src/telegram/api.ts +437 -0
  328. package/src/telegram/state.ts +229 -0
  329. package/src/telegram.ts +227 -0
  330. package/src/todoist/api.ts +736 -0
  331. package/src/todoist/state.ts +456 -0
  332. package/src/todoist.ts +316 -0
  333. package/src/xai.ts +341 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * The credential guard of the R2 port emulator (internal; used by `r2.ts`, and imported by
3
+ * `test/r2.test.ts` for the exhaustive parity tests).
4
+ *
5
+ * The copied lists here mirror `@yolk-sdk/conformance` (`isPortCredentialKey`, the credential query
6
+ * parameter names of `hasLiveCredentialParam`, `bearerPattern`, and `apiKeyPatterns`) and the R2
7
+ * conformance guard `findR2PortFixtureSecrets` (canonical placeholders and decodings). Emulators
8
+ * never import SDK code, so `test/r2.test.ts` holds one sample per entry of every list here,
9
+ * asserts the sample count equals the list length, and checks each sample against the shared
10
+ * scan; a new entry without a sample fails.
11
+ *
12
+ * @experimental
13
+ */
14
+ import { Predicate } from 'effect'
15
+ import type * as Schema from 'effect/Schema'
16
+ import { textClosureOutcome } from './stateful-secrets.ts'
17
+
18
+ type JsonObject = Schema.JsonObject
19
+
20
+ const isJsonObject = (value: Schema.Json): value is JsonObject =>
21
+ value !== null && Predicate.isObject(value) && !Array.isArray(value)
22
+
23
+ /**
24
+ * The credential field names of the shared port scan (`isPortCredentialKey`): `credential(s)`
25
+ * plus the credential field names, AWS-style `accessKeyId` / `secretAccessKey` / `sessionToken`
26
+ * included. One regular-expression source per name; matched whole and case-insensitively.
27
+ */
28
+ export const r2CredentialKeyNames: ReadonlyArray<string> = [
29
+ 'credentials?',
30
+ 'access[_-]?token',
31
+ 'refresh[_-]?token',
32
+ 'id[_-]?token',
33
+ 'auth[_-]?token',
34
+ 'api[_-]?token',
35
+ 'session[_-]?token',
36
+ 'private[_-]?token',
37
+ 'bearer[_-]?token',
38
+ 'oauth[_-]?token',
39
+ 'token',
40
+ 'client[_-]?secret',
41
+ 'secret(?:[_-]?key)?',
42
+ 'private[_-]?key',
43
+ 'password',
44
+ 'passwd',
45
+ 'api[_-]?key',
46
+ 'access[_-]?key[_-]?id',
47
+ 'secret[_-]?access[_-]?key',
48
+ 'authorization'
49
+ ]
50
+
51
+ const credentialKeyPattern = new RegExp(`^(?:${r2CredentialKeyNames.join('|')})$`, 'i')
52
+
53
+ /** True for a key whose whole value is a credential (dropped, never compared or recorded). */
54
+ export const isR2CredentialKey = (key: string): boolean => credentialKeyPattern.test(key)
55
+
56
+ /**
57
+ * The credential query or form parameter names of the shared port scan (`hasLiveCredentialParam`).
58
+ * One regular-expression source per name.
59
+ */
60
+ export const r2CredentialParamNames: ReadonlyArray<string> = [
61
+ 'api[_-]?key',
62
+ 'key',
63
+ 'token',
64
+ 'access[_-]?token',
65
+ 'refresh[_-]?token',
66
+ 'id[_-]?token',
67
+ 'auth',
68
+ 'secret',
69
+ 'password',
70
+ 'client[_-]?secret',
71
+ 'x-amz-signature',
72
+ 'x-amz-credential',
73
+ 'x-amz-security-token'
74
+ ]
75
+
76
+ // A credential parameter: a name at the start or after `?` / `&`, `=`, and a value (any character
77
+ // but `&` or `#`).
78
+ const credentialParamAt = new RegExp(`(?:^|[?&])(?:${r2CredentialParamNames.join('|')})=[^&#]`, 'i')
79
+
80
+ /**
81
+ * The shared scan's token patterns (`bearerPattern` and `apiKeyPatterns`): a bearer token, common
82
+ * API-key prefixes, JSON Web Tokens, and PEM private keys.
83
+ */
84
+ export const r2TokenPatterns: ReadonlyArray<RegExp> = [
85
+ /\bbearer\s+[A-Za-z0-9._~+/=-]{8,}/i,
86
+ /\bsk-[A-Za-z0-9_-]{16,}/,
87
+ /\b[sr]k_(live|test)_[A-Za-z0-9]{16,}/,
88
+ /\bxai-[A-Za-z0-9]{20,}/,
89
+ /\bvck_[A-Za-z0-9]{16,}/,
90
+ /\bgh[pousr]_[A-Za-z0-9]{20,}/,
91
+ /\bgithub_pat_[A-Za-z0-9_]{20,}/,
92
+ /\bAKIA[0-9A-Z]{16}\b/,
93
+ /\bAIza[0-9A-Za-z_-]{35}/,
94
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}/,
95
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/,
96
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----/
97
+ ]
98
+
99
+ // The SigV4 rules of the R2 conformance guard (`findR2PortFixtureSecrets` in
100
+ // `@yolk-sdk/connectors/r2-storage/conformance`), copied: the exact canonical placeholder
101
+ // occurrences are blanked out, then any credential name left in any decoding layer is refused.
102
+
103
+ /** The canonical placeholders (`syntheticPortCredentialParams`), as the fixtures write them. */
104
+ const canonicalPlaceholders = new Map<string, string>([
105
+ ['x-amz-signature', 'yolk-synthetic-signature'],
106
+ [
107
+ 'x-amz-credential',
108
+ encodeURIComponent('yolk-synthetic-access-key-id/20260930/auto/s3/aws4_request')
109
+ ]
110
+ ])
111
+
112
+ const canonicalNamesAt = /(^|[?&])(x-amz-signature|x-amz-credential)=/gi
113
+
114
+ const canonicalValueEnd = /^(?:[&#\s"<>]|$)/
115
+
116
+ const withoutCanonicalPlaceholders = (text: string): string => {
117
+ let masked = text
118
+
119
+ for (const match of text.matchAll(canonicalNamesAt)) {
120
+ const nameStart = match.index + (match[1] ?? '').length
121
+ const valueStart = match.index + match[0].length
122
+ const placeholder = canonicalPlaceholders.get((match[2] ?? '').toLowerCase()) ?? ''
123
+ const valueEnd = valueStart + placeholder.length
124
+
125
+ if (
126
+ placeholder.length > 0 &&
127
+ text.startsWith(placeholder, valueStart) &&
128
+ canonicalValueEnd.test(text.slice(valueEnd))
129
+ ) {
130
+ const blank = ' '.repeat(valueEnd - nameStart)
131
+
132
+ masked = `${masked.slice(0, nameStart)}${blank}${masked.slice(valueEnd)}`
133
+ }
134
+ }
135
+
136
+ return masked
137
+ }
138
+
139
+ const percentDecodedOnce = (text: string): string =>
140
+ text.replace(/%([0-9A-Fa-f]{2})/g, (_match, hex: string) =>
141
+ String.fromCharCode(Number.parseInt(hex, 16))
142
+ )
143
+
144
+ const codePoint = (value: number): string => String.fromCodePoint(Math.min(value, 0x10ffff))
145
+
146
+ const escapesDecodedOnce = (text: string): string =>
147
+ text.replace(
148
+ /\\u([0-9A-Fa-f]{4})|\\x([0-9A-Fa-f]{2})|\\\\|&#[xX]([0-9A-Fa-f]{1,6});?|&#([0-9]{1,7});?/g,
149
+ (match, unicode?: string, hex?: string, entityHex?: string, entity?: string) => {
150
+ const hexDigits = unicode ?? hex ?? entityHex
151
+
152
+ if (hexDigits !== undefined) return codePoint(Number.parseInt(hexDigits, 16))
153
+
154
+ if (entity !== undefined) return codePoint(Number.parseInt(entity, 10))
155
+
156
+ return match === '\\\\' ? '\\' : match
157
+ }
158
+ )
159
+
160
+ const maxRoundsPerDecoding = 3
161
+
162
+ /** `text` and every variant within three percent rounds and three escape rounds, in any order. */
163
+ const decodedVariants = (text: string): ReadonlyArray<string> => {
164
+ const seen = new Set<string>([text])
165
+
166
+ let frontier: ReadonlyArray<{ text: string; percent: number; escapes: number }> = [
167
+ { text, percent: 0, escapes: 0 }
168
+ ]
169
+
170
+ while (frontier.length > 0) {
171
+ frontier = frontier.flatMap(variant =>
172
+ [
173
+ ...(variant.percent < maxRoundsPerDecoding
174
+ ? [{ ...variant, text: percentDecodedOnce(variant.text), percent: variant.percent + 1 }]
175
+ : []),
176
+ ...(variant.escapes < maxRoundsPerDecoding
177
+ ? [{ ...variant, text: escapesDecodedOnce(variant.text), escapes: variant.escapes + 1 }]
178
+ : [])
179
+ ].filter(candidate => {
180
+ if (seen.has(candidate.text)) return false
181
+
182
+ seen.add(candidate.text)
183
+
184
+ return true
185
+ })
186
+ )
187
+ }
188
+
189
+ return [...seen]
190
+ }
191
+
192
+ const sigV4CredentialNames = /x-amz-credential|x-amz-signature|x-amz-security-token/i
193
+
194
+ /** True when `text` holds a SigV4 credential name, a credential parameter, or a token. */
195
+ const credentialText = (text: string): boolean =>
196
+ sigV4CredentialNames.test(text) ||
197
+ credentialParamAt.test(text) ||
198
+ r2TokenPatterns.some(pattern => pattern.test(text))
199
+
200
+ /**
201
+ * True when `text`, once its exact canonical placeholder occurrences are blanked out, holds a
202
+ * credential (`credentialText`) raw, in any variant within three percent and three escape rounds
203
+ * (the R2 guard's decodings), or anywhere in the fail-closed closure of `textClosureOutcome`
204
+ * (any depth of percent-encoding and JSON escaping; a work cap counts as a credential).
205
+ */
206
+ const carriesCredential = (text: string): boolean => {
207
+ const masked = withoutCanonicalPlaceholders(text)
208
+
209
+ return (
210
+ decodedVariants(masked).some(credentialText) ||
211
+ textClosureOutcome(masked, credentialText) !== 'clear'
212
+ )
213
+ }
214
+
215
+ /**
216
+ * True when `text` repeats a guarded value: raw, in any of R2's own decodings (`decodedVariants`:
217
+ * percent, `\uXXXX` of any code point, `\xXX`, numeric HTML references, in any order), or
218
+ * anywhere in the shared closure (a work cap counts as a repeat). Every non-empty value is
219
+ * guarded: unlike the shared `textRepeatsSecret`, there is no minimum length.
220
+ */
221
+ const repeatsGuardedValue = (text: string, guarded: ReadonlyArray<string>): boolean => {
222
+ if (guarded.length === 0) return false
223
+
224
+ const holds = (candidate: string) => guarded.some(value => candidate.includes(value))
225
+
226
+ return decodedVariants(text).some(holds) || textClosureOutcome(text, holds) !== 'clear'
227
+ }
228
+
229
+ /** True when `text` carries a credential or repeats a guarded value. */
230
+ export const textCarriesCredential = (text: string, guarded: ReadonlyArray<string>): boolean =>
231
+ carriesCredential(text) || repeatsGuardedValue(text, guarded)
232
+
233
+ const numericText = /^[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/
234
+
235
+ /**
236
+ * The texts a number is matched by: as JavaScript prints it (`1e+21`, `12345678`) and, for an
237
+ * integer, its full digit string (`1000000000000000000000`).
238
+ */
239
+ export const numberTexts = (value: number): ReadonlyArray<string> =>
240
+ Number.isSafeInteger(value) || !Number.isInteger(value)
241
+ ? [String(value)]
242
+ : [String(value), BigInt(value).toString()]
243
+
244
+ /** A guarded value and, when it is numeric text (`0012345678`), the forms of its number. */
245
+ const guardedForms = (value: string): ReadonlyArray<string> => {
246
+ if (value.length === 0) return []
247
+
248
+ const number = Number(value)
249
+
250
+ return numericText.test(value) && Number.isFinite(number)
251
+ ? [value, ...numberTexts(number)]
252
+ : [value]
253
+ }
254
+
255
+ /** Every key, string, and number under a credential field, at any depth (the values to guard). */
256
+ const credentialSubtree = (value: Schema.Json): ReadonlyArray<string> => {
257
+ if (Predicate.isString(value)) return guardedForms(value)
258
+
259
+ if (Predicate.isNumber(value)) return numberTexts(value)
260
+
261
+ if (Array.isArray(value)) return value.flatMap(credentialSubtree)
262
+
263
+ return isJsonObject(value)
264
+ ? Object.entries(value).flatMap(([key, item]) => [
265
+ ...guardedForms(key),
266
+ ...credentialSubtree(item)
267
+ ])
268
+ : []
269
+ }
270
+
271
+ /** The guarded values of every credential field of `value`, at any depth, deduplicated. */
272
+ export const guardedValues = (value: Schema.Json): ReadonlyArray<string> => {
273
+ const collect = (item: Schema.Json): ReadonlyArray<string> => {
274
+ if (Array.isArray(item)) return item.flatMap(collect)
275
+
276
+ if (!isJsonObject(item)) return []
277
+
278
+ return Object.entries(item).flatMap(([key, child]) =>
279
+ isR2CredentialKey(key) ? credentialSubtree(child) : collect(child)
280
+ )
281
+ }
282
+
283
+ return [...new Set(collect(value))]
284
+ }
285
+
286
+ /** True when `value` holds an own `__proto__` key at any depth (an admission bypass otherwise). */
287
+ export const hasOwnProtoKey = (value: Schema.Json): boolean => {
288
+ if (Array.isArray(value)) return value.some(hasOwnProtoKey)
289
+
290
+ if (!isJsonObject(value)) return false
291
+
292
+ return Object.hasOwn(value, '__proto__') || Object.values(value).some(hasOwnProtoKey)
293
+ }
294
+
295
+ const strippedOf = (value: Schema.Json): Schema.Json => {
296
+ if (Array.isArray(value)) return value.map(strippedOf)
297
+
298
+ return isJsonObject(value) ? withoutCredentials(value) : value
299
+ }
300
+
301
+ /**
302
+ * A copy without credential fields, at any depth (`redactPortPayload` of the shared scan), built
303
+ * with own data properties only (`Object.fromEntries`).
304
+ */
305
+ export const withoutCredentials = (value: JsonObject): JsonObject =>
306
+ Object.fromEntries(
307
+ Object.entries(value).flatMap(([key, item]) =>
308
+ isR2CredentialKey(key) ? [] : [[key, strippedOf(item)]]
309
+ )
310
+ )
311
+
312
+ /** Every object key, string value, and number (`numberTexts`) of `value`. */
313
+ export const textsOf = (value: Schema.Json): ReadonlyArray<string> => {
314
+ if (Predicate.isString(value)) return [value]
315
+
316
+ if (Predicate.isNumber(value)) return numberTexts(value)
317
+
318
+ if (Array.isArray(value)) return value.flatMap(textsOf)
319
+
320
+ return isJsonObject(value)
321
+ ? Object.entries(value).flatMap(([key, item]) => [key, ...textsOf(item)])
322
+ : []
323
+ }