@cybedefend/vibedefend 1.5.0 → 1.7.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 (264) hide show
  1. package/README.md +71 -2
  2. package/dist/api/gateway-request.js +49 -0
  3. package/dist/api/gateway-request.js.map +1 -0
  4. package/dist/api/projects.js +67 -0
  5. package/dist/api/projects.js.map +1 -1
  6. package/dist/api/repository-link.js +157 -0
  7. package/dist/api/repository-link.js.map +1 -0
  8. package/dist/auth/auth-store.js +107 -25
  9. package/dist/auth/auth-store.js.map +1 -1
  10. package/dist/auth/auth.js +21 -0
  11. package/dist/auth/auth.js.map +1 -1
  12. package/dist/clients/cursor.js +5 -1
  13. package/dist/clients/cursor.js.map +1 -1
  14. package/dist/clients/vscode.js +38 -29
  15. package/dist/clients/vscode.js.map +1 -1
  16. package/dist/commands/mcp/allow.js +219 -0
  17. package/dist/commands/mcp/allow.js.map +1 -0
  18. package/dist/commands/mcp/connect.js +337 -0
  19. package/dist/commands/mcp/connect.js.map +1 -0
  20. package/dist/commands/mcp/index.js +170 -0
  21. package/dist/commands/mcp/index.js.map +1 -0
  22. package/dist/commands/mcp/proxy.js +320 -0
  23. package/dist/commands/mcp/proxy.js.map +1 -0
  24. package/dist/commands/mcp/scan.js +349 -0
  25. package/dist/commands/mcp/scan.js.map +1 -0
  26. package/dist/commands/mcp/sync.js +215 -0
  27. package/dist/commands/mcp/sync.js.map +1 -0
  28. package/dist/config.js +6 -0
  29. package/dist/config.js.map +1 -1
  30. package/dist/diagnostics.js +78 -12
  31. package/dist/diagnostics.js.map +1 -1
  32. package/dist/doctor.js +19 -0
  33. package/dist/doctor.js.map +1 -1
  34. package/dist/guards-evaluator/analysis-types.js +27 -0
  35. package/dist/guards-evaluator/analysis-types.js.map +1 -0
  36. package/dist/guards-evaluator/command-analysis.js +572 -0
  37. package/dist/guards-evaluator/command-analysis.js.map +1 -0
  38. package/dist/guards-evaluator/command-view.js +586 -0
  39. package/dist/guards-evaluator/command-view.js.map +1 -0
  40. package/dist/guards-evaluator/env.js +11 -0
  41. package/dist/guards-evaluator/env.js.map +1 -0
  42. package/dist/guards-evaluator/evaluate.js +91 -41
  43. package/dist/guards-evaluator/evaluate.js.map +1 -1
  44. package/dist/guards-evaluator/index.js +2 -1
  45. package/dist/guards-evaluator/index.js.map +1 -1
  46. package/dist/guards-evaluator/inline-code.js +259 -0
  47. package/dist/guards-evaluator/inline-code.js.map +1 -0
  48. package/dist/guards-evaluator/normalize.js +716 -0
  49. package/dist/guards-evaluator/normalize.js.map +1 -0
  50. package/dist/guards-evaluator/preprocess.js +13 -5
  51. package/dist/guards-evaluator/preprocess.js.map +1 -1
  52. package/dist/guards-evaluator/redact.js +19 -0
  53. package/dist/guards-evaluator/redact.js.map +1 -1
  54. package/dist/guards-evaluator/regex.js +25 -3
  55. package/dist/guards-evaluator/regex.js.map +1 -1
  56. package/dist/guards-evaluator/safe-commands.js +155 -0
  57. package/dist/guards-evaluator/safe-commands.js.map +1 -0
  58. package/dist/guards-evaluator/triage.js +315 -0
  59. package/dist/guards-evaluator/triage.js.map +1 -0
  60. package/dist/hook-runner.js +24679 -5079
  61. package/dist/hooks/install.js +38 -0
  62. package/dist/hooks/install.js.map +1 -1
  63. package/dist/hooks/runtime/config.js +38 -0
  64. package/dist/hooks/runtime/config.js.map +1 -1
  65. package/dist/hooks/runtime/emit.js +94 -61
  66. package/dist/hooks/runtime/emit.js.map +1 -1
  67. package/dist/hooks/runtime/fetch-rules.js +22 -7
  68. package/dist/hooks/runtime/fetch-rules.js.map +1 -1
  69. package/dist/hooks/runtime/gateway-http-error.js +34 -0
  70. package/dist/hooks/runtime/gateway-http-error.js.map +1 -0
  71. package/dist/hooks/runtime/guard-rules-cache.js +139 -17
  72. package/dist/hooks/runtime/guard-rules-cache.js.map +1 -1
  73. package/dist/hooks/runtime/guard-violations-buffer.js +321 -48
  74. package/dist/hooks/runtime/guard-violations-buffer.js.map +1 -1
  75. package/dist/hooks/runtime/judge-cache.js +6 -0
  76. package/dist/hooks/runtime/judge-cache.js.map +1 -1
  77. package/dist/hooks/runtime/judge-triage.js +193 -0
  78. package/dist/hooks/runtime/judge-triage.js.map +1 -0
  79. package/dist/hooks/runtime/judge.js +7 -1
  80. package/dist/hooks/runtime/judge.js.map +1 -1
  81. package/dist/hooks/runtime/pre-compact.js +12 -6
  82. package/dist/hooks/runtime/pre-compact.js.map +1 -1
  83. package/dist/hooks/runtime/resolve.js +90 -8
  84. package/dist/hooks/runtime/resolve.js.map +1 -1
  85. package/dist/hooks/runtime/retry-policy.js +57 -0
  86. package/dist/hooks/runtime/retry-policy.js.map +1 -0
  87. package/dist/hooks/runtime/review-sessions.js +74 -0
  88. package/dist/hooks/runtime/review-sessions.js.map +1 -0
  89. package/dist/hooks/runtime/script-resolve.js +695 -0
  90. package/dist/hooks/runtime/script-resolve.js.map +1 -0
  91. package/dist/hooks/runtime/session-activation.js +5 -1
  92. package/dist/hooks/runtime/session-activation.js.map +1 -1
  93. package/dist/hooks/runtime/session-review.js +163 -45
  94. package/dist/hooks/runtime/session-review.js.map +1 -1
  95. package/dist/hooks/runtime/session-start.js +2 -1
  96. package/dist/hooks/runtime/session-start.js.map +1 -1
  97. package/dist/hooks/runtime/session-writes.js +232 -0
  98. package/dist/hooks/runtime/session-writes.js.map +1 -0
  99. package/dist/hooks/runtime/sniff.js +26 -7
  100. package/dist/hooks/runtime/sniff.js.map +1 -1
  101. package/dist/hooks/runtime/target-resolve.js +336 -0
  102. package/dist/hooks/runtime/target-resolve.js.map +1 -0
  103. package/dist/hooks/runtime/unmapped-tools.js +93 -0
  104. package/dist/hooks/runtime/unmapped-tools.js.map +1 -0
  105. package/dist/hooks/runtime/usage-block.js +4 -0
  106. package/dist/hooks/runtime/usage-block.js.map +1 -1
  107. package/dist/hooks/runtime/user-prompt-submit.js +2 -1
  108. package/dist/hooks/runtime/user-prompt-submit.js.map +1 -1
  109. package/dist/hooks/runtime/wire-text.js +41 -0
  110. package/dist/hooks/runtime/wire-text.js.map +1 -0
  111. package/dist/hooks/runtime/work-dir.js +84 -0
  112. package/dist/hooks/runtime/work-dir.js.map +1 -0
  113. package/dist/index.js +45 -12
  114. package/dist/index.js.map +1 -1
  115. package/dist/install.js +174 -11
  116. package/dist/install.js.map +1 -1
  117. package/dist/link.js +74 -48
  118. package/dist/link.js.map +1 -1
  119. package/dist/login.js +77 -11
  120. package/dist/login.js.map +1 -1
  121. package/dist/mcp/auth/agent-sign-ins.js +174 -0
  122. package/dist/mcp/auth/agent-sign-ins.js.map +1 -0
  123. package/dist/mcp/auth/callback.js +200 -0
  124. package/dist/mcp/auth/callback.js.map +1 -0
  125. package/dist/mcp/auth/connect.js +392 -0
  126. package/dist/mcp/auth/connect.js.map +1 -0
  127. package/dist/mcp/auth/discovery.js +399 -0
  128. package/dist/mcp/auth/discovery.js.map +1 -0
  129. package/dist/mcp/auth/oauth.js +326 -0
  130. package/dist/mcp/auth/oauth.js.map +1 -0
  131. package/dist/mcp/auth/probe.js +128 -0
  132. package/dist/mcp/auth/probe.js.map +1 -0
  133. package/dist/mcp/auth/secrets.js +446 -0
  134. package/dist/mcp/auth/secrets.js.map +1 -0
  135. package/dist/mcp/auth/store.js +260 -0
  136. package/dist/mcp/auth/store.js.map +1 -0
  137. package/dist/mcp/auth/tokenSource.js +55 -0
  138. package/dist/mcp/auth/tokenSource.js.map +1 -0
  139. package/dist/mcp/config/atomicWrite.js +98 -0
  140. package/dist/mcp/config/atomicWrite.js.map +1 -0
  141. package/dist/mcp/config/clients.js +171 -0
  142. package/dist/mcp/config/clients.js.map +1 -0
  143. package/dist/mcp/config/cybedefend-servers.js +48 -0
  144. package/dist/mcp/config/cybedefend-servers.js.map +1 -0
  145. package/dist/mcp/config/discover.js +114 -0
  146. package/dist/mcp/config/discover.js.map +1 -0
  147. package/dist/mcp/config/hook.js +123 -0
  148. package/dist/mcp/config/hook.js.map +1 -0
  149. package/dist/mcp/config/index.js +11 -0
  150. package/dist/mcp/config/index.js.map +1 -0
  151. package/dist/mcp/config/install.js +132 -0
  152. package/dist/mcp/config/install.js.map +1 -0
  153. package/dist/mcp/config/installed.js +37 -0
  154. package/dist/mcp/config/installed.js.map +1 -0
  155. package/dist/mcp/config/jsonEdit.js +119 -0
  156. package/dist/mcp/config/jsonEdit.js.map +1 -0
  157. package/dist/mcp/config/lock.js +102 -0
  158. package/dist/mcp/config/lock.js.map +1 -0
  159. package/dist/mcp/config/proxyBinary.js +211 -0
  160. package/dist/mcp/config/proxyBinary.js.map +1 -0
  161. package/dist/mcp/config/rewrite.js +388 -0
  162. package/dist/mcp/config/rewrite.js.map +1 -0
  163. package/dist/mcp/config/types.js +34 -0
  164. package/dist/mcp/config/types.js.map +1 -0
  165. package/dist/mcp/proxy/audit.js +83 -0
  166. package/dist/mcp/proxy/audit.js.map +1 -0
  167. package/dist/mcp/proxy/authInterceptor.js +119 -0
  168. package/dist/mcp/proxy/authInterceptor.js.map +1 -0
  169. package/dist/mcp/proxy/expand.js +93 -0
  170. package/dist/mcp/proxy/expand.js.map +1 -0
  171. package/dist/mcp/proxy/inject.js +412 -0
  172. package/dist/mcp/proxy/inject.js.map +1 -0
  173. package/dist/mcp/proxy/intercept.js +63 -0
  174. package/dist/mcp/proxy/intercept.js.map +1 -0
  175. package/dist/mcp/proxy/jsonrpc.js +153 -0
  176. package/dist/mcp/proxy/jsonrpc.js.map +1 -0
  177. package/dist/mcp/proxy/log.js +109 -0
  178. package/dist/mcp/proxy/log.js.map +1 -0
  179. package/dist/mcp/proxy/paths.js +57 -0
  180. package/dist/mcp/proxy/paths.js.map +1 -0
  181. package/dist/mcp/proxy/proxy.js +520 -0
  182. package/dist/mcp/proxy/proxy.js.map +1 -0
  183. package/dist/mcp/proxy/serverCheckTimer.js +75 -0
  184. package/dist/mcp/proxy/serverCheckTimer.js.map +1 -0
  185. package/dist/mcp/proxy/sessionLifecycle.js +29 -0
  186. package/dist/mcp/proxy/sessionLifecycle.js.map +1 -0
  187. package/dist/mcp/proxy/spec.js +203 -0
  188. package/dist/mcp/proxy/spec.js.map +1 -0
  189. package/dist/mcp/proxy/sse.js +77 -0
  190. package/dist/mcp/proxy/sse.js.map +1 -0
  191. package/dist/mcp/proxy/upstream.js +2 -0
  192. package/dist/mcp/proxy/upstream.js.map +1 -0
  193. package/dist/mcp/proxy/upstreamHttp.js +499 -0
  194. package/dist/mcp/proxy/upstreamHttp.js.map +1 -0
  195. package/dist/mcp/proxy/upstreamStdio.js +188 -0
  196. package/dist/mcp/proxy/upstreamStdio.js.map +1 -0
  197. package/dist/mcp/proxy/upstreams.js +143 -0
  198. package/dist/mcp/proxy/upstreams.js.map +1 -0
  199. package/dist/mcp/proxy/verifyInterceptor.js +718 -0
  200. package/dist/mcp/proxy/verifyInterceptor.js.map +1 -0
  201. package/dist/mcp/proxy/windowsCommand.js +99 -0
  202. package/dist/mcp/proxy/windowsCommand.js.map +1 -0
  203. package/dist/mcp/report/buffer.js +351 -0
  204. package/dist/mcp/report/buffer.js.map +1 -0
  205. package/dist/mcp/report/coalesce.js +165 -0
  206. package/dist/mcp/report/coalesce.js.map +1 -0
  207. package/dist/mcp/report/constants.js +51 -0
  208. package/dist/mcp/report/constants.js.map +1 -0
  209. package/dist/mcp/report/project.js +83 -0
  210. package/dist/mcp/report/project.js.map +1 -0
  211. package/dist/mcp/report/reporter.js +443 -0
  212. package/dist/mcp/report/reporter.js.map +1 -0
  213. package/dist/mcp/report/types.js +3 -0
  214. package/dist/mcp/report/types.js.map +1 -0
  215. package/dist/mcp/report/validate.js +179 -0
  216. package/dist/mcp/report/validate.js.map +1 -0
  217. package/dist/mcp/scan/args.js +228 -0
  218. package/dist/mcp/scan/args.js.map +1 -0
  219. package/dist/mcp/scan/results.js +253 -0
  220. package/dist/mcp/scan/results.js.map +1 -0
  221. package/dist/mcp/scan/types.js +40 -0
  222. package/dist/mcp/scan/types.js.map +1 -0
  223. package/dist/mcp/verify/api.js +514 -0
  224. package/dist/mcp/verify/api.js.map +1 -0
  225. package/dist/mcp/verify/backend.js +143 -0
  226. package/dist/mcp/verify/backend.js.map +1 -0
  227. package/dist/mcp/verify/cache.js +200 -0
  228. package/dist/mcp/verify/cache.js.map +1 -0
  229. package/dist/mcp/verify/contract-errors.js +198 -0
  230. package/dist/mcp/verify/contract-errors.js.map +1 -0
  231. package/dist/mcp/verify/digest.js +91 -0
  232. package/dist/mcp/verify/digest.js.map +1 -0
  233. package/dist/mcp/verify/factory.js +56 -0
  234. package/dist/mcp/verify/factory.js.map +1 -0
  235. package/dist/mcp/verify/identity.js +286 -0
  236. package/dist/mcp/verify/identity.js.map +1 -0
  237. package/dist/mcp/verify/policy-store.js +63 -0
  238. package/dist/mcp/verify/policy-store.js.map +1 -0
  239. package/dist/mcp/verify/policy.js +160 -0
  240. package/dist/mcp/verify/policy.js.map +1 -0
  241. package/dist/mcp/verify/pool.js +27 -0
  242. package/dist/mcp/verify/pool.js.map +1 -0
  243. package/dist/mcp/verify/token-refresh.js +44 -0
  244. package/dist/mcp/verify/token-refresh.js.map +1 -0
  245. package/dist/mcp/verify/token.js +67 -0
  246. package/dist/mcp/verify/token.js.map +1 -0
  247. package/dist/mcp/verify/verifier.js +1166 -0
  248. package/dist/mcp/verify/verifier.js.map +1 -0
  249. package/dist/org-gate.js +1 -1
  250. package/dist/org-gate.js.map +1 -1
  251. package/dist/org.js +146 -0
  252. package/dist/org.js.map +1 -0
  253. package/dist/project-create.js +129 -0
  254. package/dist/project-create.js.map +1 -0
  255. package/dist/prompts.js +21 -0
  256. package/dist/prompts.js.map +1 -1
  257. package/dist/re2.wasm +0 -0
  258. package/dist/status.js +61 -0
  259. package/dist/status.js.map +1 -1
  260. package/dist/uninstall.js +49 -4
  261. package/dist/uninstall.js.map +1 -1
  262. package/dist/utils.js +62 -6
  263. package/dist/utils.js.map +1 -1
  264. package/package.json +10 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/mcp/verify/digest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC1B;IAArB,YAAqB,QAAgB;QACnC,KAAK,CAAC,+BAA+B,QAAQ,SAAS,CAAC,CAAC;QADrC,aAAQ,GAAR,QAAQ,CAAQ;QAEnC,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,KAAc,EAAE,KAAa;IACjD,IAAI,KAAK,GAAG,mBAAmB,EAAE,CAAC;QAChC,MAAM,IAAI,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACtB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC;SAC1D,IAAI,EAAE;SACN,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,YAAY,CAAE,KAAiC,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,CAC3F,CAAC;IACJ,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC;AAEnC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,MAAM,SAAS,GAAG,eAAe,CAAC;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;QACnC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;QACrC,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI;QACvC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;KACtC,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzE,OAAO,GAAG,cAAc,IAAI,GAAG,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { PROXY_ENV_PREFIX } from '../proxy/paths.js';
2
+ import { persistedOrganization, resolveBackendAccess } from './backend.js';
3
+ import { createJsonVerifyCache } from './cache.js';
4
+ import { upstreamServerId } from './identity.js';
5
+ import { verifyTokenResolver } from './token.js';
6
+ import { DEFAULT_VERIFY_TIMEOUT_MS, Verifier } from './verifier.js';
7
+ /**
8
+ * A verifier for `spec`, named `serverName` on every finding.
9
+ *
10
+ * `spec` is the STORED spec, never the expanded one: `upstreamServerId`
11
+ * hashes it, and that hash keys the cache and the user's overrides.
12
+ */
13
+ export function buildVerifier(spec, serverName, logger, env, overrides = {}) {
14
+ const serverId = upstreamServerId(spec);
15
+ const timeoutMs = overrides.timeoutMs ?? positiveMs(env[`${PROXY_ENV_PREFIX}VERIFY_TIMEOUT_MS`]) ?? DEFAULT_VERIFY_TIMEOUT_MS;
16
+ const resolved = overrides.backend === undefined
17
+ ? (overrides.resolveBackendAccessFn ?? resolveBackendAccess)({
18
+ env,
19
+ warn: (message, fields) => logger.warn(message, fields),
20
+ })
21
+ : overrides.backend;
22
+ const backend = resolved
23
+ ? {
24
+ ...resolved,
25
+ options: {
26
+ resolveTokenFn: verifyTokenResolver({ env }),
27
+ ...('options' in resolved ? (resolved.options ?? {}) : {}),
28
+ },
29
+ }
30
+ : null;
31
+ const verifier = new Verifier({
32
+ serverId,
33
+ serverName,
34
+ spec,
35
+ cache: overrides.cache ?? createJsonVerifyCache({ env }),
36
+ logger,
37
+ timeoutMs,
38
+ callTimeoutMs: overrides.callTimeoutMs,
39
+ now: overrides.now,
40
+ sessionId: overrides.sessionId,
41
+ env,
42
+ backend: backend ?? undefined,
43
+ // With no backend at all, the last known policy of the persisted
44
+ // organization still applies to what the cache decides.
45
+ organizationId: backend?.organizationId ?? persistedOrganization()?.id,
46
+ });
47
+ return { verifier, serverId, serverName, timeoutMs, backend };
48
+ }
49
+ /** A strictly positive number of milliseconds from an environment value, or nothing. */
50
+ export function positiveMs(value) {
51
+ if (!value)
52
+ return undefined;
53
+ const parsed = Number(value);
54
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
55
+ }
56
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/mcp/verify/factory.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAsB,MAAM,cAAc,CAAC;AAC/F,OAAO,EAAE,qBAAqB,EAAoB,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAC;AA8BxF;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAkB,EAClB,UAAkB,EAClB,MAAc,EACd,GAAsB,EACtB,YAAoC,EAAE;IAEtC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,SAAS,GACb,SAAS,CAAC,SAAS,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,gBAAgB,mBAAmB,CAAC,CAAC,IAAI,yBAAyB,CAAC;IAC9G,MAAM,QAAQ,GACZ,SAAS,CAAC,OAAO,KAAK,SAAS;QAC7B,CAAC,CAAC,CAAC,SAAS,CAAC,sBAAsB,IAAI,oBAAoB,CAAC,CAAC;YACzD,GAAG;YACH,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC;SACxD,CAAC;QACJ,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;IACxB,MAAM,OAAO,GAAyB,QAAQ;QAC5C,CAAC,CAAC;YACE,GAAG,QAAQ;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,mBAAmB,CAAC,EAAE,GAAG,EAAE,CAAC;gBAC5C,GAAG,CAAC,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aAC3D;SACF;QACH,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC;QAC5B,QAAQ;QACR,UAAU;QACV,IAAI;QACJ,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,qBAAqB,CAAC,EAAE,GAAG,EAAE,CAAC;QACxD,MAAM;QACN,SAAS;QACT,aAAa,EAAE,SAAS,CAAC,aAAa;QACtC,GAAG,EAAE,SAAS,CAAC,GAAG;QAClB,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,GAAG;QACH,OAAO,EAAE,OAAO,IAAI,SAAS;QAC7B,iEAAiE;QACjE,wDAAwD;QACxD,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,qBAAqB,EAAE,EAAE,EAAE;KACvE,CAAC,CAAC;IAEH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAChE,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,UAAU,CAAC,KAAyB;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACpE,CAAC"}
@@ -0,0 +1,286 @@
1
+ /** Identity of a coordinate, used to deduplicate one server across configs. */
2
+ export function coordinateKey(c) {
3
+ if (c.url)
4
+ return `url:${c.url}`;
5
+ if (c.purl)
6
+ return `purl:${c.purl}`;
7
+ return `pkg:${c.ecosystem ?? ''}:${c.packageName ?? ''}:${c.version ?? ''}`;
8
+ }
9
+ // ─── Stable identity of a discovered server ──────────────────────────────────
10
+ /**
11
+ * Names the identity of a server in a way that is stable across runs. The
12
+ * discovery layer has already deduplicated by coordinate, so every entry it
13
+ * yields is one physical server; `source` and `name` only matter for servers
14
+ * that resolved to no coordinate.
15
+ */
16
+ export function serverIdOf(server) {
17
+ if (server.coordinate)
18
+ return coordinateKey(server.coordinate);
19
+ return `local:${server.source}:${server.name}`;
20
+ }
21
+ // ─── purl / server-check coordinate ──────────────────────────────────────────
22
+ /**
23
+ * Builds a `purl` (package URL) from an npm/pip coordinate.
24
+ *
25
+ * The namespace of a SCOPED npm package is a path segment of its own
26
+ * (`pkg:npm/%40scope/name@version`), not a prefix encoded together with the
27
+ * name (`pkg:npm/%40scope%2Fname@version`). Encoding `@scope/name` as one
28
+ * string also encodes the `/` separator, which produced a non-conforming purl
29
+ * for every `@modelcontextprotocol/server-*` package.
30
+ */
31
+ export function purlOf(coordinate) {
32
+ if (coordinate.purl)
33
+ return coordinate.purl;
34
+ if (!coordinate.ecosystem || !coordinate.packageName)
35
+ return undefined;
36
+ const type = coordinate.ecosystem === 'pip' ? 'pypi' : coordinate.ecosystem;
37
+ const versionSuffix = coordinate.version ? `@${encodeURIComponent(coordinate.version)}` : '';
38
+ if (type === 'npm' && coordinate.packageName.startsWith('@')) {
39
+ const withoutAt = coordinate.packageName.slice(1); // "scope/name"
40
+ const slashIdx = withoutAt.indexOf('/');
41
+ if (slashIdx > 0) {
42
+ const namespace = withoutAt.slice(0, slashIdx);
43
+ const name = withoutAt.slice(slashIdx + 1);
44
+ return `pkg:npm/%40${encodeURIComponent(namespace)}/${encodeURIComponent(name)}${versionSuffix}`;
45
+ }
46
+ }
47
+ return `pkg:${type}/${encodeURIComponent(coordinate.packageName)}${versionSuffix}`;
48
+ }
49
+ /** What `POST /mcp/servers/check` accepts for this coordinate, or `null` when nothing applies. */
50
+ export function checkInputOf(coordinate) {
51
+ if (!coordinate)
52
+ return null;
53
+ if (coordinate.url)
54
+ return { url: coordinate.url };
55
+ const purl = purlOf(coordinate);
56
+ return purl ? { purl } : null;
57
+ }
58
+ export function checkInputKey(input) {
59
+ return input.url ? `url:${input.url}` : `purl:${input.purl ?? ''}`;
60
+ }
61
+ // ─── Identity of an upstream the proxy runs in front of (Phase 3) ────────────
62
+ import { createHash } from 'node:crypto';
63
+ import { canonicalMcpUrl } from '../auth/oauth.js';
64
+ import { HOST_PATTERN, LABEL_PATTERN, NPM_NAME_PATTERN, PACKAGE_VERSION_PATTERN, PYPI_NAME_PATTERN, } from '../report/constants.js';
65
+ /**
66
+ * Package launchers whose first non-flag argument names a package.
67
+ *
68
+ * `npx`, `pnpm dlx` and `bunx` for npm; `uvx` and `pipx` for PyPI. Anything
69
+ * else — a path to a script, a binary on `PATH`, a shell wrapper — has no
70
+ * package coordinate to read, and pretending otherwise would file two
71
+ * unrelated servers under one identity.
72
+ */
73
+ const NPM_LAUNCHERS = new Set(['npx', 'bunx', 'pnpx']);
74
+ const PYPI_LAUNCHERS = new Set(['uvx', 'pipx']);
75
+ /** Flags a launcher takes before the package name. */
76
+ const LAUNCHER_FLAGS = new Set(['-y', '--yes', '-q', '--quiet', '--silent', '-p', '--package', 'run']);
77
+ /** Strips a `.cmd` / `.exe` suffix and any directory, so `C:\…\npx.cmd` reads as `npx`. */
78
+ export function launcherName(command) {
79
+ const base = command.replace(/\\/g, '/').split('/').pop() ?? command;
80
+ return base.replace(/\.(cmd|exe|bat|ps1)$/i, '').toLowerCase();
81
+ }
82
+ /**
83
+ * The package a launcher command runs, or `null`.
84
+ *
85
+ * `npx -y @scope/name@1.2.3` reads as `@scope/name` at `1.2.3`. The leading
86
+ * `@` of a scope is not a version separator, which is the one thing a naive
87
+ * `split('@')` gets wrong on exactly the packages MCP servers ship as.
88
+ */
89
+ export function parsePackageCoordinate(command, args) {
90
+ const launcher = launcherName(command);
91
+ const ecosystem = NPM_LAUNCHERS.has(launcher)
92
+ ? 'npm'
93
+ : PYPI_LAUNCHERS.has(launcher)
94
+ ? 'pypi'
95
+ : null;
96
+ if (!ecosystem)
97
+ return null;
98
+ const spec = args.find((arg) => !arg.startsWith('-') && !LAUNCHER_FLAGS.has(arg));
99
+ if (!spec)
100
+ return null;
101
+ // A scope starts with `@`; the version separator is the `@` AFTER it.
102
+ const separator = spec.lastIndexOf('@');
103
+ if (separator > 0) {
104
+ const version = spec.slice(separator + 1);
105
+ // `@scope/name` has its only `@` at index 0, already excluded above; a
106
+ // trailing `@` with nothing after it pins nothing.
107
+ if (version !== '' && !version.includes('/')) {
108
+ return { ecosystem, name: spec.slice(0, separator), version };
109
+ }
110
+ }
111
+ return { ecosystem, name: spec };
112
+ }
113
+ /**
114
+ * The identity the proxy files a server under.
115
+ *
116
+ * Four forms, in the order they are tried:
117
+ *
118
+ * `url:<canonical>` an HTTP server, keyed the same way its
119
+ * credential is (`canonicalMcpUrl`), so the two
120
+ * never disagree about which server this is;
121
+ * `npm:<pkg>@<version>` a pinned package, shareable across
122
+ * `pypi:<pkg>@<version>` organisations because it names the same bytes
123
+ * for everybody;
124
+ * `cmd:sha256(...)` anything else, including an UNPINNED package.
125
+ *
126
+ * The last one covers more than it looks: `npx -y @scope/server` with no
127
+ * version is the shape most people use, and it lands here on purpose. A
128
+ * verdict on "whatever npx resolves today" is not a fact about a package, it
129
+ * is a fact about one machine on one day, and sharing it between
130
+ * organisations would be sharing a guess.
131
+ */
132
+ export function upstreamServerId(spec) {
133
+ if (spec.transport === 'http') {
134
+ return `url:${canonicalMcpUrl(spec.url) ?? spec.url}`;
135
+ }
136
+ const coordinate = parsePackageCoordinate(spec.command, spec.args);
137
+ if (coordinate?.version) {
138
+ return `${coordinate.ecosystem}:${coordinate.name}@${coordinate.version}`;
139
+ }
140
+ return commandIdentity(spec);
141
+ }
142
+ /**
143
+ * `cmd:<sha256>` of a local command and its arguments, in order, separated by
144
+ * a character no argument can contain: `a b` and `a` `b` must not hash alike.
145
+ */
146
+ export function commandIdentity(spec) {
147
+ const material = [spec.command, ...spec.args].join('\u0000');
148
+ return `cmd:${createHash('sha256').update(material).digest('hex')}`;
149
+ }
150
+ /** A filesystem-safe name for the cache file of a server id. */
151
+ export function serverIdFileName(serverId) {
152
+ // The id may contain `/`, `:` and `@`; the hash keeps it unambiguous while
153
+ // the readable prefix keeps the cache directory browsable.
154
+ const readable = serverId.replace(/[^A-Za-z0-9._-]+/g, '-').slice(0, 48).replace(/^-+|-+$/g, '');
155
+ const fingerprint = createHash('sha256').update(serverId).digest('hex').slice(0, 16);
156
+ return `${readable || 'server'}.${fingerprint}.json`;
157
+ }
158
+ /**
159
+ * A package name an advisory list could name: the npm or PyPI characters, of
160
+ * any case (a legacy npm name such as `JSONStream` is uppercase). Anything
161
+ * else read from a launcher's arguments (a git URL, a path) is not sent.
162
+ */
163
+ const PUBLIC_NPM_NAME = /^(@[A-Za-z0-9._~-]+\/)?[A-Za-z0-9._~-]+$/;
164
+ const PUBLIC_PYPI_NAME = /^[A-Za-z0-9._-]+$/;
165
+ /**
166
+ * What `POST …/servers/check` accepts for an upstream, or `null`.
167
+ *
168
+ * A server the report files as `local` sends its `serverKey`, which the
169
+ * backend matches against organisation blocks. When it still has a public
170
+ * coordinate — a URL whose host the report refuses (IPv6, an underscore), a
171
+ * package name the report refuses (uppercase, past 214 characters) — that
172
+ * coordinate goes with it, so the advisory lookup it had before 1.7.0 stays.
173
+ * A local command with none sends its key alone.
174
+ */
175
+ export function upstreamCheckInput(spec) {
176
+ const identity = reportServerIdentity(spec);
177
+ if (identity?.kind === 'local') {
178
+ const coordinate = localPublicCoordinate(spec);
179
+ return coordinate ? { ...coordinate, serverKey: identity.serverKey } : { serverKey: identity.serverKey };
180
+ }
181
+ if (spec.transport === 'http') {
182
+ const url = canonicalMcpUrl(spec.url);
183
+ return url ? { url } : null;
184
+ }
185
+ const coordinate = parsePackageCoordinate(spec.command, spec.args);
186
+ if (!coordinate)
187
+ return null;
188
+ return checkInputOf({
189
+ ecosystem: coordinate.ecosystem === 'pypi' ? 'pip' : 'npm',
190
+ packageName: coordinate.name,
191
+ version: coordinate.version,
192
+ });
193
+ }
194
+ /** The URL or the package of a server the report files as local, or `null`. */
195
+ function localPublicCoordinate(spec) {
196
+ if (spec.transport === 'http') {
197
+ const url = canonicalMcpUrl(spec.url);
198
+ return url ? { url } : null;
199
+ }
200
+ const coordinate = parsePackageCoordinate(spec.command, spec.args);
201
+ if (!coordinate)
202
+ return null;
203
+ const named = coordinate.ecosystem === 'npm'
204
+ ? PUBLIC_NPM_NAME.test(coordinate.name)
205
+ : PUBLIC_PYPI_NAME.test(coordinate.name);
206
+ if (!named)
207
+ return null;
208
+ const purl = purlOf({
209
+ ecosystem: coordinate.ecosystem === 'pypi' ? 'pip' : 'npm',
210
+ packageName: coordinate.name,
211
+ version: coordinate.version && PACKAGE_VERSION_PATTERN.test(coordinate.version) ? coordinate.version : undefined,
212
+ });
213
+ return purl ? { purl } : null;
214
+ }
215
+ /**
216
+ * Whether the upstream has a public coordinate an advisory list can name: a
217
+ * URL, or an npm/pypi package (pinned or not). A local command has none, so a server record is
218
+ * never stale for want of an advisory lookup.
219
+ */
220
+ export function hasPublicCoordinate(spec) {
221
+ const input = upstreamCheckInput(spec);
222
+ return input !== null && (input.url !== undefined || input.purl !== undefined);
223
+ }
224
+ const sha256Hex = (value) => createHash('sha256').update(value).digest('hex');
225
+ /** A config entry name as a report label, or `undefined` when nothing safe is left of it. */
226
+ export function reportLabel(raw) {
227
+ if (!raw)
228
+ return undefined;
229
+ const label = raw
230
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
231
+ .replace(/^-+|-+$/g, '')
232
+ .slice(0, 64)
233
+ .replace(/-+$/g, '');
234
+ return LABEL_PATTERN.test(label) ? label : undefined;
235
+ }
236
+ /**
237
+ * The identity a report files a server under, or `null` for an HTTP spec whose
238
+ * URL does not canonicalise. Does not use `describeUpstream`, which prints
239
+ * what a report must never carry.
240
+ */
241
+ export function reportServerIdentity(spec, entryName) {
242
+ const entryLabel = reportLabel(entryName);
243
+ if (spec.transport === 'http') {
244
+ const canonical = canonicalMcpUrl(spec.url);
245
+ if (!canonical)
246
+ return null;
247
+ const serverKey = sha256Hex(`url:${canonical}`);
248
+ const host = new URL(canonical).hostname;
249
+ if (!HOST_PATTERN.test(host)) {
250
+ // An IPv6 literal: no host a report may carry. Same key, reported as local.
251
+ return {
252
+ serverKey,
253
+ kind: 'local',
254
+ label: entryLabel ?? reportLabel(host.replace(/[[\]]/g, '')) ?? 'local',
255
+ };
256
+ }
257
+ return { serverKey, kind: 'remote', host, ...(entryLabel ? { label: entryLabel } : {}) };
258
+ }
259
+ const coordinate = parsePackageCoordinate(spec.command, spec.args);
260
+ if (coordinate) {
261
+ const nameOk = coordinate.ecosystem === 'npm'
262
+ ? NPM_NAME_PATTERN.test(coordinate.name)
263
+ : PYPI_NAME_PATTERN.test(coordinate.name);
264
+ if (nameOk) {
265
+ const version = coordinate.version && PACKAGE_VERSION_PATTERN.test(coordinate.version)
266
+ ? coordinate.version
267
+ : undefined;
268
+ return {
269
+ serverKey: sha256Hex(`${coordinate.ecosystem}:${coordinate.name}`),
270
+ kind: 'package',
271
+ package: {
272
+ ecosystem: coordinate.ecosystem,
273
+ name: coordinate.name,
274
+ ...(version ? { version } : {}),
275
+ },
276
+ ...(entryLabel ? { label: entryLabel } : {}),
277
+ };
278
+ }
279
+ }
280
+ return {
281
+ serverKey: sha256Hex(commandIdentity(spec)),
282
+ kind: 'local',
283
+ label: entryLabel ?? reportLabel(launcherName(spec.command)) ?? 'local',
284
+ };
285
+ }
286
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/mcp/verify/identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The last known policy of each organization, on this machine.
3
+ *
4
+ * `~/.cybedefend/mcp/policy.json`, keyed by organization id. It exists for
5
+ * the degraded session — backend unreachable, or no credential — which must
6
+ * still decide under the policy the organization actually has rather than
7
+ * under the default: an organization that set `block` last week has set it
8
+ * for the day the network is down too. Keyed by organization so a machine
9
+ * that moves between two of them never carries one's `block` into the
10
+ * other.
11
+ *
12
+ * Written by the verifier every time a response carries a policy, and by
13
+ * `GET …/policy`. Never a lever: nothing on this machine reads it as a
14
+ * setting, only as a memory of what the server said.
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { writeFileAtomic } from '../config/atomicWrite.js';
19
+ import { mcpHomeDir } from '../proxy/paths.js';
20
+ import { isPolicyMode } from './policy.js';
21
+ export function policyFile(env = process.env) {
22
+ return join(mcpHomeDir(env), 'policy.json');
23
+ }
24
+ function readAll(env) {
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(readFileSync(policyFile(env), 'utf8'));
28
+ }
29
+ catch {
30
+ return {};
31
+ }
32
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
33
+ }
34
+ function toStoredPolicy(raw) {
35
+ if (!raw || typeof raw !== 'object')
36
+ return null;
37
+ const shape = raw;
38
+ if (!isPolicyMode(shape.mode) || typeof shape.strictIssuer !== 'boolean')
39
+ return null;
40
+ return {
41
+ mode: shape.mode,
42
+ strictIssuer: shape.strictIssuer,
43
+ learnedAt: typeof shape.learnedAt === 'string' ? shape.learnedAt : new Date(0).toISOString(),
44
+ };
45
+ }
46
+ /** What was last learned about this organization, or `null`. A corrupt entry is nothing known. */
47
+ export function readStoredPolicy(organizationId, options = {}) {
48
+ const env = options.env ?? process.env;
49
+ return toStoredPolicy(readAll(env)[organizationId]);
50
+ }
51
+ /** Records the policy for this organization, leaving every other organization's entry as it is. */
52
+ export function writeStoredPolicy(organizationId, policy, options = {}) {
53
+ const env = options.env ?? process.env;
54
+ const all = readAll(env);
55
+ const entry = {
56
+ mode: policy.mode,
57
+ strictIssuer: policy.strictIssuer,
58
+ learnedAt: (options.now ?? new Date()).toISOString(),
59
+ };
60
+ all[organizationId] = entry;
61
+ writeFileAtomic(policyFile(env), `${JSON.stringify(all, null, 2)}\n`, { mode: 0o600 });
62
+ }
63
+ //# sourceMappingURL=policy-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-store.js","sourceRoot":"","sources":["../../../src/mcp/verify/policy-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAkB,MAAM,aAAa,CAAC;AAY3D,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,OAAO,CAAC,GAAsB;IACrC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnH,CAAC;AAED,SAAS,cAAc,CAAC,GAAY;IAClC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACjD,MAAM,KAAK,GAAG,GAA8B,CAAC;IAC7C,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtF,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,SAAS,EAAE,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;KAC7F,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,gBAAgB,CAAC,cAAsB,EAAE,UAA8B,EAAE;IACvF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,OAAO,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,iBAAiB,CAAC,cAAsB,EAAE,MAAiB,EAAE,UAA8B,EAAE;IAC3G,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,KAAK,GAAiB;QAC1B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;KACrD,CAAC;IACF,GAAG,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC;IAC5B,eAAe,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1,160 @@
1
+ export const SERVER_ACTIONS = ['allow', 'ask', 'warn', 'block', 'pending'];
2
+ export function isServerAction(value) {
3
+ return typeof value === 'string' && SERVER_ACTIONS.includes(value);
4
+ }
5
+ export function isPolicyMode(value) {
6
+ return value === 'warn' || value === 'block';
7
+ }
8
+ /**
9
+ * Used ONLY when nothing was ever learned from the backend — the same
10
+ * fallback global-management applies to an organization that never set a
11
+ * policy. Not a lever: nothing on this machine can change it.
12
+ */
13
+ /**
14
+ * Used only when this machine never learned any policy for the organization —
15
+ * the same fallback global-management serves for an organization that never
16
+ * set one (`block` since 2026-09-15): a verdict nobody chose to relax is refused.
17
+ */
18
+ export const DEFAULT_ORG_POLICY = Object.freeze({ mode: 'block', strictIssuer: false });
19
+ /**
20
+ * The backend's table, recomputed for a record that has no fresh answer.
21
+ *
22
+ * `docs/mcp-verify-integration.md` §1, in our verdict vocabulary: not
23
+ * verified ⇒ `pending`; CLEAN ⇒ `allow`; SUSPICIOUS ⇒ `ask` in every mode
24
+ * (the user decides, at the call); MALICIOUS ⇒ the mode. A `contract-error`
25
+ * is not a verdict about the server and stays undecided. A fresh action from
26
+ * a response always wins over this — see `Verifier`; this is for the cache.
27
+ */
28
+ export function actionFor(verdict, policy) {
29
+ switch (verdict) {
30
+ case 'ok':
31
+ return 'allow';
32
+ case 'suspicious':
33
+ return 'ask';
34
+ case 'malicious':
35
+ return policy.mode;
36
+ default:
37
+ return 'pending';
38
+ }
39
+ }
40
+ /**
41
+ * The worst of a set of actions, `block` first. `pending` ranks above
42
+ * `allow` so that a list with anything unverified in it is not called clean.
43
+ */
44
+ const ACTION_SEVERITY = {
45
+ block: 5,
46
+ warn: 4,
47
+ ask: 3,
48
+ pending: 2,
49
+ allow: 1,
50
+ };
51
+ export function worstAction(actions) {
52
+ let worst = 'allow';
53
+ for (const action of actions) {
54
+ if (ACTION_SEVERITY[action] > ACTION_SEVERITY[worst])
55
+ worst = action;
56
+ }
57
+ return worst;
58
+ }
59
+ /**
60
+ * The worst of a set of verdicts.
61
+ *
62
+ * "Worst" is not the same as "most recent": a server whose ten tools are
63
+ * clean and whose eleventh is malicious is a malicious server, and averaging
64
+ * would be a way of losing exactly the finding that matters.
65
+ */
66
+ const SEVERITY = {
67
+ malicious: 5,
68
+ suspicious: 4,
69
+ // ⚠️ Above `pending`, below `suspicious`, and both halves matter.
70
+ //
71
+ // Above, because a server whose tools are all unverified BECAUSE the backend
72
+ // refuses our requests is not in the same state as one we simply have not
73
+ // got to yet — and only the aggregate is rendered.
74
+ //
75
+ // Below, because a contract error must never MASK a real security verdict.
76
+ // If it outranked `malicious`, breaking the contract would become a way of
77
+ // erasing a blacklist.
78
+ 'contract-error': 3,
79
+ pending: 2,
80
+ unknown: 1,
81
+ ok: 0,
82
+ };
83
+ export function worstVerdict(verdicts) {
84
+ let worst = 'ok';
85
+ for (const verdict of verdicts) {
86
+ if (SEVERITY[verdict] > SEVERITY[worst])
87
+ worst = verdict;
88
+ }
89
+ return worst;
90
+ }
91
+ export function serverVerdict(server, tools) {
92
+ return worstVerdict([server.verdict, ...tools.map((tool) => tool.verdict)]);
93
+ }
94
+ /**
95
+ * Decides — that is, executes.
96
+ *
97
+ * The user's own decision comes first, in both directions: an `allow` on a
98
+ * server the backend calls malicious is a decision they were shown the reason
99
+ * for and made anyway, and overriding it would be telling them they were
100
+ * wrong every session. A `deny` blocks whatever the backend says, including
101
+ * `allow`.
102
+ *
103
+ * Then the server's action, verbatim: `block` blocks, `warn` alerts and lets
104
+ * the call through, `ask` alerts and has the user confirm the call. The
105
+ * rationale names the verdict the action came from, which is what the agent
106
+ * and the user are told.
107
+ */
108
+ export function decide(input) {
109
+ if (input.organizationBlock)
110
+ return { action: 'block', rationale: 'blocked-by-organization' };
111
+ if (input.override === 'deny')
112
+ return { action: 'block', rationale: 'denied-by-user' };
113
+ if (input.override === 'allow')
114
+ return { action: 'pass', rationale: 'allowed-by-user' };
115
+ if (input.action === 'block')
116
+ return { action: 'block', rationale: 'malicious' };
117
+ if (input.action === 'warn')
118
+ return { action: 'alert', rationale: 'malicious' };
119
+ if (input.action === 'ask')
120
+ return { action: 'alert', rationale: 'suspicious' };
121
+ // Our own machinery, not this server. It ALERTS and never blocks: blocking
122
+ // on a contract disagreement would be a fail-closed triggered by a bug of
123
+ // ours, on servers we know strictly nothing about — the most expensive
124
+ // possible mistake, where the product becomes the incident. And it only
125
+ // speaks once it has survived a restart; see `contractErrorPersisted`.
126
+ if (input.verdict === 'contract-error') {
127
+ return input.contractErrorPersisted
128
+ ? { action: 'alert', rationale: 'contract-error' }
129
+ : { action: 'pass', rationale: 'not-verified' };
130
+ }
131
+ // A list that changed under us is worth a word even when the new content
132
+ // reads clean: the change itself is the signal, and it is the shape a
133
+ // rug-pull takes.
134
+ if (input.changedDuringSession)
135
+ return { action: 'alert', rationale: 'changed-mid-session' };
136
+ if (input.action === 'pending')
137
+ return { action: 'pass', rationale: 'not-verified' };
138
+ return { action: 'pass', rationale: 'clean' };
139
+ }
140
+ /**
141
+ * Whether to refuse a server the backend cannot be asked about right now.
142
+ *
143
+ * Section 0: fail-open by default, fail-closed only for a server the LOCAL
144
+ * cache already calls malicious and that the user has not allowed. The
145
+ * asymmetry is the point — an outage must not turn every server off, and it
146
+ * must not turn a known-bad one back on either.
147
+ *
148
+ * It takes the AGGREGATE verdict, not the server record's own: a server whose
149
+ * ten tools are clean and whose eleventh is known malicious is a malicious
150
+ * server, and the record on the server itself is often `unknown` — a local
151
+ * command has no public coordinate for the advisory list to be about.
152
+ */
153
+ export function failClosed(verdict, override) {
154
+ if (override === 'allow')
155
+ return false;
156
+ if (override === 'deny')
157
+ return true;
158
+ return verdict === 'malicious';
159
+ }
160
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/mcp/verify/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A few things at a time, and no dependency to do it.
3
+ *
4
+ * Used where a list of independent calls would otherwise run one after the
5
+ * other — the slices of an audit, the servers of a scan — and where running
6
+ * them all at once would be as wrong: a backend has a request cap, and a
7
+ * hundred stdio servers spawned together is a fork bomb with a security
8
+ * label on it.
9
+ */
10
+ /**
11
+ * Runs `worker` over `items`, at most `concurrency` at a time, in order of
12
+ * start. Resolves when every item is done; a worker that throws fails the
13
+ * whole run, so a worker that must not takes care of its own errors.
14
+ */
15
+ export async function runPool(items, concurrency, worker) {
16
+ const width = Math.max(1, Math.min(Math.floor(concurrency), items.length));
17
+ let next = 0;
18
+ const lane = async () => {
19
+ while (next < items.length) {
20
+ const index = next;
21
+ next += 1;
22
+ await worker(items[index], index);
23
+ }
24
+ };
25
+ await Promise.all(Array.from({ length: width }, () => lane()));
26
+ }
27
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../src/mcp/verify/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAmB,EACnB,WAAmB,EACnB,MAAiD;IAEjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3E,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC;YACnB,IAAI,IAAI,CAAC,CAAC;YACV,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CAAC;IACF,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC"}