ax-audit 3.6.0 → 4.1.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 (202) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/LICENSE +1 -1
  3. package/README.md +58 -36
  4. package/dist/baseline.d.ts +2 -0
  5. package/dist/baseline.d.ts.map +1 -1
  6. package/dist/baseline.js +42 -4
  7. package/dist/baseline.js.map +1 -1
  8. package/dist/check-ids.d.ts +19 -0
  9. package/dist/check-ids.d.ts.map +1 -0
  10. package/dist/check-ids.js +53 -0
  11. package/dist/check-ids.js.map +1 -0
  12. package/dist/checks/agent-access.d.ts +23 -6
  13. package/dist/checks/agent-access.d.ts.map +1 -1
  14. package/dist/checks/agent-access.js +200 -54
  15. package/dist/checks/agent-access.js.map +1 -1
  16. package/dist/checks/agent-card.d.ts +37 -0
  17. package/dist/checks/agent-card.d.ts.map +1 -0
  18. package/dist/checks/agent-card.js +352 -0
  19. package/dist/checks/agent-card.js.map +1 -0
  20. package/dist/checks/agent-operability.d.ts +66 -0
  21. package/dist/checks/agent-operability.d.ts.map +1 -0
  22. package/dist/checks/agent-operability.js +383 -0
  23. package/dist/checks/agent-operability.js.map +1 -0
  24. package/dist/checks/agent-skills.d.ts +24 -0
  25. package/dist/checks/agent-skills.d.ts.map +1 -0
  26. package/dist/checks/agent-skills.js +316 -0
  27. package/dist/checks/agent-skills.js.map +1 -0
  28. package/dist/checks/ai-catalog.d.ts +28 -0
  29. package/dist/checks/ai-catalog.d.ts.map +1 -0
  30. package/dist/checks/ai-catalog.js +254 -0
  31. package/dist/checks/ai-catalog.js.map +1 -0
  32. package/dist/checks/ai-directives.d.ts +57 -0
  33. package/dist/checks/ai-directives.d.ts.map +1 -0
  34. package/dist/checks/ai-directives.js +263 -0
  35. package/dist/checks/ai-directives.js.map +1 -0
  36. package/dist/checks/api-discovery.d.ts +26 -0
  37. package/dist/checks/api-discovery.d.ts.map +1 -0
  38. package/dist/checks/api-discovery.js +432 -0
  39. package/dist/checks/api-discovery.js.map +1 -0
  40. package/dist/checks/auth-discovery.d.ts +28 -0
  41. package/dist/checks/auth-discovery.d.ts.map +1 -0
  42. package/dist/checks/auth-discovery.js +213 -0
  43. package/dist/checks/auth-discovery.js.map +1 -0
  44. package/dist/checks/commerce-discovery.d.ts +40 -0
  45. package/dist/checks/commerce-discovery.d.ts.map +1 -0
  46. package/dist/checks/commerce-discovery.js +295 -0
  47. package/dist/checks/commerce-discovery.js.map +1 -0
  48. package/dist/checks/content-negotiation.d.ts.map +1 -1
  49. package/dist/checks/content-negotiation.js +135 -20
  50. package/dist/checks/content-negotiation.js.map +1 -1
  51. package/dist/checks/crawl-efficiency.d.ts +13 -1
  52. package/dist/checks/crawl-efficiency.d.ts.map +1 -1
  53. package/dist/checks/crawl-efficiency.js +65 -1
  54. package/dist/checks/crawl-efficiency.js.map +1 -1
  55. package/dist/checks/frontmatter.d.ts +34 -0
  56. package/dist/checks/frontmatter.d.ts.map +1 -0
  57. package/dist/checks/frontmatter.js +100 -0
  58. package/dist/checks/frontmatter.js.map +1 -0
  59. package/dist/checks/html-rendering.d.ts.map +1 -1
  60. package/dist/checks/html-rendering.js +0 -1
  61. package/dist/checks/html-rendering.js.map +1 -1
  62. package/dist/checks/html-utils.d.ts +10 -0
  63. package/dist/checks/html-utils.d.ts.map +1 -1
  64. package/dist/checks/html-utils.js +19 -0
  65. package/dist/checks/html-utils.js.map +1 -1
  66. package/dist/checks/http-headers.d.ts.map +1 -1
  67. package/dist/checks/http-headers.js +82 -10
  68. package/dist/checks/http-headers.js.map +1 -1
  69. package/dist/checks/http-hygiene.d.ts +26 -0
  70. package/dist/checks/http-hygiene.d.ts.map +1 -0
  71. package/dist/checks/http-hygiene.js +257 -0
  72. package/dist/checks/http-hygiene.js.map +1 -0
  73. package/dist/checks/index.d.ts.map +1 -1
  74. package/dist/checks/index.js +24 -8
  75. package/dist/checks/index.js.map +1 -1
  76. package/dist/checks/llms-txt.d.ts +15 -0
  77. package/dist/checks/llms-txt.d.ts.map +1 -1
  78. package/dist/checks/llms-txt.js +162 -2
  79. package/dist/checks/llms-txt.js.map +1 -1
  80. package/dist/checks/mcp-discovery.d.ts +30 -0
  81. package/dist/checks/mcp-discovery.d.ts.map +1 -0
  82. package/dist/checks/mcp-discovery.js +523 -0
  83. package/dist/checks/mcp-discovery.js.map +1 -0
  84. package/dist/checks/meta-tags.d.ts.map +1 -1
  85. package/dist/checks/meta-tags.js +6 -5
  86. package/dist/checks/meta-tags.js.map +1 -1
  87. package/dist/checks/robots-parser.d.ts +110 -0
  88. package/dist/checks/robots-parser.d.ts.map +1 -0
  89. package/dist/checks/robots-parser.js +277 -0
  90. package/dist/checks/robots-parser.js.map +1 -0
  91. package/dist/checks/robots-txt.d.ts +2 -20
  92. package/dist/checks/robots-txt.d.ts.map +1 -1
  93. package/dist/checks/robots-txt.js +219 -120
  94. package/dist/checks/robots-txt.js.map +1 -1
  95. package/dist/checks/rsl.d.ts +0 -2
  96. package/dist/checks/rsl.d.ts.map +1 -1
  97. package/dist/checks/rsl.js +1 -11
  98. package/dist/checks/rsl.js.map +1 -1
  99. package/dist/checks/security-txt.d.ts.map +1 -1
  100. package/dist/checks/security-txt.js +0 -1
  101. package/dist/checks/security-txt.js.map +1 -1
  102. package/dist/checks/seo-basics.d.ts.map +1 -1
  103. package/dist/checks/seo-basics.js +0 -1
  104. package/dist/checks/seo-basics.js.map +1 -1
  105. package/dist/checks/sitemap.d.ts.map +1 -1
  106. package/dist/checks/sitemap.js +0 -1
  107. package/dist/checks/sitemap.js.map +1 -1
  108. package/dist/checks/structured-data.d.ts.map +1 -1
  109. package/dist/checks/structured-data.js +215 -4
  110. package/dist/checks/structured-data.js.map +1 -1
  111. package/dist/checks/structured-fields.d.ts +46 -0
  112. package/dist/checks/structured-fields.d.ts.map +1 -0
  113. package/dist/checks/structured-fields.js +112 -0
  114. package/dist/checks/structured-fields.js.map +1 -0
  115. package/dist/checks/surface.d.ts +59 -0
  116. package/dist/checks/surface.d.ts.map +1 -0
  117. package/dist/checks/surface.js +106 -0
  118. package/dist/checks/surface.js.map +1 -0
  119. package/dist/checks/tls-https.d.ts.map +1 -1
  120. package/dist/checks/tls-https.js +0 -1
  121. package/dist/checks/tls-https.js.map +1 -1
  122. package/dist/checks/usage-policy.d.ts +53 -0
  123. package/dist/checks/usage-policy.d.ts.map +1 -0
  124. package/dist/checks/usage-policy.js +339 -0
  125. package/dist/checks/usage-policy.js.map +1 -0
  126. package/dist/checks/utils.d.ts +25 -1
  127. package/dist/checks/utils.d.ts.map +1 -1
  128. package/dist/checks/utils.js +33 -1
  129. package/dist/checks/utils.js.map +1 -1
  130. package/dist/checks/waf.d.ts +75 -0
  131. package/dist/checks/waf.d.ts.map +1 -0
  132. package/dist/checks/waf.js +203 -0
  133. package/dist/checks/waf.js.map +1 -0
  134. package/dist/checks/webmcp.d.ts +55 -0
  135. package/dist/checks/webmcp.d.ts.map +1 -0
  136. package/dist/checks/webmcp.js +209 -0
  137. package/dist/checks/webmcp.js.map +1 -0
  138. package/dist/checks/well-known.d.ts +38 -0
  139. package/dist/checks/well-known.d.ts.map +1 -0
  140. package/dist/checks/well-known.js +202 -0
  141. package/dist/checks/well-known.js.map +1 -0
  142. package/dist/cli.d.ts +12 -0
  143. package/dist/cli.d.ts.map +1 -1
  144. package/dist/cli.js +110 -4
  145. package/dist/cli.js.map +1 -1
  146. package/dist/constants.d.ts +195 -14
  147. package/dist/constants.d.ts.map +1 -1
  148. package/dist/constants.js +598 -70
  149. package/dist/constants.js.map +1 -1
  150. package/dist/fetcher.d.ts.map +1 -1
  151. package/dist/fetcher.js +44 -14
  152. package/dist/fetcher.js.map +1 -1
  153. package/dist/guide-urls.js +1 -1
  154. package/dist/guide-urls.js.map +1 -1
  155. package/dist/index.d.ts +22 -2
  156. package/dist/index.d.ts.map +1 -1
  157. package/dist/index.js +20 -1
  158. package/dist/index.js.map +1 -1
  159. package/dist/orchestrator.d.ts.map +1 -1
  160. package/dist/orchestrator.js +5 -1
  161. package/dist/orchestrator.js.map +1 -1
  162. package/dist/reporter/html.d.ts +9 -0
  163. package/dist/reporter/html.d.ts.map +1 -1
  164. package/dist/reporter/html.js +49 -11
  165. package/dist/reporter/html.js.map +1 -1
  166. package/dist/reporter/markdown.d.ts.map +1 -1
  167. package/dist/reporter/markdown.js +36 -6
  168. package/dist/reporter/markdown.js.map +1 -1
  169. package/dist/reporter/terminal.d.ts.map +1 -1
  170. package/dist/reporter/terminal.js +36 -1
  171. package/dist/reporter/terminal.js.map +1 -1
  172. package/dist/scorer.d.ts +24 -1
  173. package/dist/scorer.d.ts.map +1 -1
  174. package/dist/scorer.js +51 -6
  175. package/dist/scorer.js.map +1 -1
  176. package/dist/types.d.ts +90 -3
  177. package/dist/types.d.ts.map +1 -1
  178. package/docs/api.md +58 -2
  179. package/docs/architecture.md +27 -11
  180. package/docs/checks.md +285 -52
  181. package/docs/cli.md +36 -0
  182. package/docs/concepts.md +27 -13
  183. package/docs/faq.md +18 -6
  184. package/docs/getting-started.md +20 -13
  185. package/docs/roadmap.md +367 -0
  186. package/package.json +13 -5
  187. package/dist/checks/agent-json.d.ts +0 -14
  188. package/dist/checks/agent-json.d.ts.map +0 -1
  189. package/dist/checks/agent-json.js +0 -167
  190. package/dist/checks/agent-json.js.map +0 -1
  191. package/dist/checks/mcp.d.ts +0 -4
  192. package/dist/checks/mcp.d.ts.map +0 -1
  193. package/dist/checks/mcp.js +0 -162
  194. package/dist/checks/mcp.js.map +0 -1
  195. package/dist/checks/openapi.d.ts +0 -4
  196. package/dist/checks/openapi.d.ts.map +0 -1
  197. package/dist/checks/openapi.js +0 -121
  198. package/dist/checks/openapi.js.map +0 -1
  199. package/dist/checks/well-known-ai.d.ts +0 -17
  200. package/dist/checks/well-known-ai.d.ts.map +0 -1
  201. package/dist/checks/well-known-ai.js +0 -123
  202. package/dist/checks/well-known-ai.js.map +0 -1
@@ -1,8 +1,9 @@
1
+ import { CHECK_CATEGORIES } from '../constants.js';
1
2
  import { guideUrl } from '../guide-urls.js';
2
3
  export function clampScore(score) {
3
4
  return Math.max(0, Math.min(100, score));
4
5
  }
5
- export function buildResult(meta, score, findings, start) {
6
+ export function buildResult(meta, score, findings, start, options = {}) {
6
7
  return {
7
8
  id: meta.id,
8
9
  name: meta.name,
@@ -10,8 +11,39 @@ export function buildResult(meta, score, findings, start) {
10
11
  score: clampScore(score),
11
12
  findings,
12
13
  duration: Math.round(performance.now() - start),
14
+ // Falls back to the central map so every result carries its category,
15
+ // including in JSON output where no reporter is there to resolve it.
16
+ ...((meta.category ?? CHECK_CATEGORIES[meta.id]) ? { category: meta.category ?? CHECK_CATEGORIES[meta.id] } : {}),
17
+ ...(options.applicable === false ? { applicable: false } : {}),
13
18
  };
14
19
  }
20
+ /**
21
+ * Result for a check whose subject the site does not have: no API to describe,
22
+ * no commerce surface, no MCP server. Reported as N/A and excluded from the
23
+ * score rather than counted as a failure.
24
+ */
25
+ export function notApplicable(meta, findings, start) {
26
+ return buildResult(meta, 0, findings, start, { applicable: false });
27
+ }
28
+ /**
29
+ * Whether a response body is a web page rather than the machine-readable
30
+ * document that was asked for.
31
+ *
32
+ * Single-page applications answer every unmatched path with their index shell,
33
+ * status 200. Probing `/.well-known/agent-card.json` or `/mcp/server-card` on
34
+ * such a site returns HTML, and a check that trusts the status code reports a
35
+ * *malformed* document where the honest answer is that the document is absent.
36
+ * That mistake is worse than a miss: it sends an operator hunting for a bug in
37
+ * a file they never wrote.
38
+ *
39
+ * Detection is deliberately narrow — a doctype or an `<html>` tag near the top
40
+ * of the body. Anything else is handed to the real parser, which reports its own
41
+ * problems precisely.
42
+ */
43
+ export function isHtmlDocument(body) {
44
+ const head = body.slice(0, 1024).trimStart();
45
+ return /^<!doctype\s+html/i.test(head) || /^<html[\s>]/i.test(head) || /<html[\s>]/i.test(head.slice(0, 512));
46
+ }
15
47
  /**
16
48
  * Validate the `Content-Type` of a fetched resource against a list of acceptable MIME types.
17
49
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/checks/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAe,EAAE,KAAa,EAAE,QAAmB,EAAE,KAAa;IAC5F,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC;QACxB,QAAQ;QACR,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;KAChD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAkB,EAClB,QAAkB,EAClB,OAAmE;IAEnE,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7D,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,OAAO;YACL,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG,OAAO,CAAC,aAAa,6BAA6B;YAC9D,IAAI,EAAE,SAAS,OAAO,CAAC,aAAa,iBAAiB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC3E,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;SACxD,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO;QACL,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,GAAG,OAAO,CAAC,aAAa,qBAAqB,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG;QACzE,MAAM,EAAE,oBAAoB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACjD,IAAI,EAAE,kCAAkC,OAAO,CAAC,aAAa,OAAO,QAAQ,CAAC,CAAC,CAAC,mCAAmC;QAClH,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;KACxD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/checks/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Classify what actually happened to a request, beyond `res.ok`.
3
+ *
4
+ * An audit that probes a site with a crawler user agent gets back a status code
5
+ * and has to answer one question honestly: *does this site block AI crawlers?*
6
+ * A bare 403 does not answer it. The same 403 can mean:
7
+ *
8
+ * - a deliberate AI-crawler block in the WAF (a real finding);
9
+ * - a JavaScript challenge that any fetch-only client fails, real crawler
10
+ * included (a different, usually accidental, finding);
11
+ * - anti-spoofing: the edge verifies crawlers by IP range or by Web Bot Auth
12
+ * signature, so it rejects *this* probe precisely because the probe is
13
+ * unsigned and comes from the wrong network, while admitting the genuine
14
+ * crawler (not a finding at all — the site is behaving correctly).
15
+ *
16
+ * Reporting all three as "blocks AI crawlers" would be wrong often enough to
17
+ * make the check untrustworthy. So every classification carries the evidence
18
+ * that produced it and an `inconclusive` flag for the cases an unauthenticated
19
+ * probe genuinely cannot settle.
20
+ *
21
+ * Header signatures verified 2026-09-04:
22
+ * - Cloudflare challenge: `cf-mitigated: challenge`, always `text/html`.
23
+ * https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/detect-response/
24
+ * - AWS WAF challenge: HTTP **202** with `x-amzn-waf-action: challenge`.
25
+ * https://docs.aws.amazon.com/waf/latest/developerguide/waf-captcha-and-challenge-actions.html
26
+ * - Vercel challenge: `x-vercel-mitigated: challenge` plus `x-vercel-challenge-token`.
27
+ * - Cloudflare pay-per-crawl: 402 with `crawler-price`.
28
+ * https://developers.cloudflare.com/ai-crawl-control/features/pay-per-crawl/
29
+ * - AWS WAF / x402 monetization: 402 with `payment-signature` or an x402 body.
30
+ * - Web Bot Auth required: 403 with `Accept-Signature`.
31
+ * https://datatracker.ietf.org/doc/html/draft-ietf-webbotauth-httpsig-protocol
32
+ * - RSL Open Licensing Protocol: 401/402 with `WWW-Authenticate: License`.
33
+ */
34
+ import type { FetchResponse } from '../types.js';
35
+ export type ResponseKind = 'ok'
36
+ /** An interstitial that requires running JavaScript. Fetch-only clients never get past it. */
37
+ | 'challenge'
38
+ /** 402 with a crawler price attached: content is monetised, not blocked. */
39
+ | 'paywall'
40
+ /** 403 asking the client to re-request with an HTTP Message Signature. */
41
+ | 'needs-signature'
42
+ /** 401/402 pointing at a machine-readable licence (RSL Open Licensing Protocol). */
43
+ | 'license-required' | 'rate-limited'
44
+ /** A plain refusal with no negotiation offered. */
45
+ | 'blocked' | 'not-found' | 'server-error' | 'network-error';
46
+ export interface ResponseClass {
47
+ kind: ResponseKind;
48
+ /** Edge or WAF vendor implicated, when the response identifies one. */
49
+ vendor?: string;
50
+ /** One sentence describing the outcome, written for a report. */
51
+ label: string;
52
+ /** Header names, values or body markers that produced this classification. */
53
+ evidence: string[];
54
+ /**
55
+ * True when this response cannot settle whether the site blocks AI crawlers.
56
+ * A challenge and a signature demand both reject an unsigned probe from a
57
+ * non-vendor IP while potentially admitting the genuine crawler.
58
+ */
59
+ inconclusive: boolean;
60
+ /** Price string from a pay-per-crawl response, e.g. `USD 0.01`. */
61
+ price?: string;
62
+ }
63
+ /** Identify the CDN or edge from response headers, for context in findings. */
64
+ export declare function detectEdgeVendor(headers: Record<string, string>): string | undefined;
65
+ /**
66
+ * Classify a fetch result. Pure and total: any response produces a
67
+ * classification, and unknown shapes fall back to status-based buckets.
68
+ */
69
+ export declare function classifyResponse(res: FetchResponse): ResponseClass;
70
+ /**
71
+ * Standard caveat for classifications an unauthenticated probe cannot settle.
72
+ * Appended to hints so a report never overstates what it observed.
73
+ */
74
+ export declare const INCONCLUSIVE_CAVEAT: string;
75
+ //# sourceMappingURL=waf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waf.d.ts","sourceRoot":"","sources":["../../src/checks/waf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,MAAM,YAAY,GACpB,IAAI;AACN,8FAA8F;GAC5F,WAAW;AACb,4EAA4E;GAC1E,SAAS;AACX,0EAA0E;GACxE,iBAAiB;AACnB,oFAAoF;GAClF,kBAAkB,GAClB,cAAc;AAChB,mDAAmD;GACjD,SAAS,GACT,WAAW,GACX,cAAc,GACd,eAAe,CAAC;AAEpB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmBD,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CAQpF;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,aAAa,CA6KlE;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,QAGM,CAAC"}
@@ -0,0 +1,203 @@
1
+ /** Body markers for challenge interstitials that do not set a header. */
2
+ const CHALLENGE_BODY_MARKERS = [
3
+ { marker: /\/cdn-cgi\/challenge-platform\//i, vendor: 'Cloudflare' },
4
+ { marker: /<title>\s*Just a moment/i, vendor: 'Cloudflare' },
5
+ { marker: /Vercel Security Checkpoint/i, vendor: 'Vercel' },
6
+ { marker: /challenges\.cloudflare\.com\/turnstile/i, vendor: 'Cloudflare Turnstile' },
7
+ { marker: /www\.google\.com\/recaptcha\//i, vendor: 'reCAPTCHA' },
8
+ { marker: /\bhcaptcha\.com\/(1\/api\.js|captcha)/i, vendor: 'hCaptcha' },
9
+ { marker: /_Incapsula_Resource|Incapsula incident ID/i, vendor: 'Imperva' },
10
+ { marker: /Checking your browser before accessing/i, vendor: 'Cloudflare' },
11
+ { marker: /Please enable (JS|JavaScript) and disable any ad ?blocker/i, vendor: 'Cloudflare' },
12
+ { marker: /Request unsuccessful\. Incapsula/i, vendor: 'Imperva' },
13
+ { marker: /Pardon Our Interruption/i, vendor: 'Imperva' },
14
+ { marker: /PerimeterX|px-captcha/i, vendor: 'HUMAN (PerimeterX)' },
15
+ { marker: /Access Denied.*Reference #[0-9a-f.]+/is, vendor: 'Akamai' },
16
+ ];
17
+ /** Identify the CDN or edge from response headers, for context in findings. */
18
+ export function detectEdgeVendor(headers) {
19
+ if (headers['cf-ray'] !== undefined || /cloudflare/i.test(headers['server'] ?? ''))
20
+ return 'Cloudflare';
21
+ if (headers['x-vercel-id'] !== undefined || /vercel/i.test(headers['server'] ?? ''))
22
+ return 'Vercel';
23
+ if (headers['x-amzn-waf-action'] !== undefined || headers['x-amz-cf-id'] !== undefined)
24
+ return 'AWS';
25
+ if (headers['x-served-by']?.includes('cache-') || /varnish/i.test(headers['via'] ?? ''))
26
+ return 'Fastly';
27
+ if (headers['x-akamai-transformed'] !== undefined || /akamai/i.test(headers['server'] ?? ''))
28
+ return 'Akamai';
29
+ if (/netlify/i.test(headers['server'] ?? ''))
30
+ return 'Netlify';
31
+ return undefined;
32
+ }
33
+ function withVendor(base, vendor) {
34
+ return vendor === undefined ? base : `${base} (${vendor})`;
35
+ }
36
+ /**
37
+ * Classify a fetch result. Pure and total: any response produces a
38
+ * classification, and unknown shapes fall back to status-based buckets.
39
+ */
40
+ export function classifyResponse(res) {
41
+ const h = res.headers ?? {};
42
+ const edge = detectEdgeVendor(h);
43
+ const body = res.body ?? '';
44
+ if (res.status === 0) {
45
+ return {
46
+ kind: 'network-error',
47
+ label: res.error ? `Network error: ${res.error}` : 'Network error',
48
+ evidence: res.error ? [res.error] : [],
49
+ inconclusive: true,
50
+ };
51
+ }
52
+ /* ── Challenges: an interstitial, not a policy decision ───────────────── */
53
+ if ((h['cf-mitigated'] ?? '').toLowerCase() === 'challenge') {
54
+ return {
55
+ kind: 'challenge',
56
+ vendor: 'Cloudflare',
57
+ label: 'Cloudflare served a challenge page',
58
+ evidence: ['cf-mitigated: challenge'],
59
+ inconclusive: true,
60
+ };
61
+ }
62
+ if ((h['x-vercel-mitigated'] ?? '').toLowerCase() === 'challenge' || h['x-vercel-challenge-token'] !== undefined) {
63
+ return {
64
+ kind: 'challenge',
65
+ vendor: 'Vercel',
66
+ label: 'Vercel served a security checkpoint',
67
+ evidence: [
68
+ h['x-vercel-mitigated'] !== undefined ? `x-vercel-mitigated: ${h['x-vercel-mitigated']}` : '',
69
+ h['x-vercel-challenge-token'] !== undefined ? 'x-vercel-challenge-token present' : '',
70
+ ].filter(Boolean),
71
+ inconclusive: true,
72
+ };
73
+ }
74
+ if ((h['x-amzn-waf-action'] ?? '').toLowerCase() === 'challenge') {
75
+ return {
76
+ kind: 'challenge',
77
+ vendor: 'AWS WAF',
78
+ label: `AWS WAF served a challenge (HTTP ${res.status})`,
79
+ evidence: [`x-amzn-waf-action: challenge`, `status ${res.status}`],
80
+ inconclusive: true,
81
+ };
82
+ }
83
+ /* ── Payment and licensing: an offer, not a refusal ───────────────────── */
84
+ if (res.status === 402) {
85
+ const price = h['crawler-price'];
86
+ if (price !== undefined) {
87
+ return {
88
+ kind: 'paywall',
89
+ vendor: 'Cloudflare pay-per-crawl',
90
+ label: `Content is priced for crawlers at ${price}`,
91
+ evidence: [`crawler-price: ${price}`],
92
+ inconclusive: false,
93
+ price,
94
+ };
95
+ }
96
+ if (h['payment-signature'] !== undefined || /"(payTo|maxTimeoutSeconds|x402Version)"/.test(body)) {
97
+ return {
98
+ kind: 'paywall',
99
+ vendor: withVendor('x402', edge),
100
+ label: 'Content is monetised through an x402 payment challenge',
101
+ evidence: [h['payment-signature'] !== undefined ? 'payment-signature header' : 'x402 fields in body'],
102
+ inconclusive: false,
103
+ };
104
+ }
105
+ if (/^License\b/i.test(h['www-authenticate'] ?? '')) {
106
+ return {
107
+ kind: 'license-required',
108
+ vendor: 'RSL Open Licensing Protocol',
109
+ label: 'A licence is required before this content may be used',
110
+ evidence: [`www-authenticate: ${h['www-authenticate']}`],
111
+ inconclusive: false,
112
+ };
113
+ }
114
+ return {
115
+ kind: 'blocked',
116
+ vendor: edge,
117
+ label: withVendor('Refused with 402 but no payment mechanism advertised', edge),
118
+ evidence: ['status 402 with no crawler-price, x402 fields, or licence challenge'],
119
+ inconclusive: false,
120
+ };
121
+ }
122
+ if (res.status === 401 && /^License\b/i.test(h['www-authenticate'] ?? '')) {
123
+ return {
124
+ kind: 'license-required',
125
+ vendor: 'RSL Open Licensing Protocol',
126
+ label: 'A licence is required before this content may be used',
127
+ evidence: [`www-authenticate: ${h['www-authenticate']}`],
128
+ inconclusive: false,
129
+ };
130
+ }
131
+ /* ── Signature demands: the site wants a verifiable agent ─────────────── */
132
+ if ((res.status === 401 || res.status === 403) && h['accept-signature'] !== undefined) {
133
+ return {
134
+ kind: 'needs-signature',
135
+ vendor: withVendor('Web Bot Auth', edge),
136
+ label: 'The origin requires a signed request (Web Bot Auth)',
137
+ evidence: [`accept-signature: ${h['accept-signature']}`],
138
+ inconclusive: true,
139
+ };
140
+ }
141
+ if (res.status === 429) {
142
+ return {
143
+ kind: 'rate-limited',
144
+ vendor: edge,
145
+ label: h['retry-after'] !== undefined ? `Rate limited, retry after ${h['retry-after']}` : 'Rate limited',
146
+ evidence: h['retry-after'] !== undefined ? [`retry-after: ${h['retry-after']}`] : ['status 429'],
147
+ inconclusive: true,
148
+ };
149
+ }
150
+ /* ── Body-marker challenges, on any status ────────────────────────────── */
151
+ const bodyChallenge = CHALLENGE_BODY_MARKERS.find((m) => m.marker.test(body));
152
+ if (bodyChallenge !== undefined) {
153
+ return {
154
+ kind: 'challenge',
155
+ vendor: bodyChallenge.vendor,
156
+ label: `${bodyChallenge.vendor} interstitial served instead of the page`,
157
+ evidence: [`body matched ${bodyChallenge.vendor} challenge markup`, `status ${res.status}`],
158
+ inconclusive: true,
159
+ };
160
+ }
161
+ if (res.status === 401 || res.status === 403) {
162
+ return {
163
+ kind: 'blocked',
164
+ vendor: edge,
165
+ label: withVendor(`Refused with HTTP ${res.status}`, edge),
166
+ evidence: [`status ${res.status}`],
167
+ // An edge that verifies bots by IP range or signature rejects an unsigned
168
+ // probe on principle, so a bare 403 from a known bot-managing CDN cannot
169
+ // prove a policy block.
170
+ inconclusive: edge !== undefined,
171
+ };
172
+ }
173
+ if (res.status === 404 || res.status === 410) {
174
+ return { kind: 'not-found', vendor: edge, label: `HTTP ${res.status}`, evidence: [], inconclusive: false };
175
+ }
176
+ if (res.status >= 500) {
177
+ return {
178
+ kind: 'server-error',
179
+ vendor: edge,
180
+ label: `Server error (HTTP ${res.status})`,
181
+ evidence: [`status ${res.status}`],
182
+ inconclusive: true,
183
+ };
184
+ }
185
+ if (res.ok) {
186
+ return { kind: 'ok', vendor: edge, label: `HTTP ${res.status}`, evidence: [], inconclusive: false };
187
+ }
188
+ return {
189
+ kind: 'blocked',
190
+ vendor: edge,
191
+ label: `Unexpected HTTP ${res.status}`,
192
+ evidence: [`status ${res.status}`],
193
+ inconclusive: true,
194
+ };
195
+ }
196
+ /**
197
+ * Standard caveat for classifications an unauthenticated probe cannot settle.
198
+ * Appended to hints so a report never overstates what it observed.
199
+ */
200
+ export const INCONCLUSIVE_CAVEAT = 'ax-audit sends this user agent from its own network without a Web Bot Auth signature, so an edge that verifies ' +
201
+ 'crawlers by IP range or signature will reject the probe while admitting the real crawler. Confirm against your ' +
202
+ 'WAF logs before changing any rule.';
203
+ //# sourceMappingURL=waf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waf.js","sourceRoot":"","sources":["../../src/checks/waf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
2
+ /**
3
+ * "webmcp" — can a browser agent invoke this page's forms as tools?
4
+ *
5
+ * Today an agent that wants to use a form has to look at pixels, guess which
6
+ * field is which, type into them, and hope. WebMCP lets a page declare a form as
7
+ * a callable tool with named parameters, so the agent calls it instead of
8
+ * driving it. The declarative form is two attributes:
9
+ *
10
+ * ```html
11
+ * <form toolname="searchDocs" tooldescription="Search the documentation">
12
+ * <input name="q" toolparamdescription="The search query">
13
+ * </form>
14
+ * ```
15
+ *
16
+ * Status, verified 2026-09-04: a W3C Web Machine Learning Community Group draft
17
+ * with a Chrome origin trial running from Chrome 149. Not a standard, not
18
+ * shipped by default, not implemented in Firefox or Safari. Adoption is
19
+ * effectively zero.
20
+ *
21
+ * So this check never asks for WebMCP and never scores a site down for lacking
22
+ * it. It reports N/A on a page with no forms and no WebMCP code at all. What it
23
+ * does do, on pages that have adopted it, is catch the mistakes a static
24
+ * analysis can catch with certainty: a `toolname` with no `tooldescription`,
25
+ * which is the exact condition Lighthouse's `webmcp-schema-validity` audit
26
+ * fails, parameters with no description, and use of the `navigator.modelContext`
27
+ * namespace that Chrome deprecated in favour of `document.modelContext`.
28
+ */
29
+ export declare const meta: CheckMeta;
30
+ export interface FormAnalysis {
31
+ total: number;
32
+ annotated: number;
33
+ /** Forms declaring a name but no description — invalid, and Lighthouse fails them. */
34
+ missingDescription: string[];
35
+ /** Forms declaring a description but no name: nothing for an agent to call. */
36
+ missingName: number;
37
+ /** Tool names that are not usable identifiers. */
38
+ invalidNames: string[];
39
+ /** Annotated forms whose named controls lack `toolparamdescription`. */
40
+ undescribedParams: {
41
+ tool: string;
42
+ missing: number;
43
+ total: number;
44
+ }[];
45
+ autoSubmit: string[];
46
+ }
47
+ /** Analyse declarative WebMCP annotations across every form on the page. */
48
+ export declare function analyseForms(html: string): FormAnalysis;
49
+ /** Detect imperative WebMCP registration in inline scripts. */
50
+ export declare function detectImperative(html: string): {
51
+ registers: boolean;
52
+ deprecatedNamespace: boolean;
53
+ };
54
+ export default function check(ctx: CheckContext): Promise<CheckResult>;
55
+ //# sourceMappingURL=webmcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.d.ts","sourceRoot":"","sources":["../../src/checks/webmcp.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAIjF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAWF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,wEAAwE;IACxE,iBAAiB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAWD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAiDvD;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAA;CAAE,CAMnG;AAED,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAmH3E"}
@@ -0,0 +1,209 @@
1
+ import { guideUrl } from '../guide-urls.js';
2
+ import { buildResult, notApplicable } from './utils.js';
3
+ import { getAttribute } from './html-utils.js';
4
+ /**
5
+ * "webmcp" — can a browser agent invoke this page's forms as tools?
6
+ *
7
+ * Today an agent that wants to use a form has to look at pixels, guess which
8
+ * field is which, type into them, and hope. WebMCP lets a page declare a form as
9
+ * a callable tool with named parameters, so the agent calls it instead of
10
+ * driving it. The declarative form is two attributes:
11
+ *
12
+ * ```html
13
+ * <form toolname="searchDocs" tooldescription="Search the documentation">
14
+ * <input name="q" toolparamdescription="The search query">
15
+ * </form>
16
+ * ```
17
+ *
18
+ * Status, verified 2026-09-04: a W3C Web Machine Learning Community Group draft
19
+ * with a Chrome origin trial running from Chrome 149. Not a standard, not
20
+ * shipped by default, not implemented in Firefox or Safari. Adoption is
21
+ * effectively zero.
22
+ *
23
+ * So this check never asks for WebMCP and never scores a site down for lacking
24
+ * it. It reports N/A on a page with no forms and no WebMCP code at all. What it
25
+ * does do, on pages that have adopted it, is catch the mistakes a static
26
+ * analysis can catch with certainty: a `toolname` with no `tooldescription`,
27
+ * which is the exact condition Lighthouse's `webmcp-schema-validity` audit
28
+ * fails, parameters with no description, and use of the `navigator.modelContext`
29
+ * namespace that Chrome deprecated in favour of `document.modelContext`.
30
+ */
31
+ export const meta = {
32
+ id: 'webmcp',
33
+ name: 'WebMCP',
34
+ description: 'Checks declarative WebMCP tool annotations on forms',
35
+ category: 'protocols',
36
+ };
37
+ /** Tool names are identifiers an agent calls, so they follow identifier rules. */
38
+ const TOOL_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
39
+ /** Form controls that carry a value an agent would need described. */
40
+ const CONTROL_RE = /<(input|select|textarea)\b([^>]*)>/gi;
41
+ /** Input types that carry no user-supplied value worth describing. */
42
+ const VALUELESS_TYPES = new Set(['hidden', 'submit', 'button', 'reset', 'image']);
43
+ /** Split the document into `<form>` blocks, keeping each element's attributes and inner HTML. */
44
+ function forms(html) {
45
+ const out = [];
46
+ for (const m of html.matchAll(/<form\b([^>]*)>([\s\S]*?)<\/form>/gi)) {
47
+ out.push({ attrs: m[1], inner: m[2] });
48
+ }
49
+ return out;
50
+ }
51
+ /** Analyse declarative WebMCP annotations across every form on the page. */
52
+ export function analyseForms(html) {
53
+ const analysis = {
54
+ total: 0,
55
+ annotated: 0,
56
+ missingDescription: [],
57
+ missingName: 0,
58
+ invalidNames: [],
59
+ undescribedParams: [],
60
+ autoSubmit: [],
61
+ };
62
+ for (const form of forms(html)) {
63
+ analysis.total++;
64
+ const toolName = getAttribute(form.attrs, 'toolname');
65
+ const toolDescription = getAttribute(form.attrs, 'tooldescription');
66
+ if (toolName === null && toolDescription === null)
67
+ continue;
68
+ const label = toolName ?? '(unnamed)';
69
+ analysis.annotated++;
70
+ if (toolName === null || toolName.trim() === '') {
71
+ analysis.missingName++;
72
+ }
73
+ if (toolDescription === null || toolDescription.trim() === '') {
74
+ analysis.missingDescription.push(label);
75
+ }
76
+ if (toolName !== null && !TOOL_NAME_RE.test(toolName)) {
77
+ analysis.invalidNames.push(toolName);
78
+ }
79
+ if (/\btoolautosubmit\b/i.test(form.attrs)) {
80
+ analysis.autoSubmit.push(label);
81
+ }
82
+ let total = 0;
83
+ let missing = 0;
84
+ for (const control of form.inner.matchAll(CONTROL_RE)) {
85
+ const attrs = control[2];
86
+ const type = (getAttribute(attrs, 'type') ?? '').toLowerCase();
87
+ if (VALUELESS_TYPES.has(type))
88
+ continue;
89
+ if (getAttribute(attrs, 'name') === null)
90
+ continue;
91
+ total++;
92
+ if (getAttribute(attrs, 'toolparamdescription') === null)
93
+ missing++;
94
+ }
95
+ if (total > 0 && missing > 0) {
96
+ analysis.undescribedParams.push({ tool: label, missing, total });
97
+ }
98
+ }
99
+ return analysis;
100
+ }
101
+ /** Detect imperative WebMCP registration in inline scripts. */
102
+ export function detectImperative(html) {
103
+ const scripts = [...html.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)].map((m) => m[1]).join('\n');
104
+ return {
105
+ registers: /\bmodelContext\s*\.\s*(registerTool|provideContext)\s*\(/.test(scripts),
106
+ deprecatedNamespace: /\bnavigator\s*\.\s*modelContext\b/.test(scripts),
107
+ };
108
+ }
109
+ export default async function check(ctx) {
110
+ const start = performance.now();
111
+ const findings = [];
112
+ let score = 100;
113
+ const html = ctx.html ?? '';
114
+ const analysis = analyseForms(html);
115
+ const imperative = detectImperative(html);
116
+ if (analysis.total === 0 && !imperative.registers && !imperative.deprecatedNamespace) {
117
+ findings.push({
118
+ status: 'pass',
119
+ message: 'No forms and no WebMCP code — nothing here for an agent to invoke as a tool',
120
+ });
121
+ return notApplicable(meta, findings, start);
122
+ }
123
+ if (imperative.registers) {
124
+ findings.push({
125
+ status: 'pass',
126
+ message: 'Imperative WebMCP registration detected in page scripts',
127
+ detail: 'A static audit cannot enumerate the registered tools or validate their schemas; that needs a browser.',
128
+ });
129
+ }
130
+ if (imperative.deprecatedNamespace) {
131
+ findings.push({
132
+ status: 'warn',
133
+ message: 'Page uses the deprecated navigator.modelContext namespace',
134
+ hint: 'Chrome moved the API to document.modelContext. Feature-detect both while the origin trial runs: const ctx = document.modelContext ?? navigator.modelContext.',
135
+ learnMoreUrl: guideUrl(meta.id, 'deprecated-namespace'),
136
+ });
137
+ score -= 10;
138
+ }
139
+ if (analysis.annotated === 0) {
140
+ findings.push({
141
+ status: 'warn',
142
+ message: `${analysis.total} form(s) on the page, none declared as agent tools`,
143
+ detail: 'WebMCP is a W3C Community Group draft in a Chrome origin trial. It is not a standard and adoption is minimal, ' +
144
+ 'so this is a forward-looking note, not a defect.',
145
+ hint: 'A declared form is called by an agent rather than driven pixel by pixel. Add toolname and tooldescription to ' +
146
+ 'the forms worth automating — search, filter, subscribe — and toolparamdescription to each field.',
147
+ learnMoreUrl: guideUrl(meta.id, 'no-annotations'),
148
+ });
149
+ return buildResult(meta, score, findings, start);
150
+ }
151
+ findings.push({
152
+ status: 'pass',
153
+ message: `${analysis.annotated}/${analysis.total} form(s) declared as agent tools`,
154
+ });
155
+ if (analysis.missingDescription.length > 0) {
156
+ findings.push({
157
+ status: 'fail',
158
+ message: `${analysis.missingDescription.length} tool form(s) declare a name but no description`,
159
+ detail: analysis.missingDescription.join(', '),
160
+ hint: 'The description is what an agent reads to decide whether to call the tool; a named tool without one is ' +
161
+ 'unusable. Both attributes are required together.',
162
+ learnMoreUrl: guideUrl(meta.id, 'missing-description'),
163
+ });
164
+ score -= 30;
165
+ }
166
+ if (analysis.missingName > 0) {
167
+ findings.push({
168
+ status: 'fail',
169
+ message: `${analysis.missingName} form(s) declare a tool description but no tool name`,
170
+ hint: 'Without a toolname there is nothing for an agent to call. Both attributes are required together.',
171
+ learnMoreUrl: guideUrl(meta.id, 'missing-name'),
172
+ });
173
+ score -= 30;
174
+ }
175
+ if (analysis.invalidNames.length > 0) {
176
+ findings.push({
177
+ status: 'warn',
178
+ message: 'Tool name is not a usable identifier',
179
+ detail: analysis.invalidNames.join(', '),
180
+ hint: 'A tool name is called like a function. Use letters, digits and underscores, starting with a letter: searchDocs, subscribe_newsletter.',
181
+ learnMoreUrl: guideUrl(meta.id, 'invalid-name'),
182
+ });
183
+ score -= 10;
184
+ }
185
+ if (analysis.undescribedParams.length > 0) {
186
+ const totalMissing = analysis.undescribedParams.reduce((acc, p) => acc + p.missing, 0);
187
+ findings.push({
188
+ status: 'warn',
189
+ message: `${totalMissing} tool parameter(s) have no description`,
190
+ detail: analysis.undescribedParams.map((p) => `${p.tool}: ${p.missing}/${p.total} fields undescribed`).join('\n'),
191
+ hint: 'Add toolparamdescription to each named field. Without it an agent has to infer a field’s meaning from its ' +
192
+ 'name, which is how a search box gets an email address in it.',
193
+ learnMoreUrl: guideUrl(meta.id, 'undescribed-params'),
194
+ });
195
+ score -= 15;
196
+ }
197
+ else {
198
+ findings.push({ status: 'pass', message: 'Every tool parameter carries a description' });
199
+ }
200
+ if (analysis.autoSubmit.length > 0) {
201
+ findings.push({
202
+ status: 'pass',
203
+ message: `${analysis.autoSubmit.length} tool form(s) declare toolautosubmit`,
204
+ detail: 'The agent submits without a further confirmation step. Appropriate for read-only actions such as search, not for anything that charges, sends or deletes.',
205
+ });
206
+ }
207
+ return buildResult(meta, score, findings, start);
208
+ }
209
+ //# sourceMappingURL=webmcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.js","sourceRoot":"","sources":["../../src/checks/webmcp.ts"],"names":[],"mappings":"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"}