@zackbart/connecta 0.34.1 → 0.36.0

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 (441) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/README.md +65 -105
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2044 -1161
  23. package/dist/auth/downstream-revocation.js +88 -88
  24. package/dist/auth/native-call.js +20 -0
  25. package/dist/auth/native.js +184 -0
  26. package/dist/auth/oauth-start-reset.js +11 -5
  27. package/dist/auth/oauth-token-response.js +121 -102
  28. package/dist/auth/oauth-v2-migration.js +97 -78
  29. package/dist/auth/renewable-grant.js +10 -0
  30. package/dist/auth/static-oauth.js +751 -281
  31. package/dist/auth/vercel-agent.js +266 -0
  32. package/dist/auth-admission.js +204 -0
  33. package/dist/auth-elicitation.js +350 -241
  34. package/dist/authorization-handoff.js +164 -90
  35. package/dist/call-admission-context.js +3 -10
  36. package/dist/call-admission.js +2 -0
  37. package/dist/call-classification.js +9 -0
  38. package/dist/call-recovery.js +32 -13
  39. package/dist/canonical-json.js +8 -0
  40. package/dist/catalog-cache.js +369 -213
  41. package/dist/catalog-service.js +659 -530
  42. package/dist/catalog.js +5 -1
  43. package/dist/concurrency.js +0 -25
  44. package/dist/config-defaults.d.ts +1 -0
  45. package/dist/config-defaults.js +1 -0
  46. package/dist/config-schema.d.ts +3 -1
  47. package/dist/config-schema.js +13 -3
  48. package/dist/config.d.ts +40 -6
  49. package/dist/config.js +40 -26
  50. package/dist/connector-caller.d.ts +4 -0
  51. package/dist/connector-caller.js +17 -4
  52. package/dist/connectors/api-connector.d.ts +29 -7
  53. package/dist/connectors/api-connector.js +145 -57
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/remote-mcp.d.ts +10 -30
  58. package/dist/connectors/remote-mcp.js +955 -563
  59. package/dist/d1.js +10 -243
  60. package/dist/derived-context.js +5 -0
  61. package/dist/describe-config.d.ts +3 -1
  62. package/dist/describe-config.js +2 -0
  63. package/dist/downstream-input.js +347 -260
  64. package/dist/errors.d.ts +87 -6
  65. package/dist/errors.js +114 -4
  66. package/dist/execute.js +87 -50
  67. package/dist/executor-admission.js +3 -0
  68. package/dist/executors/quickjs.js +163 -112
  69. package/dist/guarded-transport.js +50 -0
  70. package/dist/guest-runtime.js +1 -1
  71. package/dist/http/client.js +439 -0
  72. package/dist/http/errors.js +558 -0
  73. package/dist/http/fields.js +49 -0
  74. package/dist/http/hatch-paging.js +318 -0
  75. package/dist/http/hatch.js +1279 -0
  76. package/dist/http/json-schema.js +267 -0
  77. package/dist/http/node-transport.js +241 -0
  78. package/dist/http/oauth.js +162 -0
  79. package/dist/http/paging.js +227 -0
  80. package/dist/http/provider.js +221 -0
  81. package/dist/http/safety.js +82 -0
  82. package/dist/http/services.js +79 -0
  83. package/dist/http/tools.js +379 -0
  84. package/dist/http/vendor-index.js +138 -0
  85. package/dist/http/vendor.js +448 -0
  86. package/dist/http/write-request.js +66 -0
  87. package/dist/http-policy.d.ts +11 -0
  88. package/dist/http-policy.js +45 -0
  89. package/dist/inbound-credential.js +1 -1
  90. package/dist/index.d.ts +2 -2
  91. package/dist/index.js +4 -0
  92. package/dist/invocation-auth.js +30 -19
  93. package/dist/invocation.js +218 -85
  94. package/dist/mcp-result.js +0 -4
  95. package/dist/membership.d.ts +4 -0
  96. package/dist/meta-output.js +1 -1
  97. package/dist/meta-tools.js +531 -406
  98. package/dist/native-recovery.js +52 -0
  99. package/dist/native-tools.js +382 -0
  100. package/dist/node.d.ts +4 -3
  101. package/dist/node.js +45 -8
  102. package/dist/oauth-handoff.js +177 -104
  103. package/dist/oauth-partition.js +6 -20
  104. package/dist/oauth-sealing.js +6 -7
  105. package/dist/operation-vocabulary.js +101 -0
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +17 -4
  108. package/dist/operator-ui/contract.d.ts +8 -3
  109. package/dist/operator-ui/generated.js +4 -4
  110. package/dist/operator-ui/model.d.ts +2 -3
  111. package/dist/program-writes.js +47 -26
  112. package/dist/provider-call-facts.d.ts +115 -0
  113. package/dist/provider-call-facts.js +182 -0
  114. package/dist/provider-call.d.ts +112 -0
  115. package/dist/provider-call.js +271 -0
  116. package/dist/provider.d.ts +65 -31
  117. package/dist/provider.js +116 -39
  118. package/dist/providers/_shared/google/hatch.js +91 -0
  119. package/dist/providers/_shared/google/kit.js +427 -0
  120. package/dist/providers/_shared/google/result-size.js +15 -2
  121. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  122. package/dist/providers/_shared/google/service-account.js +8 -4
  123. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  124. package/dist/providers/_shared/google/workspace.js +6 -425
  125. package/dist/providers/_shared/rest/compact-index.js +86 -0
  126. package/dist/providers/_shared/rest/kit.js +146 -0
  127. package/dist/providers/_shared/rest/operation-index.js +230 -14
  128. package/dist/providers/_shared/rest/response-media.js +11 -0
  129. package/dist/providers/_shared/rest/value-safety.js +353 -195
  130. package/dist/providers/_shared/sensitive-fields.js +27 -0
  131. package/dist/providers/breeze/api.js +584 -0
  132. package/dist/providers/breeze/index.d.ts +1 -3
  133. package/dist/providers/breeze/index.js +67 -1420
  134. package/dist/providers/breeze/openapi.generated.js +60 -0
  135. package/dist/providers/breeze/skill.generated.js +2 -6
  136. package/dist/providers/breeze/tools.js +696 -0
  137. package/dist/providers/ccb/api.js +496 -0
  138. package/dist/providers/ccb/index.d.ts +15 -37
  139. package/dist/providers/ccb/index.js +77 -1702
  140. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  141. package/dist/providers/ccb/lib/constants.js +74 -0
  142. package/dist/providers/ccb/lib/guide.js +9 -0
  143. package/dist/providers/ccb/lib/verdicts.js +387 -0
  144. package/dist/providers/ccb/openapi.generated.js +397 -0
  145. package/dist/providers/ccb/safety.js +295 -0
  146. package/dist/providers/ccb/skill.generated.js +2 -6
  147. package/dist/providers/ccb/tools.js +853 -0
  148. package/dist/providers/cloudflare/api.js +1066 -0
  149. package/dist/providers/cloudflare/index.d.ts +5 -22
  150. package/dist/providers/cloudflare/index.js +79 -197
  151. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  152. package/dist/providers/cloudflare/lib/config.js +2 -0
  153. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  154. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  155. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  156. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  157. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  158. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  159. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  160. package/dist/providers/cloudflare/skill.generated.js +2 -10
  161. package/dist/providers/cloudflare/tools.js +360 -0
  162. package/dist/providers/docs/api.js +51 -0
  163. package/dist/providers/docs/index.d.ts +1 -12
  164. package/dist/providers/docs/index.js +53 -1165
  165. package/dist/providers/docs/lib/constants.d.ts +18 -0
  166. package/dist/providers/docs/lib/constants.js +18 -0
  167. package/dist/providers/docs/lib/discovery.js +26 -0
  168. package/dist/providers/docs/lib/requests.js +48 -0
  169. package/dist/providers/docs/lib/schemas.js +168 -0
  170. package/dist/providers/docs/openapi.generated.js +16 -0
  171. package/dist/providers/docs/safety.js +54 -0
  172. package/dist/providers/docs/skill.generated.js +2 -6
  173. package/dist/providers/docs/tools.js +801 -0
  174. package/dist/providers/drive/api.js +177 -0
  175. package/dist/providers/drive/index.d.ts +1 -12
  176. package/dist/providers/drive/index.js +38 -1448
  177. package/dist/providers/drive/lib/constants.d.ts +6 -0
  178. package/dist/providers/drive/lib/constants.js +6 -0
  179. package/dist/providers/drive/lib/contracts.js +532 -0
  180. package/dist/providers/drive/lib/hatch.js +197 -0
  181. package/dist/providers/drive/openapi.generated.js +77 -0
  182. package/dist/providers/drive/safety.js +90 -0
  183. package/dist/providers/drive/skill.generated.js +2 -6
  184. package/dist/providers/drive/tools.js +1290 -0
  185. package/dist/providers/forms/api.js +71 -0
  186. package/dist/providers/forms/index.d.ts +1 -12
  187. package/dist/providers/forms/index.js +49 -1145
  188. package/dist/providers/forms/lib/constants.d.ts +14 -0
  189. package/dist/providers/forms/lib/constants.js +19 -0
  190. package/dist/providers/forms/lib/hatch.js +72 -0
  191. package/dist/providers/forms/openapi.generated.js +23 -0
  192. package/dist/providers/forms/safety.js +34 -0
  193. package/dist/providers/forms/skill.generated.js +2 -8
  194. package/dist/providers/forms/tools.js +1023 -0
  195. package/dist/providers/github/api.js +150 -0
  196. package/dist/providers/github/index.d.ts +4 -5
  197. package/dist/providers/github/index.js +30 -87
  198. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  199. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  200. package/dist/providers/github/lib/discovery.js +132 -0
  201. package/dist/providers/github/lib/operations.js +58 -0
  202. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  203. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  204. package/dist/providers/github/openapi.generated.js +1216 -0
  205. package/dist/providers/github/safety.js +216 -0
  206. package/dist/providers/github/skill.generated.js +2 -5
  207. package/dist/providers/github/tools.js +506 -0
  208. package/dist/providers/gmail/api.js +114 -0
  209. package/dist/providers/gmail/index.d.ts +1 -12
  210. package/dist/providers/gmail/index.js +73 -1621
  211. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  212. package/dist/providers/gmail/lib/constants.js +16 -0
  213. package/dist/providers/gmail/lib/index.js +173 -0
  214. package/dist/providers/gmail/lib/schemas.js +176 -0
  215. package/dist/providers/gmail/lib/threading.js +120 -0
  216. package/dist/providers/gmail/openapi.generated.js +92 -0
  217. package/dist/providers/gmail/safety.js +21 -0
  218. package/dist/providers/gmail/skill.generated.js +2 -6
  219. package/dist/providers/gmail/tools.js +1435 -0
  220. package/dist/providers/overflow/api.js +1008 -0
  221. package/dist/providers/overflow/index.d.ts +1 -17
  222. package/dist/providers/overflow/index.js +60 -1488
  223. package/dist/providers/overflow/skill.generated.js +2 -5
  224. package/dist/providers/overflow/tools.js +873 -0
  225. package/dist/providers/planning-center/api.js +467 -0
  226. package/dist/providers/planning-center/index.d.ts +4 -35
  227. package/dist/providers/planning-center/index.js +69 -2445
  228. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  229. package/dist/providers/planning-center/lib/constants.js +83 -0
  230. package/dist/providers/planning-center/lib/discovery.js +40 -0
  231. package/dist/providers/planning-center/lib/guide.js +39 -0
  232. package/dist/providers/planning-center/lib/json-api.js +83 -0
  233. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  234. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  235. package/dist/providers/planning-center/safety.js +188 -0
  236. package/dist/providers/planning-center/skill.generated.js +2 -27
  237. package/dist/providers/planning-center/tools.js +1010 -0
  238. package/dist/providers/quickbooks/api.js +299 -0
  239. package/dist/providers/quickbooks/index.d.ts +13 -0
  240. package/dist/providers/quickbooks/index.js +93 -0
  241. package/dist/providers/quickbooks/lib/query.js +118 -0
  242. package/dist/providers/quickbooks/lib/writes.js +168 -0
  243. package/dist/providers/quickbooks/safety.js +222 -0
  244. package/dist/providers/quickbooks/skill.generated.js +5 -0
  245. package/dist/providers/quickbooks/tools.js +117 -0
  246. package/dist/providers/sheets/api.js +133 -0
  247. package/dist/providers/sheets/index.d.ts +4 -16
  248. package/dist/providers/sheets/index.js +61 -1172
  249. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  250. package/dist/providers/sheets/lib/constants.js +14 -0
  251. package/dist/providers/sheets/lib/hatch.js +163 -0
  252. package/dist/providers/sheets/lib/requests.js +105 -0
  253. package/dist/providers/sheets/lib/schemas.js +138 -0
  254. package/dist/providers/sheets/openapi.generated.js +30 -0
  255. package/dist/providers/sheets/safety.js +27 -0
  256. package/dist/providers/sheets/skill.generated.js +2 -7
  257. package/dist/providers/sheets/tools.js +733 -0
  258. package/dist/providers/slides/api.js +147 -0
  259. package/dist/providers/slides/index.d.ts +2 -10
  260. package/dist/providers/slides/index.js +48 -2555
  261. package/dist/providers/slides/lib/constants.d.ts +11 -0
  262. package/dist/providers/slides/lib/constants.js +11 -0
  263. package/dist/providers/slides/lib/requests.js +60 -0
  264. package/dist/providers/slides/lib/schemas.js +565 -0
  265. package/dist/providers/slides/openapi.generated.js +18 -0
  266. package/dist/providers/slides/skill.generated.js +2 -6
  267. package/dist/providers/slides/tools.js +1968 -0
  268. package/dist/providers/stripe/api.js +401 -0
  269. package/dist/providers/stripe/index.d.ts +5 -27
  270. package/dist/providers/stripe/index.js +74 -210
  271. package/dist/providers/stripe/lib/config.d.ts +4 -0
  272. package/dist/providers/stripe/lib/config.js +2 -0
  273. package/dist/providers/stripe/openapi.generated.js +4 -3
  274. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  275. package/dist/providers/stripe/skill.generated.js +2 -11
  276. package/dist/providers/stripe/tools.js +77 -0
  277. package/dist/providers/tithely/api.js +531 -0
  278. package/dist/providers/tithely/index.d.ts +17 -0
  279. package/dist/providers/tithely/index.js +86 -965
  280. package/dist/providers/tithely/skill.generated.js +2 -5
  281. package/dist/providers/tithely/tools.js +426 -0
  282. package/dist/providers/vercel/api.js +551 -0
  283. package/dist/providers/vercel/index.d.ts +9 -25
  284. package/dist/providers/vercel/index.js +65 -260
  285. package/dist/providers/vercel/lib/config.d.ts +2 -0
  286. package/dist/providers/vercel/lib/config.js +2 -0
  287. package/dist/providers/vercel/lib/index.js +117 -0
  288. package/dist/providers/vercel/lib/logs.js +100 -0
  289. package/dist/providers/vercel/lib/pin.js +270 -0
  290. package/dist/providers/vercel/openapi.generated.js +10 -5
  291. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  292. package/dist/providers/vercel/skill.generated.js +2 -7
  293. package/dist/providers/vercel/tools.js +642 -0
  294. package/dist/redaction-identity.js +47 -0
  295. package/dist/redaction-marker.js +125 -0
  296. package/dist/registry.d.ts +21 -15
  297. package/dist/registry.js +409 -376
  298. package/dist/routes/access-tokens.js +1 -1
  299. package/dist/routes/actor-labels.js +1 -1
  300. package/dist/routes/connect.js +1 -1
  301. package/dist/routes/dispatch.js +103 -0
  302. package/dist/routes/mcp.js +170 -79
  303. package/dist/routes/oauth.js +6 -1
  304. package/dist/routes/operator.js +2 -2
  305. package/dist/routes/shared.d.ts +5 -4
  306. package/dist/routes/shared.js +244 -177
  307. package/dist/routes/skills.js +10 -8
  308. package/dist/routes/ui-config.js +27 -3
  309. package/dist/routes/ui-data.js +21 -20
  310. package/dist/routes/ui.js +1 -1
  311. package/dist/runtime/call-admission.js +0 -26
  312. package/dist/runtime/call-entry.js +30 -0
  313. package/dist/runtime/call.js +552 -0
  314. package/dist/runtime/connector-scope.js +7 -0
  315. package/dist/runtime/credential-probe.js +34 -0
  316. package/dist/runtime/executor.js +40 -0
  317. package/dist/runtime/native-storage.js +63 -0
  318. package/dist/runtime/registry.js +27 -0
  319. package/dist/runtime/request-scope.js +72 -0
  320. package/dist/runtime/run.js +15 -7
  321. package/dist/runtime/shared-read.js +34 -5
  322. package/dist/runtime/storage.js +34 -21
  323. package/dist/sent-secrets.d.ts +194 -37
  324. package/dist/sent-secrets.js +1743 -340
  325. package/dist/server.js +5 -56
  326. package/dist/skills.js +322 -223
  327. package/dist/sqlite.js +15 -14
  328. package/dist/storage/d1-copy.js +255 -0
  329. package/dist/storage/d1-driver.js +15 -0
  330. package/dist/storage/keys.js +28 -0
  331. package/dist/storage/sql-driver.js +88 -0
  332. package/dist/storage/sql-native.js +544 -0
  333. package/dist/storage/sql-schema.d.ts +6 -0
  334. package/dist/storage/sql-schema.js +15 -0
  335. package/dist/storage/sql.d.ts +14 -20
  336. package/dist/storage/sql.js +29 -535
  337. package/dist/timeout.js +0 -26
  338. package/dist/tool-safety.js +20 -10
  339. package/dist/types.d.ts +9 -109
  340. package/dist/ui.d.ts +1 -1
  341. package/dist/ui.js +0 -1
  342. package/dist/upward.js +3 -0
  343. package/dist/usage-guide.js +3 -1
  344. package/dist/validate.d.ts +4 -2
  345. package/dist/validate.js +86 -9
  346. package/dist/version.d.ts +1 -1
  347. package/dist/version.js +1 -1
  348. package/dist/worker.js +312 -292
  349. package/dist/workspace-subject.js +5 -1
  350. package/dist/write-outcome.js +454 -34
  351. package/documentation/README.md +28 -7
  352. package/documentation/architecture.md +2409 -255
  353. package/documentation/auth.md +867 -114
  354. package/documentation/code-mode.md +49 -16
  355. package/documentation/deploying.md +164 -20
  356. package/documentation/hosted-mcp-migration.md +460 -0
  357. package/documentation/integrating.md +129 -144
  358. package/documentation/meta-tools.md +409 -32
  359. package/documentation/operating.md +27 -3
  360. package/documentation/operator-tests.md +3 -0
  361. package/documentation/operator-ui.md +84 -48
  362. package/documentation/provider-auth-migration.md +16 -63
  363. package/documentation/provider-migration-0.29.md +10 -5
  364. package/documentation/provider-migration-0.35.md +975 -0
  365. package/documentation/providers.md +1347 -0
  366. package/examples/worker/AGENTS.md +23 -9
  367. package/examples/worker/README.md +254 -135
  368. package/examples/worker/alchemy.run.ts +116 -0
  369. package/examples/worker/gitignore +10 -0
  370. package/examples/worker/src/connecta.config.ts +23 -21
  371. package/examples/worker/wrangler.jsonc +14 -5
  372. package/guides/connecting-agents.md +157 -0
  373. package/guides/how-it-works.md +156 -0
  374. package/guides/safety-and-trust.md +153 -0
  375. package/package.json +30 -50
  376. package/templates/node/.env.example +3 -0
  377. package/templates/node/README.md +22 -2
  378. package/templates/node/docker-compose.yml +1 -0
  379. package/templates/node/package.json +3 -2
  380. package/templates/node/src/connecta.config.ts +8 -0
  381. package/dist/catalog-drift.d.ts +0 -138
  382. package/dist/catalog-drift.js +0 -578
  383. package/dist/operator-ui/agents.d.ts +0 -74
  384. package/dist/operator-ui/agents.js +0 -104
  385. package/dist/operator-ui/config-summary.js +0 -565
  386. package/dist/operator-ui/connection-map.js +0 -376
  387. package/dist/operator-ui/endpoints.js +0 -221
  388. package/dist/operator-ui/fix-prompts.d.ts +0 -18
  389. package/dist/operator-ui/fix-prompts.js +0 -87
  390. package/dist/operator-ui/health.js +0 -52
  391. package/dist/operator-ui/member-page.js +0 -97
  392. package/dist/operator-ui/overview.js +0 -123
  393. package/dist/operator-ui/setup-commands.js +0 -51
  394. package/dist/operator-ui/view.d.ts +0 -401
  395. package/dist/operator-ui/view.js +0 -705
  396. package/dist/providers/_shared/request-token.js +0 -83
  397. package/dist/providers/_shared/rest/dispatch.js +0 -41
  398. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  399. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  400. package/dist/providers/_shared/rest/tools.js +0 -569
  401. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  402. package/dist/providers/basecamp/index.d.ts +0 -45
  403. package/dist/providers/basecamp/index.js +0 -997
  404. package/dist/providers/basecamp/skill.generated.js +0 -9
  405. package/dist/providers/cloudflare/rest.d.ts +0 -37
  406. package/dist/providers/cloudflare/rest.js +0 -1097
  407. package/dist/providers/github/hosted.js +0 -218
  408. package/dist/providers/github/rest.js +0 -321
  409. package/dist/providers/infisical/index.d.ts +0 -7
  410. package/dist/providers/infisical/index.js +0 -713
  411. package/dist/providers/infisical/skill.generated.js +0 -8
  412. package/dist/providers/intercom/index.d.ts +0 -24
  413. package/dist/providers/intercom/index.js +0 -121
  414. package/dist/providers/intercom/skill.generated.js +0 -8
  415. package/dist/providers/linear/index.d.ts +0 -55
  416. package/dist/providers/linear/index.js +0 -537
  417. package/dist/providers/linear/skill.generated.js +0 -8
  418. package/dist/providers/mixpanel/index.d.ts +0 -59
  419. package/dist/providers/mixpanel/index.js +0 -472
  420. package/dist/providers/mixpanel/operations.js +0 -20
  421. package/dist/providers/mixpanel/rest.js +0 -603
  422. package/dist/providers/mixpanel/skill.generated.js +0 -10
  423. package/dist/providers/mixpanel/value-safety.js +0 -16
  424. package/dist/providers/notion/index.d.ts +0 -67
  425. package/dist/providers/notion/index.js +0 -368
  426. package/dist/providers/notion/openapi.generated.js +0 -75
  427. package/dist/providers/notion/rest.d.ts +0 -24
  428. package/dist/providers/notion/rest.js +0 -1226
  429. package/dist/providers/notion/skill.generated.js +0 -11
  430. package/dist/providers/notion/value-safety.js +0 -56
  431. package/dist/providers/revenuecat/index.d.ts +0 -51
  432. package/dist/providers/revenuecat/index.js +0 -697
  433. package/dist/providers/revenuecat/skill.generated.js +0 -11
  434. package/dist/providers/stripe/rest.d.ts +0 -14
  435. package/dist/providers/stripe/rest.js +0 -402
  436. package/dist/providers/vercel/rest.d.ts +0 -20
  437. package/dist/providers/vercel/rest.js +0 -1020
  438. package/dist/providers/wispr-flow/index.d.ts +0 -14
  439. package/dist/providers/wispr-flow/index.js +0 -117
  440. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  441. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -28,15 +28,25 @@
28
28
  // statuses but never its text (`withholdsErrors`), unless its review
29
29
  // found its errors cannot echo a stored secret (`vendorErrors`).
30
30
  //
31
- // Residual risk, as with Infisical: a secret a person typed into a free-text
31
+ // Residual risk: a secret a person typed into a free-text
32
32
  // name, description, log line, or identifier is out of scope.
33
33
  //
34
+ // Sent-secret redaction runs first, in the provider kit's parse pass, so a
35
+ // rule that decides on a value can find the placeholder where the vendor
36
+ // wrote what the rule matches. Every such decision input fails closed
37
+ // (#703): an altered type discriminator applies every resource rule; an
38
+ // altered label or type makes a secret record or a header rule; an altered
39
+ // URL scheme or parameter name withholds the whole URL or its value; an
40
+ // altered key withholds its value; a placeholder that may stand for a
41
+ // credential's public prefix withholds the remainder after it.
42
+ //
34
43
  // The key-name rules combine Cloudflare's and Vercel's former engines.
35
44
  // URL policy and exemptions are reviewed per vendor in its table (`keep`,
36
45
  // `fields`, `scopeMaps`, `envBodies`, `embeddedUrls`).
37
46
  import { ConnectorCallError } from "../../../errors.js";
38
- /** What a removed secret reads as. */
39
- export const REDACTED = "[redacted]";
47
+ import { bareReferenceEnd, characterReferenceEnd, REDACTED, redactionAltered, splitRemainder, } from "../../../redaction-marker.js";
48
+ /** What a removed secret reads as: the placeholder sent-secret redaction writes too. */
49
+ export { REDACTED };
40
50
  /** Credential vocabulary over normalized names, matched anywhere; mirrors `scripts/value-safety.mjs`. */
41
51
  export const CREDENTIAL_WORDS = [
42
52
  "token",
@@ -127,6 +137,8 @@ const LABELLED_VALUE_FIELDS = [...ENV_VALUE_FIELDS, "args", "text"];
127
137
  const URL_PARAM_CONTAINS = /token|secret|password|passwd|signature|^sig$|key|auth|code|jwt|credential|bypass/i;
128
138
  const URL_PARAM_SUFFIX = /(token|secret|password|passphrase|key|signature|sig|credential|auth|authorization|jwt)$/;
129
139
  const URL_SHAPE = /^[a-z][a-z0-9+.-]*:\/\//i;
140
+ /** What makes a string text rather than one whole URL: whitespace, quotes, escapes, entities, Markdown. */
141
+ const TEXT = /[\s<>"'`\\]|&(?:#|[a-z]+;)|\]\(/i;
130
142
  /** The fields of a header, cookie, or query rule that hold the matched or set value. */
131
143
  const HEADER_RULE_VALUES = ["value", "values", "args"];
132
144
  /** Keys whose absolute URL is a route or redirect destination. */
@@ -195,6 +207,48 @@ export function credentialName(name) {
195
207
  function isRecord(value) {
196
208
  return typeof value === "object" && value !== null && !Array.isArray(value);
197
209
  }
210
+ /** A rule's test over one decision input; an input redaction altered passes it (#703). */
211
+ function decides(input, test) {
212
+ return typeof input === "string" && (redactionAltered(input) || test(input));
213
+ }
214
+ /** Value safety's own `@keys` placeholders, which no redaction of a vendor's key wrote. */
215
+ const PLACEHOLDER_KEYS = new WeakSet();
216
+ /**
217
+ * Whether redaction altered one of a record's keys, so no key-based decision
218
+ * about it can be read (#703). Parsed JSON keeps its keys; text the kit
219
+ * redacted whole and a result hook parsed after it (NDJSON, log streams) can
220
+ * carry the placeholder in one.
221
+ */
222
+ function keysAltered(value) {
223
+ return !PLACEHOLDER_KEYS.has(value) && Object.keys(value).some(redactionAltered);
224
+ }
225
+ /** What may come before a URL's first placeholder when redaction took part of its scheme or authority. */
226
+ const SCHEME_PREFIX = /^(?:[a-z][a-z0-9+.-]*(?::\/{0,2})?)?$/i;
227
+ /**
228
+ * Whether redaction altered the scheme a whole-string URL decision reads
229
+ * (#703): the text is no longer URL-shaped, everything before its first
230
+ * placeholder could begin a URL, and more than placeholders follows it. A
231
+ * URL a vendor sends holds no whitespace.
232
+ */
233
+ function alteredScheme(text) {
234
+ if (!redactionAltered(text) || URL_SHAPE.test(text) || /\s/.test(text))
235
+ return false;
236
+ const at = text.indexOf(REDACTED);
237
+ return SCHEME_PREFIX.test(text.slice(0, at)) && text.slice(at).replaceAll(REDACTED, "") !== "";
238
+ }
239
+ /**
240
+ * Every place a parameter name can sit in a URL's path, query, or fragment:
241
+ * each run before an `=` since the last `/`, `?`, `&`, `#`, `;`, or `=`. A
242
+ * name redaction altered, or a separator it took, leaves the placeholder in
243
+ * one of them.
244
+ */
245
+ function parameterNames(text) {
246
+ return text.split(/[/?&#;]/).flatMap((part) => part.split("=").slice(0, -1));
247
+ }
248
+ /** Whether a fragment carries a placeholder other than the one value safety writes for a withheld fragment. */
249
+ function alteredFragment(fragment) {
250
+ return redactionAltered(fragment) && fragment !== REDACTED;
251
+ }
198
252
  /** A URL without userinfo, credential query parameters, or a credential-looking fragment; anything unparseable is withheld. */
199
253
  function sanitizeUrl(value, credentials = true) {
200
254
  let url;
@@ -224,8 +278,24 @@ function sanitizeUrl(value, credentials = true) {
224
278
  // An ordinary URL comes back exactly as the vendor sent it.
225
279
  return changed ? url.toString() : value;
226
280
  }
227
- /** Signing parameters that are a credential in any URL, with their `=` plain or encoded. */
228
- const SIGNING_PARAM = /x-(?:amz|goog)-(?:signature|credential|security-token)(?:=|%(?:25)*3d|&#0*61;?|&#x0*3d;?|&equals;?|\\u003d)/gi;
281
+ // Embedded URLs in reviewed REST text lose credential-bearing queries and
282
+ // fragments. AWS and Google signing parameters are withheld globally. The
283
+ // destination scanner also drives generic write admission for copied placeholders.
284
+ /** An `=`, plain or encoded, as a signing parameter's name ends with one. */
285
+ const EQUALS = /=|%(?:25)*3d|&(?:amp(?:;|(?=#))|#0*38;?|#x0*26;?)*(?:#0*61;?|#x0*3d;?|equals;?)|\\u003d/iy;
286
+ const EQUALS_BEFORE = new RegExp(`(?:${EQUALS.source})$`, "i");
287
+ const AMP_REFERENCE = String.raw `&(?:amp(?:;|(?=#))|#0*38;?|#x0*26;?)*`;
288
+ /** Fixed ASCII name units, matched in source coordinates without decoding or rewriting the text. */
289
+ function signingName(text) {
290
+ return Array.from(text, (char) => {
291
+ const codes = [...new Set([char.toLowerCase().charCodeAt(0), char.toUpperCase().charCodeAt(0)])];
292
+ const hex = codes.map((code) => code.toString(16)).join("|");
293
+ const named = char === "-" ? "|(?:hyphen|dash);" : "";
294
+ return `(?:${char === "." ? "\\." : char}|%(?:25)*(?:${hex})|${AMP_REFERENCE}(?:#0*(?:${codes.join("|")});?|#x0*(?:${hex});?${named})|\\\\(?:u00|x)(?:${hex}))`;
295
+ }).join("");
296
+ }
297
+ /** Established signing names and withheld suffixes, retaining each unit's source spelling. */
298
+ const SIGNING_PARAM = new RegExp(`${signingName("x-")}(?:${signingName("amz")}|${signingName("goog")})${signingName("-")}(?:(?:${["signature", "credential", "security-token"].map(signingName).join("|")})(?:${EQUALS.source})|\\[redacted\\])`, "gi");
229
299
  /**
230
300
  * One encoded character at the start of link text: a percent escape, an HTML
231
301
  * character reference, a `\u` or `\x` escape, or a Markdown backslash escape.
@@ -237,8 +307,6 @@ const TRAILING_ESCAPE = /(?:%(?:25){0,4}([0-9a-f]{2})|&(?:#0{0,6}(\d{1,7})|#x0{0
237
307
  const ESCAPES = /%([0-9a-f]{2})|&(?:#(\d{1,7})|#x([0-9a-f]{1,6})|([a-z]{2,8}));?|\\u([0-9a-f]{4})|\\x([0-9a-f]{2})|\\(?=[^a-z0-9])/gi;
238
308
  /** One slash however it is spelled, read forwards. */
239
309
  const SLASH = /\/|\\\/|%(?:25){0,4}2f|&#0{0,6}47;?|&#x0{0,6}2f;?|&sol;?|\\u002f|\\x2f/iy;
240
- /** An S3 endpoint host: global, regional, dual-stack, or accelerated. */
241
- const S3_ENDPOINT = String.raw `s3(?:[.-](?:dualstack|accelerate|external-1|[a-z]{2}(?:-[a-z]{1,12}){1,3}-\d{1,2})){0,2}\.amazonaws\.com`;
242
310
  const NAMED = {
243
311
  amp: "&",
244
312
  colon: ":",
@@ -259,10 +327,6 @@ const NAMED = {
259
327
  rsqb: "]",
260
328
  period: ".",
261
329
  };
262
- /** A host name's label character: the subdomain labels before a reviewed name are these alone. */
263
- const LABEL = /[a-z0-9.-]/i;
264
- /** The longest host name DNS allows: the bound on authority inspection. */
265
- const MAX_HOST = 253;
266
330
  /** The longest scheme read back from its `:`. */
267
331
  const MAX_SCHEME = 32;
268
332
  const SCHEME_CHAR = /[a-z0-9+.-]/i;
@@ -347,33 +411,6 @@ function authorityAt(text, at) {
347
411
  start--;
348
412
  return start < colon && (start === 0 || OPENS_LINK.test(text[start - 1])) ? start : -1;
349
413
  }
350
- /** Where a host's optional trailing dot and port end after `at`, or `-1` when a `:` holds no port (userinfo's password). */
351
- function afterPort(text, at) {
352
- if (text[at] === ".")
353
- at++;
354
- if (text[at] !== ":")
355
- return at;
356
- let digits = at + 1;
357
- while (digits - at <= 5 && /\d/.test(text[digits] ?? ""))
358
- digits++;
359
- return digits > at + 1 ? digits : -1;
360
- }
361
- /**
362
- * Whether a host or path segment ends at `at`: a slash, query, or fragment
363
- * however spelled, or the link's end. Never another
364
- * name character or an `@`: `file.notion.so@evil.com` names userinfo, and
365
- * `prod-files-secure@other` and `prod-files-securex` are other segments.
366
- */
367
- function boundary(text, at) {
368
- if (at < 0)
369
- return false;
370
- if (at >= text.length)
371
- return true;
372
- if (endsLink(text.charCodeAt(at), text[at]))
373
- return true;
374
- SLASH.lastIndex = at;
375
- return SLASH.test(text) || opener(text, at) !== undefined;
376
- }
377
414
  /** Whether the `#` at `at` belongs to a numeric character reference (`&#38;`) rather than opening a fragment. */
378
415
  function reference(text, at) {
379
416
  return text.charCodeAt(at - 1) === 0x26 && /[0-9x]/i.test(text[at + 1] ?? "");
@@ -394,47 +431,21 @@ function endsValue(text, at) {
394
431
  // `\/` is a JSON-escaped slash inside the value; any other backslash escapes what follows it.
395
432
  return code === 0x5c && text.charCodeAt(at + 1) !== 0x2f;
396
433
  }
397
- const HOST_MATCHERS = new WeakMap();
398
- function hostMatcher(hosts) {
399
- let matcher = HOST_MATCHERS.get(hosts);
400
- if (!matcher) {
401
- matcher = {
402
- endpoint: new RegExp(`^${S3_ENDPOINT}$`, "i"),
403
- domains: hosts.domains.map((host) => host.toLowerCase()),
404
- buckets: hosts.buckets.map((bucket) => bucket.toLowerCase()),
405
- };
406
- HOST_MATCHERS.set(hosts, matcher);
407
- }
408
- return matcher;
409
- }
410
- /** The end of a reviewed file authority, read only at the current URL's host. */
411
- function fileHost(text, start, matcher) {
412
- let end = start;
413
- while (end < text.length && end - start <= MAX_HOST && LABEL.test(text[end]))
414
- end++;
415
- if (end === start || end - start > MAX_HOST)
416
- return undefined;
417
- const port = afterPort(text, end);
418
- if (!boundary(text, port))
419
- return undefined;
420
- const name = text.slice(start, end).toLowerCase().replace(/\.$/, "");
421
- if (matcher.domains.some((domain) => name === domain || name.endsWith(`.${domain}`)))
422
- return end;
423
- if (matcher.buckets.some((bucket) => name.startsWith(`${bucket}.`) && matcher.endpoint.test(name.slice(bucket.length + 1)))) {
424
- return end;
425
- }
426
- if (!matcher.endpoint.test(name))
427
- return undefined;
428
- SLASH.lastIndex = port;
429
- if (!SLASH.test(text))
430
- return undefined;
431
- const segment = SLASH.lastIndex;
432
- for (const bucket of matcher.buckets) {
433
- const after = segment + bucket.length;
434
- if (text.slice(segment, after).toLowerCase() === bucket && boundary(text, after))
435
- return after;
436
- }
437
- return undefined;
434
+ /**
435
+ * Where a word at `start` holds a placeholder in place of a link's scheme
436
+ * and host (`[redacted]/pkg?token=…`, `https:[redacted]…`), or `-1`:
437
+ * redaction took the authority that makes text a link (#703). A Markdown
438
+ * label (`[redacted](…)`) is not one; its destination is read as a link.
439
+ */
440
+ function markerLed(text, start) {
441
+ let at = start;
442
+ while (at < text.length && at - start <= MAX_SCHEME + 3 && /[a-z0-9+.:/-]/i.test(text[at]))
443
+ at++;
444
+ return text.startsWith(REDACTED, at) &&
445
+ text[at + REDACTED.length] !== "(" &&
446
+ SCHEME_PREFIX.test(text.slice(start, at))
447
+ ? at
448
+ : -1;
438
449
  }
439
450
  /** The authority immediately after a destination's scheme and slashes, however escaped. */
440
451
  function destinationHost(text, start) {
@@ -472,11 +483,9 @@ function markdownStart(text, at) {
472
483
  }
473
484
  /**
474
485
  * Read a destination once. Admission ends at its actual CommonMark close,
475
- * counting balanced and escaped parentheses. Redaction with a query keeps
476
- * its separate fail-closed span through whitespace, a quote, or an angle
477
- * bracket, since parentheses can also be part of a signed value.
486
+ * counting balanced and escaped parentheses, before inspecting its query.
478
487
  */
479
- function linkSpan(text, start, host, inline, redact) {
488
+ function linkSpan(text, start, host, inline) {
480
489
  let depth = 0;
481
490
  let escaped = false;
482
491
  let cut = -1;
@@ -496,7 +505,7 @@ function linkSpan(text, start, host, inline, redact) {
496
505
  else if (code === 0x29 && depth-- === 0)
497
506
  break;
498
507
  }
499
- if (host === undefined || end < host || cut >= 0)
508
+ if (end < host || cut >= 0)
500
509
  continue;
501
510
  if (code === 0x3f || (code === 0x23 && !reference(text, end))) {
502
511
  cut = end;
@@ -510,27 +519,26 @@ function linkSpan(text, start, host, inline, redact) {
510
519
  }
511
520
  }
512
521
  }
513
- if (redact && cut >= 0) {
514
- while (end < text.length && !endsLink(text.charCodeAt(end), text[end]))
515
- end++;
516
- }
517
522
  return { cut, open, end };
518
523
  }
519
524
  /**
520
- * Scan destinations, including ones at other hosts, so an outer URL owns
521
- * every byte of its path and query. A nested scheme or // cannot re-anchor
522
- * file-host matching. Each consumed span is skipped in full; admission and
523
- * redaction choose their own span boundary, and both stay linear.
525
+ * Visit each destination once, respecting its CommonMark close and encoded
526
+ * delimiters. Redaction (`failClosed`) also reads a link whose scheme and
527
+ * host redaction took (#703); admission never does.
524
528
  */
525
- function* hostLinks(text, hosts, redact) {
526
- const matcher = hosts ? hostMatcher(hosts) : undefined;
527
- // The first URL delimiter in the current text run also protects relative
528
- // paths/queries such as /proxy/https://… and image?url=//… .
529
- let firstReference = -1;
529
+ function* linkSpans(text, failClosed = false) {
530
+ const markers = failClosed && redactionAltered(text);
530
531
  for (let at = 0; at < text.length; at++) {
531
532
  const char = text[at];
532
- if (endsLink(text.charCodeAt(at), char)) {
533
- firstReference = -1;
533
+ const marker = markers && (at === 0 || OPENS_LINK.test(text[at - 1])) ? markerLed(text, at) : -1;
534
+ if (marker >= 0) {
535
+ // A placeholder opening a word stands where a link's scheme and host may have been.
536
+ // Its run is read once, query or not, and the scan resumes after it.
537
+ const host = marker + REDACTED.length;
538
+ const span = linkSpan(text, host, host, false);
539
+ const start = at;
540
+ at = Math.max(at, span.end - 1);
541
+ yield { ...span, start, host };
534
542
  continue;
535
543
  }
536
544
  if (char === "]" && text[at + 1] === "(") {
@@ -538,19 +546,10 @@ function* hostLinks(text, hosts, redact) {
538
546
  if (destination !== undefined) {
539
547
  const angle = text[destination] === "<";
540
548
  const start = destination + (angle ? 1 : 0);
541
- const authority = destinationHost(text, start);
542
- // Generic policy inspects a relative destination's own query/fragment.
543
- // Host policy must still attribute the leading authority to the vendor.
544
- const host = matcher
545
- ? authority === undefined
546
- ? undefined
547
- : fileHost(text, authority, matcher)
548
- : (authority ?? start);
549
- const span = linkSpan(text, start, host, !angle, redact);
549
+ const host = destinationHost(text, start) ?? start;
550
+ const span = linkSpan(text, start, host, !angle);
550
551
  at = Math.max(destination, span.end) - 1;
551
- firstReference = -1;
552
- if (host !== undefined)
553
- yield span;
552
+ yield { ...span, start, host };
554
553
  continue;
555
554
  }
556
555
  }
@@ -561,37 +560,16 @@ function* hostLinks(text, hosts, redact) {
561
560
  SLASH.lastIndex = next;
562
561
  if (SLASH.test(text)) {
563
562
  const authority = SLASH.lastIndex;
564
- const start = authorityAt(text, authority);
565
- // Global parameter scanning needs no host attribution. This also
566
- // catches a URL after a log label such as `download:https://…`.
567
- // File-host policy keeps the stricter authority/proxy boundary.
568
- if (!matcher || (start >= 0 && (firstReference < 0 || firstReference >= start))) {
569
- const host = matcher ? fileHost(text, authority, matcher) : authority;
570
- const span = linkSpan(text, authority, host, false, redact);
571
- at = span.end - 1;
572
- firstReference = -1;
573
- if (host !== undefined)
574
- yield span;
575
- continue;
576
- }
563
+ const begin = authorityAt(text, authority);
564
+ const span = linkSpan(text, authority, authority, false);
565
+ const start = begin >= 0 ? begin : at;
566
+ at = span.end - 1;
567
+ yield { ...span, start, host: authority };
577
568
  }
578
- if (firstReference < 0)
579
- firstReference = at;
580
569
  }
581
570
  }
582
- if (firstReference < 0 && (char === ":" || char === "?" || (char === "#" && !reference(text, at)))) {
583
- firstReference = at;
584
- }
585
571
  }
586
572
  }
587
- /**
588
- * `text` with every pre-signed link withheld: at one of `hosts` (Notion's
589
- * file storage, say), its whole query and fragment; anywhere, a signing
590
- * parameter's value. Everything else comes back byte for byte.
591
- */
592
- export function withholdSignedLinks(text, hosts) {
593
- return withholdSigningParams(withholdLinkQueries(text, hosts));
594
- }
595
573
  /** A credential-named parameter in a URL, rather than an assignment in prose. */
596
574
  function credentialQuery(text) {
597
575
  let decoded = decodeLinkText(text, false);
@@ -605,8 +583,12 @@ function credentialQuery(text) {
605
583
  decoded = next;
606
584
  }
607
585
  const fragment = decoded.indexOf("#");
608
- if (fragment >= 0 && /token|secret|key|code|auth/i.test(decodeLinkText(decoded.slice(fragment + 1))))
609
- return true;
586
+ if (fragment >= 0) {
587
+ const words = decodeLinkText(decoded.slice(fragment + 1));
588
+ // A fragment redaction altered cannot be read (#703).
589
+ if (/token|secret|key|code|auth/i.test(words) || alteredFragment(words))
590
+ return true;
591
+ }
610
592
  const query = fragment < 0 ? decoded : decoded.slice(0, fragment);
611
593
  for (let at = query[0] === "?" ? 1 : 0; at < query.length;) {
612
594
  const next = query.indexOf("&", at);
@@ -614,6 +596,10 @@ function credentialQuery(text) {
614
596
  const pair = decodeLinkText(query.slice(at, end));
615
597
  const equals = pair.indexOf("=");
616
598
  at = end + 1;
599
+ // A component redaction altered with no readable name (its name, or its `=`, taken) may be a
600
+ // credential parameter's value, however encoded (#703).
601
+ if (pair !== REDACTED && redactionAltered(equals < 0 ? pair : pair.slice(0, equals)))
602
+ return true;
617
603
  if (equals < 0)
618
604
  continue;
619
605
  const name = pair.slice(0, equals);
@@ -625,42 +611,62 @@ function credentialQuery(text) {
625
611
  return false;
626
612
  }
627
613
  /** Remove a link's entire query/fragment, keeping its surrounding syntax. */
628
- function withholdLinkQueries(text, hosts) {
614
+ function withholdLinkQueries(text) {
629
615
  let out = "";
630
616
  let copied = 0;
631
- for (const { cut, open, end } of hostLinks(text, hosts, hosts !== undefined)) {
632
- if (cut < 0)
633
- continue;
634
- if (!hosts && !credentialQuery(text.slice(cut, end)))
617
+ for (const { cut, open, end, start, host } of linkSpans(text, true)) {
618
+ // A parameter name redaction altered cannot be read, so the whole link goes (#703).
619
+ const whole = alteredLink(text.slice(host, end));
620
+ if (!whole && (cut < 0 || !credentialQuery(text.slice(cut, end))))
635
621
  continue;
622
+ const from = Math.max(whole ? start : cut, copied);
623
+ // A reference's final semicolon belongs to the withheld query, not the surrounding prose.
624
+ // Search only this disjoint span, once, even when it ends in a long punctuation run.
625
+ const ampersand = text.slice(from, end).lastIndexOf("&");
626
+ const referenceEnd = ampersand < 0 ? -1 : characterReferenceEnd(text, from + ampersand);
636
627
  let keep = end;
637
- while (keep > cut + 1 && TRAILING.test(text[keep - 1])) {
628
+ while (keep > from + 1 && TRAILING.test(text[keep - 1])) {
638
629
  // A signing pass may already have replaced the final parameter. Its
639
630
  // closing bracket belongs to the value, not the surrounding Markdown.
640
- if (text.endsWith(REDACTED, keep))
631
+ if (keep === referenceEnd || text.endsWith(REDACTED, keep))
641
632
  break;
642
633
  keep--;
643
634
  }
644
635
  // A query with nothing in it.
645
- if (keep <= cut + 1)
636
+ if (keep <= from + 1)
646
637
  continue;
647
- out += `${text.slice(copied, cut)}${open}${REDACTED}`;
638
+ out += `${text.slice(copied, from)}${whole ? "" : open}${REDACTED}`;
648
639
  copied = keep;
649
640
  }
650
641
  return copied === 0 ? text : out + text.slice(copied);
651
642
  }
643
+ /** Whether redaction altered a parameter name in a link's own path, query, or fragment (#703). */
644
+ function alteredLink(link) {
645
+ return redactionAltered(link) && parameterNames(decodeLinkText(link)).some(redactionAltered);
646
+ }
652
647
  function withholdSigningParams(anchored) {
653
648
  let out = "";
654
649
  let copied = 0;
655
650
  SIGNING_PARAM.lastIndex = 0;
656
- for (let match = SIGNING_PARAM.exec(anchored); match; match = SIGNING_PARAM.exec(anchored)) {
657
- const start = match.index + match[0].length;
651
+ let signing = SIGNING_PARAM.exec(anchored);
652
+ let altered = alteredParameter(anchored, 0);
653
+ while (signing || altered) {
654
+ const named = signing ? signing.index + signing[0].length : -1;
655
+ const alteredStart = altered?.start ?? -1;
656
+ const boundary = altered?.value ?? -1;
657
+ // A complete altered-name boundary takes precedence over its withheld family suffix.
658
+ const inferred = alteredStart >= 0 && (named < 0 || alteredStart < signing.index || (alteredStart < named && boundary > named));
659
+ const start = inferred ? alteredStart : named;
658
660
  // Skip an existing complete placeholder, but still remove any suffix.
659
661
  const already = anchored.startsWith(REDACTED, start);
660
- let end = already ? start + REDACTED.length : start;
661
- while (end < anchored.length && !endsValue(anchored, end))
662
- end++;
662
+ const from = inferred && altered?.value !== undefined ? altered.value : already ? start + REDACTED.length : start;
663
+ const end = valueEnd(anchored, from, inferred && altered?.value === undefined);
664
+ // Resume both scans after this value, as one scan would.
663
665
  SIGNING_PARAM.lastIndex = end;
666
+ if (named >= 0 && named <= end)
667
+ signing = SIGNING_PARAM.exec(anchored);
668
+ if (altered && altered.start <= end)
669
+ altered = alteredParameter(anchored, end);
664
670
  if (end === start || (already && end === start + REDACTED.length))
665
671
  continue;
666
672
  out += `${anchored.slice(copied, start)}${REDACTED}`;
@@ -668,18 +674,79 @@ function withholdSigningParams(anchored) {
668
674
  }
669
675
  return copied === 0 ? anchored : out + anchored.slice(copied);
670
676
  }
677
+ const NAME_CHAR = /[\w.%~;-]/;
678
+ const NAME_ESCAPE = new RegExp(Array.from("abcdefghijklmnopqrstuvwxyz0123456789_.~-", signingName).join("|"), "iy");
679
+ /** Where a parameter's value ends, reading placeholders and character references whole (#703). */
680
+ function valueEnd(text, at, inferred) {
681
+ while (at < text.length) {
682
+ const reference = Math.max(characterReferenceEnd(text, at, inferred), bareReferenceEnd(text, at, inferred));
683
+ if (text.startsWith(REDACTED, at))
684
+ at += REDACTED.length;
685
+ else if (reference > at)
686
+ at = reference;
687
+ else if (endsValue(text, at))
688
+ break;
689
+ else
690
+ at++;
691
+ }
692
+ return at;
693
+ }
694
+ /**
695
+ * The next placeholder that may hide a signing parameter, at or after `from`,
696
+ * and its value boundary when a name is recognized (#703). Redaction may have taken a signing name, its
697
+ * `=`, and the start of its value, so a placeholder followed by any of a
698
+ * value's base64 or base64url characters (`+`, `/`, `=`, `-`, `_` included)
699
+ * or `%` escapes goes with them, however short. References count when they
700
+ * spell credential data or an ampersand, including a short numeric prefix
701
+ * when no semicolon ends the reference. One inside a name ending at an
702
+ * encoded `=` (`&#61;`, `\u003d`) gives that value's start. A placeholder
703
+ * right after an `=` with nothing readable after it is a withheld value.
704
+ * Each placeholder is read once, resuming after its run.
705
+ */
706
+ function alteredParameter(text, from) {
707
+ for (let at = text.indexOf(REDACTED, from); at >= 0;) {
708
+ const tail = splitRemainder(text, at, endsValue);
709
+ // Read a possible name before inferring a value remainder: a name's encoded `=` or dash
710
+ // can otherwise look like the end of the remainder. A leading partial reference belongs
711
+ // to the name too; the placeholder may have removed its opening characters.
712
+ if (!EQUALS_BEFORE.test(text.slice(Math.max(0, at - 64), at))) {
713
+ // The tail already read any earlier placeholders. Only its last one can lead a
714
+ // name beyond that tail; keep the redaction start so no earlier data is exposed.
715
+ let end = text.lastIndexOf(REDACTED, tail.end - REDACTED.length) + REDACTED.length;
716
+ while (end < text.length) {
717
+ EQUALS.lastIndex = end;
718
+ if (EQUALS.test(text))
719
+ return { start: at, value: EQUALS.lastIndex };
720
+ NAME_ESCAPE.lastIndex = end;
721
+ // A later placeholder gets its own scan; never look ahead across it twice.
722
+ if (text.startsWith(REDACTED, end))
723
+ break;
724
+ else if (NAME_CHAR.test(text[end]))
725
+ end++;
726
+ else if (NAME_ESCAPE.test(text))
727
+ end = NAME_ESCAPE.lastIndex;
728
+ else {
729
+ const reference = bareReferenceEnd(text, end, true);
730
+ if (reference === end)
731
+ break;
732
+ end = reference;
733
+ }
734
+ }
735
+ }
736
+ if (tail.readable)
737
+ return { start: at };
738
+ at = text.indexOf(REDACTED, Math.max(tail.end, at + REDACTED.length));
739
+ }
740
+ return undefined;
741
+ }
671
742
  /**
672
743
  * Reviewed REST text policy: only credential-bearing URL queries/fragments
673
744
  * change. Ordinary links, including external proxies, retain their bytes.
674
- * Literal AWS/Google signing names are withheld anywhere, as in #818.
745
+ * The established AWS/Google signing names are withheld in their source spelling anywhere.
675
746
  */
676
747
  export function withholdEmbeddedUrls(text) {
677
748
  return withholdSigningParams(withholdLinkQueries(text));
678
749
  }
679
- /** Every string inside a JSON value through `withholdSignedLinks`; unchanged values keep their identity. */
680
- export function withholdSignedLinksIn(value, hosts, depth = 0) {
681
- return mapStrings(value, (text) => withholdSignedLinks(text, hosts), depth);
682
- }
683
750
  /** Apply the reviewed text policy to a selected value or vendor error body. */
684
751
  export function withholdEmbeddedUrlsIn(value) {
685
752
  return mapStrings(value, withholdEmbeddedUrls, 0);
@@ -732,19 +799,20 @@ function decodeLinkText(text, percent = true) {
732
799
  * Only the link's own destination is read: a Markdown link ends at its
733
800
  * closing `)`, so `[f](…?a=1)[redacted]` is ordinary text after it.
734
801
  * The word alone (`unredacted`) or the placeholder away from a file link is
735
- * a person's text. When `hosts` is supplied, require its actual authority
736
- * to match that policy, preserving Notion's external-proxy boundary.
802
+ * a person's text. Every destination uses the same policy regardless of host.
737
803
  */
738
- export function carriesWithheldLink(value, hosts) {
739
- const pending = [{ value }];
740
- const ancestors = new WeakSet();
741
- const inspect = (text) => {
742
- for (const { cut, end } of hostLinks(text, hosts, false)) {
804
+ export function carriesWithheldLink(value) {
805
+ return carriesString(value, (text) => {
806
+ for (const { cut, end } of linkSpans(text)) {
743
807
  if (cut >= 0 && decodeLinkText(text.slice(cut, end)).includes(REDACTED))
744
808
  return true;
745
809
  }
746
810
  return false;
747
- };
811
+ });
812
+ }
813
+ function carriesString(value, inspect) {
814
+ const pending = [{ value }];
815
+ const ancestors = new WeakSet();
748
816
  // JSON structure is bounded by the admitted request's bytes. Visit every key
749
817
  // and value iteratively, without imposing a smaller node/depth limit. Reject
750
818
  // cycles from direct callers; repeated non-cyclic references remain valid.
@@ -760,7 +828,7 @@ export function carriesWithheldLink(value, hosts) {
760
828
  continue;
761
829
  }
762
830
  if (ancestors.has(item)) {
763
- throw new ConnectorCallError("invalid_args", "Not sent: URL admission cannot inspect cyclic JSON.");
831
+ throw new ConnectorCallError("invalid_args", "Not sent: value admission cannot inspect cyclic JSON.");
764
832
  }
765
833
  ancestors.add(item);
766
834
  pending.push({ value: item, leave: true });
@@ -779,6 +847,25 @@ export function carriesWithheldLink(value, hosts) {
779
847
  }
780
848
  return false;
781
849
  }
850
+ /** Whole marker values and withheld destinations are refused; paths and queries also refuse containment. */
851
+ export function refuseWithheldValues(value, title, containment = false) {
852
+ if (!carriesString(value, (text) => {
853
+ const decoded = decodeLinkText(text);
854
+ if (containment)
855
+ return redactionAltered(text) || redactionAltered(decoded);
856
+ if (decoded === REDACTED)
857
+ return true;
858
+ for (const candidate of [text, decoded])
859
+ for (const { start, end } of linkSpans(candidate))
860
+ if (redactionAltered(decodeLinkText(candidate.slice(start, end))))
861
+ return true;
862
+ return false;
863
+ }))
864
+ return;
865
+ throw new ConnectorCallError("invalid_args", containment
866
+ ? `Not sent: ${title} path or query contains a withheld placeholder ([redacted]). Supply the original path/query value or omit the parameter.`
867
+ : `Not sent: ${title} arguments contain a whole withheld value ([redacted]) or a withheld link. Supply the original value/link or omit the field. Longer prose may mention the marker.`);
868
+ }
782
869
  /** Refuse a withheld destination copied into customer data, before any dispatch. */
783
870
  export function refuseWithheldLinks(value, title) {
784
871
  if (!carriesWithheldLink(value))
@@ -867,24 +954,52 @@ function labelsOf(value) {
867
954
  labels.push(target["key"]);
868
955
  return labels;
869
956
  }
870
- /** Whether a record's label names a credential, so its value or args are one. */
957
+ /**
958
+ * Whether a record's label names a credential, so its value or args are one.
959
+ * A label redaction altered, or keys that may hide one, does too (#703).
960
+ */
871
961
  function credentialLabelled(value) {
872
- return isRecord(value) && labelsOf(value).some(credentialName);
962
+ if (!isRecord(value))
963
+ return false;
964
+ const target = value["target"];
965
+ return (keysAltered(value) ||
966
+ (isRecord(target) && keysAltered(target)) ||
967
+ labelsOf(value).some((label) => decides(label, credentialName)));
968
+ }
969
+ /** A record typed as a secret (`secret_text`); a type redaction altered may have been one (#703). */
970
+ function typedSecret(value) {
971
+ return keysAltered(value) || decides(value["type"], (type) => /secret/i.test(type));
972
+ }
973
+ /** A list item typed as an environment variable; a type redaction altered may have been one (#703). */
974
+ function envTyped(value) {
975
+ if (!isRecord(value))
976
+ return false;
977
+ return keysAltered(value) || decides(String(value["type"]), (type) => /env/i.test(type));
873
978
  }
874
- /** A header, cookie, or query rule (`request.headers`, `header`, `cookie`, `query`, …). */
979
+ /** A header, cookie, or query rule (`request.headers`, `header`, `cookie`, `query`, …); likewise (#703). */
875
980
  function headerLike(value) {
876
- return isRecord(value) && typeof value["type"] === "string" && /header|cookie|query/i.test(value["type"]);
981
+ return isRecord(value) && (keysAltered(value) || decides(value["type"], (type) => /header|cookie|query/i.test(type)));
877
982
  }
878
983
  /** A route-like object: its `headers` are set on requests or responses. */
879
984
  function routeLike(value) {
880
- return ["src", "source", "dest", "destination"].some((key) => key in value);
985
+ return keysAltered(value) || ["src", "source", "dest", "destination"].some((key) => key in value);
881
986
  }
882
987
  /** Every value of a headers map, or of a list of `{ key, value }` header pairs, withheld. */
883
988
  function headerValues(value) {
884
989
  if (isRecord(value))
885
990
  return Object.fromEntries(Object.keys(value).map((name) => [name, REDACTED]));
886
991
  if (Array.isArray(value)) {
887
- return value.map((item) => (isRecord(item) && "value" in item ? { ...item, value: REDACTED } : item));
992
+ // A pair whose keys redaction altered may hold its value under any of them (#703).
993
+ return value.map((item) => !isRecord(item)
994
+ ? item
995
+ : keysAltered(item)
996
+ ? Object.fromEntries(Object.entries(item).map(([key, entry]) => [
997
+ key,
998
+ key === "value" || redactionAltered(key) ? withheld(entry) : entry,
999
+ ]))
1000
+ : "value" in item
1001
+ ? { ...item, value: REDACTED }
1002
+ : item);
888
1003
  }
889
1004
  return value;
890
1005
  }
@@ -959,7 +1074,7 @@ function applyPath(body, raw, transform) {
959
1074
  if (!Array.isArray(current))
960
1075
  return current;
961
1076
  return current.map((item) => {
962
- if (segment === "[?env]" && !/env/i.test(String(isRecord(item) ? item["type"] : "")))
1077
+ if (segment === "[?env]" && !envTyped(item))
963
1078
  return item;
964
1079
  if (segment === "[?credential]" && !credentialLabelled(item))
965
1080
  return item;
@@ -979,7 +1094,9 @@ function applyPath(body, raw, transform) {
979
1094
  if (segment === "@keys") {
980
1095
  if (!isRecord(current))
981
1096
  return current;
982
- return Object.fromEntries(Object.values(current).map((item, position) => [`${REDACTED} ${position + 1}`, item]));
1097
+ const keyed = Object.fromEntries(Object.values(current).map((item, position) => [`${REDACTED} ${position + 1}`, item]));
1098
+ PLACEHOLDER_KEYS.add(keyed);
1099
+ return keyed;
983
1100
  }
984
1101
  if (!isRecord(current) || !Object.hasOwn(current, segment))
985
1102
  return current;
@@ -1003,7 +1120,8 @@ function scrubEnv(value, depth) {
1003
1120
  if (depth > 32)
1004
1121
  return REDACTED;
1005
1122
  if (Array.isArray(value)) {
1006
- return value.map((item) => (typeof item === "string" && item.includes("=") ? REDACTED : scrubEnv(item, depth + 1)));
1123
+ // A `NAME=value` string, or one whose name and `=` redaction may have taken (#703).
1124
+ return value.map((item) => typeof item === "string" && (item.includes("=") || redactionAltered(item)) ? REDACTED : scrubEnv(item, depth + 1));
1007
1125
  }
1008
1126
  if (!isRecord(value))
1009
1127
  return value;
@@ -1041,6 +1159,34 @@ function scrubValues(value, depth) {
1041
1159
  }
1042
1160
  return out;
1043
1161
  }
1162
+ /** Every rule's paths and withheld fields: the rule for an object whose type redaction altered (#703). */
1163
+ const ANY_RULE = new WeakMap();
1164
+ function anyRule(resources) {
1165
+ let rule = ANY_RULE.get(resources);
1166
+ if (!rule) {
1167
+ const paths = Object.values(resources.rules).flatMap((each) => [...(each.paths ?? []), ...(each.withheld ?? [])]);
1168
+ rule = { reason: "A type redaction altered may be any reviewed resource.", paths: [...new Set(paths)] };
1169
+ ANY_RULE.set(resources, rule);
1170
+ }
1171
+ return rule;
1172
+ }
1173
+ /**
1174
+ * What a record's type discriminator selects. A type redaction altered, or
1175
+ * one an altered key may hide, selects every rule at once (#703).
1176
+ */
1177
+ function typed(record, key, rules, any) {
1178
+ const type = record[key];
1179
+ if (redactionAltered(type) || (type === undefined && keysAltered(record)))
1180
+ return any();
1181
+ return typeof type === "string" && Object.hasOwn(rules, type) ? rules[type] : undefined;
1182
+ }
1183
+ /** What the discriminator of the object an event carries (`data.object`) selects, for its partial copies. */
1184
+ function typedInner(record, key, rules, any) {
1185
+ const inner = record[key];
1186
+ if (isRecord(inner))
1187
+ return typed(inner, key, rules, any);
1188
+ return inner === undefined && keysAltered(record) ? any() : undefined;
1189
+ }
1044
1190
  /**
1045
1191
  * Apply resource rules to every discriminated object in a body, innermost
1046
1192
  * first, and to the partial copies events carry beside one. Records the
@@ -1054,10 +1200,9 @@ function resourcePass(value, resources, verbatim, depth) {
1054
1200
  if (!isRecord(value))
1055
1201
  return value;
1056
1202
  let out = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, resourcePass(item, resources, verbatim, depth + 1)]));
1057
- const ruleFor = (type) => typeof type === "string" && Object.hasOwn(resources.rules, type) ? resources.rules[type] : undefined;
1203
+ const any = () => anyRule(resources);
1058
1204
  // An event's `previous_attributes` is a partial copy of `data.object`: every reviewed path applies to it.
1059
- const inner = out[resources.key];
1060
- const innerRule = isRecord(inner) ? ruleFor(inner[resources.key]) : undefined;
1205
+ const innerRule = typedInner(value, resources.key, resources.rules, any);
1061
1206
  if (innerRule) {
1062
1207
  for (const partial of resources.partials ?? []) {
1063
1208
  let copy = out[partial];
@@ -1068,7 +1213,7 @@ function resourcePass(value, resources, verbatim, depth) {
1068
1213
  out = { ...out, [partial]: copy };
1069
1214
  }
1070
1215
  }
1071
- const rule = ruleFor(out[resources.key]);
1216
+ const rule = typed(value, resources.key, resources.rules, any);
1072
1217
  if (!rule)
1073
1218
  return out;
1074
1219
  for (const path of rule.paths ?? [])
@@ -1101,22 +1246,33 @@ function heuristic(value, exempt) {
1101
1246
  // generic names in customer data must retain their original bytes.
1102
1247
  if (!exempt.reviewedUrls)
1103
1248
  return URL_SHAPE.test(current) ? sanitizeUrl(current) : current;
1104
- const whole = URL_SHAPE.test(current) && !/[\s<>"'`\\]|&(?:#|[a-z]+;)|\]\(/i.test(current);
1105
- return whole ? sanitizeUrl(current, false) : current;
1249
+ // A placeholder's own brackets are not Markdown; a URL whose scheme redaction
1250
+ // altered may still carry userinfo, so it is withheld whole (#703).
1251
+ const altered = redactionAltered(current);
1252
+ if (TEXT.test(altered ? current.replaceAll(REDACTED, "") : current))
1253
+ return current;
1254
+ if (altered && alteredScheme(current))
1255
+ return REDACTED;
1256
+ return URL_SHAPE.test(current) ? sanitizeUrl(current, false) : current;
1106
1257
  }
1107
1258
  if (Array.isArray(current))
1108
1259
  return current.map((item) => walk(item, trail, parent, depth + 1));
1109
1260
  if (!isRecord(current))
1110
1261
  return current;
1111
- const labelled = credentialLabelled(current) || (typeof current["type"] === "string" && /secret/i.test(current["type"]));
1112
- const destination = parent === "delivery" || "endpoint" in current || "deliveryFormat" in current;
1262
+ const labelled = credentialLabelled(current) || typedSecret(current);
1263
+ const placeholders = PLACEHOLDER_KEYS.has(current);
1264
+ const destination = parent === "delivery" || "endpoint" in current || "deliveryFormat" in current || keysAltered(current);
1113
1265
  const out = {};
1114
1266
  const verbatim = exempt.verbatim.get(current);
1115
1267
  for (const [key, item] of Object.entries(current)) {
1116
1268
  const path = [...trail, key];
1117
1269
  const normalized = normalizedName(key);
1118
1270
  const review = Object.hasOwn(exempt.fields, key) ? exempt.fields[key] : undefined;
1119
- if (review?.verdict === "redact") {
1271
+ if (!placeholders && redactionAltered(key)) {
1272
+ // A key redaction altered may have named a credential (#703).
1273
+ out[key] = withheld(item);
1274
+ }
1275
+ else if (review?.verdict === "redact") {
1120
1276
  out[key] = withheld(item);
1121
1277
  }
1122
1278
  else if (labelled && LABELLED_VALUE_FIELDS.includes(key)) {
@@ -1153,8 +1309,11 @@ function heuristic(value, exempt) {
1153
1309
  else if (key === "headers" && routeLike(current)) {
1154
1310
  out[key] = headerValues(item);
1155
1311
  }
1156
- else if (DESTINATION_KEYS.includes(key) && typeof item === "string" && URL_SHAPE.test(item)) {
1312
+ else if (DESTINATION_KEYS.includes(key) &&
1313
+ typeof item === "string" &&
1314
+ (URL_SHAPE.test(item) || alteredScheme(item))) {
1157
1315
  // An external destination can carry its secret in the path; relative paths stay.
1316
+ // One whose scheme redaction altered does not parse, so it is withheld (#703).
1158
1317
  out[key] = originOnly(item);
1159
1318
  }
1160
1319
  else if (typeof item === "string" && verbatim?.has(key)) {
@@ -1208,27 +1367,26 @@ export function valueSafety(table, index) {
1208
1367
  }
1209
1368
  const resources = table.embeddedUrls.resources;
1210
1369
  if (resources) {
1370
+ // A type redaction altered takes every resource's reviewed text paths (#703).
1371
+ const all = [...new Set(Object.values(resources.paths).flat())];
1372
+ const any = () => all;
1211
1373
  const scan = (value, depth) => {
1212
1374
  if (depth > MAX_DEPTH || value === null || typeof value !== "object")
1213
1375
  return value;
1214
1376
  if (Array.isArray(value))
1215
1377
  return value.map((item) => scan(item, depth + 1));
1216
1378
  let clean = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, scan(item, depth + 1)]));
1217
- const inner = clean[resources.key];
1218
- const innerType = isRecord(inner) ? inner[resources.key] : undefined;
1219
- if (typeof innerType === "string" && Object.hasOwn(resources.paths, innerType)) {
1379
+ const innerPaths = typedInner(value, resources.key, resources.paths, any);
1380
+ if (innerPaths) {
1220
1381
  for (const partial of resources.partials ?? []) {
1221
1382
  if (!Object.hasOwn(clean, partial))
1222
1383
  continue;
1223
- for (const path of resources.paths[innerType])
1384
+ for (const path of innerPaths)
1224
1385
  clean[partial] = applyPath(clean[partial], path, withholdEmbeddedUrlsIn);
1225
1386
  }
1226
1387
  }
1227
- const type = clean[resources.key];
1228
- if (typeof type === "string" && Object.hasOwn(resources.paths, type)) {
1229
- for (const path of resources.paths[type])
1230
- clean = applyPath(clean, path, withholdEmbeddedUrlsIn);
1231
- }
1388
+ for (const path of typed(value, resources.key, resources.paths, any) ?? [])
1389
+ clean = applyPath(clean, path, withholdEmbeddedUrlsIn);
1232
1390
  return clean;
1233
1391
  };
1234
1392
  out = scan(out, 0);