ax-audit 4.2.1 → 6.0.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 (188) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +39 -4
  3. package/dist/baseline.js +1 -1
  4. package/dist/baseline.js.map +1 -1
  5. package/dist/check-ids.js +1 -1
  6. package/dist/check-ids.js.map +1 -1
  7. package/dist/cli.js +1 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/constants.d.ts +1 -85
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/constants.js +0 -135
  12. package/dist/constants.js.map +1 -1
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/license.d.ts +8 -0
  18. package/dist/license.d.ts.map +1 -0
  19. package/dist/license.js +18 -0
  20. package/dist/license.js.map +1 -0
  21. package/dist/metadata.d.ts +6 -0
  22. package/dist/metadata.d.ts.map +1 -0
  23. package/dist/metadata.js +205 -0
  24. package/dist/metadata.js.map +1 -0
  25. package/dist/orchestrator.d.ts +1 -0
  26. package/dist/orchestrator.d.ts.map +1 -1
  27. package/dist/orchestrator.js +134 -56
  28. package/dist/orchestrator.js.map +1 -1
  29. package/dist/scorer.js +2 -2
  30. package/dist/scorer.js.map +1 -1
  31. package/dist/types.d.ts +4 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/docs/api.md +14 -3
  34. package/docs/architecture.md +9 -98
  35. package/docs/ci.md +19 -0
  36. package/docs/cli.md +11 -0
  37. package/docs/faq.md +18 -0
  38. package/docs/getting-started.md +33 -1
  39. package/package.json +5 -5
  40. package/dist/checks/agent-access.d.ts +0 -33
  41. package/dist/checks/agent-access.d.ts.map +0 -1
  42. package/dist/checks/agent-access.js +0 -256
  43. package/dist/checks/agent-access.js.map +0 -1
  44. package/dist/checks/agent-card.d.ts +0 -37
  45. package/dist/checks/agent-card.d.ts.map +0 -1
  46. package/dist/checks/agent-card.js +0 -352
  47. package/dist/checks/agent-card.js.map +0 -1
  48. package/dist/checks/agent-operability.d.ts +0 -66
  49. package/dist/checks/agent-operability.d.ts.map +0 -1
  50. package/dist/checks/agent-operability.js +0 -383
  51. package/dist/checks/agent-operability.js.map +0 -1
  52. package/dist/checks/agent-skills.d.ts +0 -24
  53. package/dist/checks/agent-skills.d.ts.map +0 -1
  54. package/dist/checks/agent-skills.js +0 -316
  55. package/dist/checks/agent-skills.js.map +0 -1
  56. package/dist/checks/ai-catalog.d.ts +0 -28
  57. package/dist/checks/ai-catalog.d.ts.map +0 -1
  58. package/dist/checks/ai-catalog.js +0 -254
  59. package/dist/checks/ai-catalog.js.map +0 -1
  60. package/dist/checks/ai-directives.d.ts +0 -57
  61. package/dist/checks/ai-directives.d.ts.map +0 -1
  62. package/dist/checks/ai-directives.js +0 -263
  63. package/dist/checks/ai-directives.js.map +0 -1
  64. package/dist/checks/api-discovery.d.ts +0 -26
  65. package/dist/checks/api-discovery.d.ts.map +0 -1
  66. package/dist/checks/api-discovery.js +0 -432
  67. package/dist/checks/api-discovery.js.map +0 -1
  68. package/dist/checks/auth-discovery.d.ts +0 -28
  69. package/dist/checks/auth-discovery.d.ts.map +0 -1
  70. package/dist/checks/auth-discovery.js +0 -302
  71. package/dist/checks/auth-discovery.js.map +0 -1
  72. package/dist/checks/commerce-discovery.d.ts +0 -40
  73. package/dist/checks/commerce-discovery.d.ts.map +0 -1
  74. package/dist/checks/commerce-discovery.js +0 -295
  75. package/dist/checks/commerce-discovery.js.map +0 -1
  76. package/dist/checks/content-negotiation.d.ts +0 -4
  77. package/dist/checks/content-negotiation.d.ts.map +0 -1
  78. package/dist/checks/content-negotiation.js +0 -253
  79. package/dist/checks/content-negotiation.js.map +0 -1
  80. package/dist/checks/crawl-efficiency.d.ts +0 -16
  81. package/dist/checks/crawl-efficiency.d.ts.map +0 -1
  82. package/dist/checks/crawl-efficiency.js +0 -186
  83. package/dist/checks/crawl-efficiency.js.map +0 -1
  84. package/dist/checks/frontmatter.d.ts +0 -34
  85. package/dist/checks/frontmatter.d.ts.map +0 -1
  86. package/dist/checks/frontmatter.js +0 -100
  87. package/dist/checks/frontmatter.js.map +0 -1
  88. package/dist/checks/html-rendering.d.ts +0 -21
  89. package/dist/checks/html-rendering.d.ts.map +0 -1
  90. package/dist/checks/html-rendering.js +0 -221
  91. package/dist/checks/html-rendering.js.map +0 -1
  92. package/dist/checks/html-utils.d.ts +0 -66
  93. package/dist/checks/html-utils.d.ts.map +0 -1
  94. package/dist/checks/html-utils.js +0 -139
  95. package/dist/checks/html-utils.js.map +0 -1
  96. package/dist/checks/http-headers.d.ts +0 -11
  97. package/dist/checks/http-headers.d.ts.map +0 -1
  98. package/dist/checks/http-headers.js +0 -249
  99. package/dist/checks/http-headers.js.map +0 -1
  100. package/dist/checks/http-hygiene.d.ts +0 -26
  101. package/dist/checks/http-hygiene.d.ts.map +0 -1
  102. package/dist/checks/http-hygiene.js +0 -257
  103. package/dist/checks/http-hygiene.js.map +0 -1
  104. package/dist/checks/index.d.ts +0 -3
  105. package/dist/checks/index.d.ts.map +0 -1
  106. package/dist/checks/index.js +0 -55
  107. package/dist/checks/index.js.map +0 -1
  108. package/dist/checks/llms-txt.d.ts +0 -19
  109. package/dist/checks/llms-txt.d.ts.map +0 -1
  110. package/dist/checks/llms-txt.js +0 -281
  111. package/dist/checks/llms-txt.js.map +0 -1
  112. package/dist/checks/mcp-discovery.d.ts +0 -30
  113. package/dist/checks/mcp-discovery.d.ts.map +0 -1
  114. package/dist/checks/mcp-discovery.js +0 -523
  115. package/dist/checks/mcp-discovery.js.map +0 -1
  116. package/dist/checks/meta-tags.d.ts +0 -17
  117. package/dist/checks/meta-tags.d.ts.map +0 -1
  118. package/dist/checks/meta-tags.js +0 -188
  119. package/dist/checks/meta-tags.js.map +0 -1
  120. package/dist/checks/robots-parser.d.ts +0 -110
  121. package/dist/checks/robots-parser.d.ts.map +0 -1
  122. package/dist/checks/robots-parser.js +0 -277
  123. package/dist/checks/robots-parser.js.map +0 -1
  124. package/dist/checks/robots-txt.d.ts +0 -6
  125. package/dist/checks/robots-txt.d.ts.map +0 -1
  126. package/dist/checks/robots-txt.js +0 -367
  127. package/dist/checks/robots-txt.js.map +0 -1
  128. package/dist/checks/rsl.d.ts +0 -4
  129. package/dist/checks/rsl.d.ts.map +0 -1
  130. package/dist/checks/rsl.js +0 -242
  131. package/dist/checks/rsl.js.map +0 -1
  132. package/dist/checks/security-txt.d.ts +0 -4
  133. package/dist/checks/security-txt.d.ts.map +0 -1
  134. package/dist/checks/security-txt.js +0 -81
  135. package/dist/checks/security-txt.js.map +0 -1
  136. package/dist/checks/seo-basics.d.ts +0 -13
  137. package/dist/checks/seo-basics.d.ts.map +0 -1
  138. package/dist/checks/seo-basics.js +0 -221
  139. package/dist/checks/seo-basics.js.map +0 -1
  140. package/dist/checks/sitemap.d.ts +0 -12
  141. package/dist/checks/sitemap.d.ts.map +0 -1
  142. package/dist/checks/sitemap.js +0 -240
  143. package/dist/checks/sitemap.js.map +0 -1
  144. package/dist/checks/structured-data.d.ts +0 -4
  145. package/dist/checks/structured-data.d.ts.map +0 -1
  146. package/dist/checks/structured-data.js +0 -408
  147. package/dist/checks/structured-data.js.map +0 -1
  148. package/dist/checks/structured-fields.d.ts +0 -46
  149. package/dist/checks/structured-fields.d.ts.map +0 -1
  150. package/dist/checks/structured-fields.js +0 -112
  151. package/dist/checks/structured-fields.js.map +0 -1
  152. package/dist/checks/surface.d.ts +0 -59
  153. package/dist/checks/surface.d.ts.map +0 -1
  154. package/dist/checks/surface.js +0 -106
  155. package/dist/checks/surface.js.map +0 -1
  156. package/dist/checks/tls-https.d.ts +0 -13
  157. package/dist/checks/tls-https.d.ts.map +0 -1
  158. package/dist/checks/tls-https.js +0 -163
  159. package/dist/checks/tls-https.js.map +0 -1
  160. package/dist/checks/usage-policy.d.ts +0 -53
  161. package/dist/checks/usage-policy.d.ts.map +0 -1
  162. package/dist/checks/usage-policy.js +0 -339
  163. package/dist/checks/usage-policy.js.map +0 -1
  164. package/dist/checks/utils.d.ts +0 -40
  165. package/dist/checks/utils.d.ts.map +0 -1
  166. package/dist/checks/utils.js +0 -74
  167. package/dist/checks/utils.js.map +0 -1
  168. package/dist/checks/waf.d.ts +0 -75
  169. package/dist/checks/waf.d.ts.map +0 -1
  170. package/dist/checks/waf.js +0 -203
  171. package/dist/checks/waf.js.map +0 -1
  172. package/dist/checks/webmcp.d.ts +0 -55
  173. package/dist/checks/webmcp.d.ts.map +0 -1
  174. package/dist/checks/webmcp.js +0 -209
  175. package/dist/checks/webmcp.js.map +0 -1
  176. package/dist/checks/well-known.d.ts +0 -38
  177. package/dist/checks/well-known.d.ts.map +0 -1
  178. package/dist/checks/well-known.js +0 -202
  179. package/dist/checks/well-known.js.map +0 -1
  180. package/dist/fetcher.d.ts +0 -15
  181. package/dist/fetcher.d.ts.map +0 -1
  182. package/dist/fetcher.js +0 -127
  183. package/dist/fetcher.js.map +0 -1
  184. package/dist/guide-urls.d.ts +0 -2
  185. package/dist/guide-urls.d.ts.map +0 -1
  186. package/dist/guide-urls.js +0 -5
  187. package/dist/guide-urls.js.map +0 -1
  188. package/docs/roadmap.md +0 -367
@@ -1,367 +0,0 @@
1
- import { ALL_AI_CRAWLERS, CONTENT_SIGNALS, CONTENT_SIGNAL_USE_VALUES, CORE_AI_CRAWLERS, crawlerInfo, crawlerPurpose, legacyCrawlerNote, } from '../constants.js';
2
- import { guideUrl } from '../guide-urls.js';
3
- import { buildResult } from './utils.js';
4
- import { parseRobotsTxt, parseContentSignalValue, parseContentUsageValue, toBotEntries, } from './robots-parser.js';
5
- export const meta = {
6
- id: 'robots-txt',
7
- name: 'Robots.txt',
8
- description: 'Checks AI crawler configuration in robots.txt',
9
- };
10
- // Re-exported for checks and consumers that were written against the pre-3.7
11
- // location of the parser. The implementation now lives in `robots-parser.ts`.
12
- export { parseUserAgents, intentBlocked } from './robots-parser.js';
13
- export default async function check(ctx) {
14
- const start = performance.now();
15
- const findings = [];
16
- let score = 100;
17
- const res = await ctx.fetch(`${ctx.url}/robots.txt`);
18
- if (!res.ok) {
19
- findings.push({
20
- status: 'fail',
21
- message: '/robots.txt not found',
22
- hint: 'Create a /robots.txt file at your site root. Add User-agent entries for AI crawlers (GPTBot, ClaudeBot, etc.) with Allow: / to grant access.',
23
- learnMoreUrl: guideUrl(meta.id, 'not-found'),
24
- });
25
- return buildResult(meta, 0, findings, start);
26
- }
27
- findings.push({ status: 'pass', message: '/robots.txt exists' });
28
- const text = res.body;
29
- const robots = parseRobotsTxt(text);
30
- const configuredBots = toBotEntries(robots);
31
- const wildcardEntry = configuredBots.find((b) => b.name === '*');
32
- const isConfigured = (bot) => configuredBots.some((b) => b.name.toLowerCase() === bot.toLowerCase());
33
- // 4.0 scores against the current core set. The 3.x freeze that held it at
34
- // eight tokens is gone, so the crawlers that gained traffic share in 2026
35
- // now count.
36
- const coreConfigured = CORE_AI_CRAWLERS.filter(isConfigured);
37
- const coreMissing = CORE_AI_CRAWLERS.filter((bot) => !isConfigured(bot));
38
- if (coreConfigured.length === CORE_AI_CRAWLERS.length) {
39
- findings.push({
40
- status: 'pass',
41
- message: `All ${CORE_AI_CRAWLERS.length} core AI crawlers explicitly configured`,
42
- });
43
- }
44
- else if (coreConfigured.length > 0) {
45
- findings.push({
46
- status: 'warn',
47
- message: `${coreConfigured.length}/${CORE_AI_CRAWLERS.length} core AI crawlers configured`,
48
- detail: coreMissing.map((bot) => `${bot} — ${crawlerInfo(bot)?.impact ?? 'no explicit rule'}`).join('\n'),
49
- hint: `Add explicit User-agent entries for the missing crawlers with Allow: / for each one.`,
50
- learnMoreUrl: guideUrl(meta.id, 'missing-crawlers'),
51
- });
52
- score -= Math.round((coreMissing.length / CORE_AI_CRAWLERS.length) * 30);
53
- }
54
- else {
55
- findings.push({
56
- status: 'fail',
57
- message: 'No core AI crawlers explicitly configured',
58
- detail: `Expected: ${CORE_AI_CRAWLERS.join(', ')}`,
59
- hint: 'Add User-agent entries for core AI crawlers in your robots.txt. For each crawler, add: User-agent: <name> followed by Allow: / on the next line.',
60
- learnMoreUrl: guideUrl(meta.id, 'no-core-crawlers'),
61
- });
62
- score -= 40;
63
- }
64
- // Check wildcard blocking unconfigured AI crawlers
65
- if (wildcardEntry?.disallowed) {
66
- const blockedByWildcard = CORE_AI_CRAWLERS.filter((bot) => !isConfigured(bot));
67
- if (blockedByWildcard.length > 0) {
68
- findings.push({
69
- status: 'warn',
70
- message: `${blockedByWildcard.length} core AI crawler(s) blocked via wildcard User-agent: *`,
71
- detail: blockedByWildcard.join(', '),
72
- hint: 'Your "User-agent: * / Disallow: /" rule blocks these crawlers. Add explicit User-agent entries with Allow: / for each AI crawler you want to permit.',
73
- learnMoreUrl: guideUrl(meta.id, 'blocked-by-wildcard'),
74
- });
75
- score -= blockedByWildcard.length * 5;
76
- }
77
- }
78
- const blockedBots = configuredBots.filter((b) => b.name !== '*' && ALL_AI_CRAWLERS.some((ai) => ai.toLowerCase() === b.name.toLowerCase()) && b.disallowed);
79
- if (blockedBots.length > 0) {
80
- findings.push({
81
- status: 'warn',
82
- message: `${blockedBots.length} AI crawler(s) explicitly blocked`,
83
- detail: blockedBots.map((b) => b.name).join(', '),
84
- hint: 'These crawlers have "Disallow: /" rules. If you want AI agents to access your site, change to "Allow: /" for each blocked crawler.',
85
- learnMoreUrl: guideUrl(meta.id, 'explicitly-blocked'),
86
- });
87
- // 4.0 removed the 3.x freeze: every catalogued crawler counts. Search
88
- // crawlers cost more than training ones, because blocking a search crawler
89
- // removes the site from that assistant's answers while blocking a training
90
- // crawler is a policy choice.
91
- for (const bot of blockedBots) {
92
- score -= crawlerPurpose(bot.name) === 'search' ? 5 : 2;
93
- }
94
- }
95
- addBlockedPurposeFindings(blockedBots.map((b) => b.name), findings);
96
- addLegacyTokenFindings(configuredBots.map((b) => b.name), findings);
97
- // Check partial restrictions (Disallow on specific paths, not full block)
98
- const restrictedBots = configuredBots.filter((b) => b.name !== '*' &&
99
- ALL_AI_CRAWLERS.some((ai) => ai.toLowerCase() === b.name.toLowerCase()) &&
100
- b.hasRestrictions &&
101
- !b.disallowed);
102
- if (restrictedBots.length > 0) {
103
- findings.push({
104
- status: 'warn',
105
- message: `${restrictedBots.length} AI crawler(s) have partial path restrictions`,
106
- detail: restrictedBots.map((b) => b.name).join(', '),
107
- hint: 'These crawlers have Disallow rules on specific paths. For full AI access, use only "Allow: /" and let the wildcard User-agent: * handle path restrictions.',
108
- learnMoreUrl: guideUrl(meta.id, 'partial-restrictions'),
109
- });
110
- }
111
- if (robots.sitemaps.length > 0) {
112
- findings.push({ status: 'pass', message: 'Sitemap directive present' });
113
- }
114
- else {
115
- findings.push({
116
- status: 'warn',
117
- message: 'No Sitemap directive found',
118
- hint: 'Add a Sitemap directive to your robots.txt: Sitemap: https://your-site.com/sitemap.xml',
119
- learnMoreUrl: guideUrl(meta.id, 'missing-sitemap'),
120
- });
121
- score -= 5;
122
- }
123
- // Usage-preference directives — informational in 3.x: findings only, no score impact.
124
- addContentSignalFindings(robots, findings);
125
- addContentUsageFindings(robots, findings);
126
- const totalConfigured = ALL_AI_CRAWLERS.filter((bot) => configuredBots.some((b) => b.name.toLowerCase() === bot.toLowerCase()));
127
- findings.push({
128
- status: totalConfigured.length >= 10 ? 'pass' : 'warn',
129
- message: `${totalConfigured.length}/${ALL_AI_CRAWLERS.length} known AI crawlers have explicit rules`,
130
- ...(totalConfigured.length < 10
131
- ? {
132
- hint: 'Add explicit User-agent entries for more AI crawlers to maximize discoverability.',
133
- learnMoreUrl: guideUrl(meta.id, 'low-coverage'),
134
- }
135
- : {}),
136
- });
137
- return buildResult(meta, score, findings, start);
138
- }
139
- /* ── Crawler catalogue reporting (informational in 3.x) ────────────────── */
140
- /**
141
- * Explain a block in terms of what it costs, grouped by purpose.
142
- *
143
- * Blocking a training crawler is a deliberate, defensible policy choice.
144
- * Blocking a search crawler removes the site from that assistant's answers,
145
- * which operators frequently do by accident when copying a "block AI" snippet.
146
- * Reporting both as one undifferentiated warning hides the difference that
147
- * matters.
148
- */
149
- function addBlockedPurposeFindings(blockedNames, findings) {
150
- if (blockedNames.length === 0)
151
- return;
152
- const byPurpose = new Map();
153
- for (const name of blockedNames) {
154
- const purpose = crawlerPurpose(name);
155
- if (purpose === undefined)
156
- continue;
157
- byPurpose.set(purpose, [...(byPurpose.get(purpose) ?? []), name]);
158
- }
159
- const search = byPurpose.get('search') ?? [];
160
- if (search.length > 0) {
161
- findings.push({
162
- status: 'warn',
163
- message: `${search.length} assistant search crawler(s) blocked — your site cannot be cited by those assistants`,
164
- detail: search.map((n) => `${n} — ${crawlerInfo(n)?.impact ?? 'search index crawler'}`).join('\n'),
165
- hint: 'Search crawlers build the index an assistant cites from; they are separate from the training crawlers. ' +
166
- 'If the intent was to opt out of training only, allow these and block the training tokens instead.',
167
- learnMoreUrl: guideUrl(meta.id, 'blocked-search-crawlers'),
168
- });
169
- }
170
- const training = byPurpose.get('training') ?? [];
171
- if (training.length > 0) {
172
- findings.push({
173
- status: 'pass',
174
- message: `${training.length} training crawler(s) blocked — recorded as a deliberate policy choice`,
175
- detail: training.join(', '),
176
- });
177
- }
178
- const userFetch = byPurpose.get('user-fetch') ?? [];
179
- const ignoring = userFetch.filter((n) => crawlerInfo(n)?.honorsRobots !== true);
180
- if (ignoring.length > 0) {
181
- findings.push({
182
- status: 'warn',
183
- message: `${ignoring.length} user-triggered fetcher(s) blocked in robots.txt that may ignore it`,
184
- detail: ignoring
185
- .map((n) => `${n} — ${crawlerInfo(n)?.note ?? 'documented as possibly ignoring robots.txt'}`)
186
- .join('\n'),
187
- hint: 'These clients fetch a page because a person asked for that URL, and their vendors document that robots.txt may not apply. ' +
188
- 'Enforce at the edge if the block must hold.',
189
- learnMoreUrl: guideUrl(meta.id, 'blocked-user-fetchers'),
190
- });
191
- }
192
- }
193
- /**
194
- * Flag rules for tokens that no longer do anything: renamed products,
195
- * discontinued crawlers, and strings that were never real user agents but
196
- * circulate widely in copy-pasted "block AI" snippets.
197
- */
198
- function addLegacyTokenFindings(configuredNames, findings) {
199
- const legacy = configuredNames
200
- .map((name) => ({ name, note: legacyCrawlerNote(name) }))
201
- .filter((e) => e.note !== undefined);
202
- if (legacy.length === 0)
203
- return;
204
- findings.push({
205
- status: 'warn',
206
- message: `${legacy.length} robots.txt rule(s) target a retired or non-existent crawler token`,
207
- detail: legacy.map((e) => `${e.name} — ${e.note}`).join('\n'),
208
- hint: 'These rules have no effect. Remove them so the file reflects your actual policy.',
209
- learnMoreUrl: guideUrl(meta.id, 'legacy-tokens'),
210
- });
211
- }
212
- /* ── Content Signals Policy (https://contentsignals.org) ───────────────── */
213
- /**
214
- * Report `Content-Signal:` declarations, the machine-readable statement of how
215
- * content may be used *after* access. Cloudflare serves these by default on its
216
- * managed robots.txt, so they show up on a large share of the web.
217
- *
218
- * Every finding here is informational in 3.x: no major AI operator has publicly
219
- * committed to honoring the policy (Google has said its crawlers ignore it), so
220
- * an absent directive must never cost a site points.
221
- */
222
- function addContentSignalFindings(robots, findings) {
223
- const declared = robots.groups.flatMap((g) => g.contentSignals.map((raw) => ({ group: g.userAgents, raw })));
224
- for (const raw of robots.orphanContentSignals) {
225
- findings.push({
226
- status: 'warn',
227
- message: 'Content-Signal directive outside a User-agent group',
228
- detail: `Content-Signal: ${raw}`,
229
- hint: 'Place Content-Signal inside a User-agent group, after the User-agent line it applies to.',
230
- learnMoreUrl: guideUrl(meta.id, 'invalid-content-signal'),
231
- });
232
- }
233
- if (declared.length === 0) {
234
- if (robots.orphanContentSignals.length > 0)
235
- return;
236
- findings.push({
237
- status: 'warn',
238
- message: 'No Content-Signal directive found (optional)',
239
- hint: 'Declare how crawlers may use your content after access with the Content Signals Policy, ' +
240
- 'e.g.: Content-Signal: search=yes, ai-train=no. Known signals: ' +
241
- `${CONTENT_SIGNALS.join(', ')}, plus the optional use=${CONTENT_SIGNAL_USE_VALUES.join('|')}. ` +
242
- 'Generate yours at contentsignals.org.',
243
- learnMoreUrl: guideUrl(meta.id, 'missing-content-signals'),
244
- });
245
- return;
246
- }
247
- if (robots.cloudflareManaged) {
248
- findings.push({
249
- status: 'pass',
250
- message: 'Content signals served from a Cloudflare-managed robots.txt block',
251
- detail: 'Edit these in the Cloudflare dashboard (AI Crawl Control), not in your origin robots.txt.',
252
- });
253
- }
254
- for (const decl of declared) {
255
- const group = decl.group.join(', ');
256
- const { valid, malformed, unknown, invalidValue } = parseContentSignalValue(decl.raw);
257
- if (valid.length > 0) {
258
- findings.push({
259
- status: 'pass',
260
- message: `Content signals declared for User-agent: ${group} — ${valid.join(', ')}`,
261
- });
262
- }
263
- if (malformed.length > 0) {
264
- findings.push({
265
- status: 'warn',
266
- message: `Malformed content signal segment(s) for User-agent: ${group}`,
267
- detail: malformed.join(', '),
268
- hint: 'Use comma-delimited signal=yes|no pairs, e.g.: Content-Signal: search=yes, ai-train=no.',
269
- learnMoreUrl: guideUrl(meta.id, 'invalid-content-signal'),
270
- });
271
- }
272
- if (invalidValue.length > 0) {
273
- findings.push({
274
- status: 'warn',
275
- message: `Content signal(s) with an out-of-vocabulary value for User-agent: ${group}`,
276
- detail: invalidValue.join(', '),
277
- hint: `${CONTENT_SIGNALS.join(', ')} take yes or no. ` +
278
- `use takes ${CONTENT_SIGNAL_USE_VALUES.join(', ')} (default: reference).`,
279
- learnMoreUrl: guideUrl(meta.id, 'invalid-content-signal'),
280
- });
281
- }
282
- if (unknown.length > 0) {
283
- findings.push({
284
- status: 'warn',
285
- message: `Unknown content signal name(s) for User-agent: ${group}`,
286
- detail: unknown.join(', '),
287
- hint: `The Content Signals Policy defines: ${CONTENT_SIGNALS.join(', ')}, use. Other names are ignored by crawlers.`,
288
- learnMoreUrl: guideUrl(meta.id, 'unknown-content-signal'),
289
- });
290
- }
291
- }
292
- }
293
- /* ── IETF AIPREF Content-Usage (draft-ietf-aipref-attach-05) ────────────── */
294
- /**
295
- * Report `Content-Usage:` rules. AIPREF is the IETF's answer to the same
296
- * question Content Signals asks, and its drafts are still pre-last-call, so
297
- * these findings are informational and an absent directive is never penalised.
298
- *
299
- * The one thing worth flagging loudly is a vocabulary mix-up: AIPREF spells the
300
- * training token `train-ai` with `y`/`n` values, while Content Signals and RSL
301
- * spell it `ai-train` with `yes`/`no`. A `Content-Usage: ai-train=no` line looks
302
- * right and does nothing.
303
- */
304
- function addContentUsageFindings(robots, findings) {
305
- const declared = robots.groups.flatMap((g) => g.contentUsage.map((raw) => ({ group: g.userAgents, raw })));
306
- const orphans = robots.orphanContentUsage;
307
- if (declared.length === 0 && orphans.length === 0)
308
- return;
309
- for (const raw of orphans) {
310
- findings.push({
311
- status: 'warn',
312
- message: 'Content-Usage rule outside a User-agent group',
313
- detail: `Content-Usage: ${raw}`,
314
- hint: 'AIPREF scopes Content-Usage rules to the User-agent group they appear in. Place the rule after a User-agent line.',
315
- learnMoreUrl: guideUrl(meta.id, 'invalid-content-usage'),
316
- });
317
- }
318
- for (const decl of declared) {
319
- const group = decl.group.join(', ');
320
- const scope = (path) => (path === null ? '' : ` for ${path}`);
321
- const { path, valid, unknown, invalidValue, malformed, crossVocabulary } = parseContentUsageValue(decl.raw);
322
- if (valid.length > 0) {
323
- findings.push({
324
- status: 'pass',
325
- message: `AI usage preferences declared for User-agent: ${group}${scope(path)} — ${valid.join(', ')}`,
326
- detail: 'IETF AIPREF (draft-ietf-aipref-attach). The drafts are pre-last-call; treat this as a forward-looking signal.',
327
- });
328
- }
329
- if (crossVocabulary.length > 0) {
330
- findings.push({
331
- status: 'warn',
332
- message: `Content-Usage uses Content Signals vocabulary for User-agent: ${group}`,
333
- detail: crossVocabulary.join(', '),
334
- hint: 'AIPREF defines train-ai and search with y/n values. Content Signals defines ai-train, ai-input and search with yes/no. Write Content-Usage: train-ai=n, and keep ai-train=no on the Content-Signal line.',
335
- learnMoreUrl: guideUrl(meta.id, 'content-usage-vocabulary'),
336
- });
337
- }
338
- if (invalidValue.length > 0) {
339
- findings.push({
340
- status: 'warn',
341
- message: `Content-Usage value(s) outside the AIPREF vocabulary for User-agent: ${group}`,
342
- detail: invalidValue.join(', '),
343
- hint: 'AIPREF preference values are y and n. An absent token means "unknown", never "allowed".',
344
- learnMoreUrl: guideUrl(meta.id, 'content-usage-vocabulary'),
345
- });
346
- }
347
- if (unknown.length > 0) {
348
- findings.push({
349
- status: 'warn',
350
- message: `Unknown Content-Usage token(s) for User-agent: ${group}`,
351
- detail: unknown.join(', '),
352
- hint: 'AIPREF vocabulary v07 defines train-ai and search. Extensions require a standards-track RFC.',
353
- learnMoreUrl: guideUrl(meta.id, 'content-usage-vocabulary'),
354
- });
355
- }
356
- if (malformed.length > 0) {
357
- findings.push({
358
- status: 'warn',
359
- message: `Malformed Content-Usage segment(s) for User-agent: ${group}`,
360
- detail: malformed.join(', '),
361
- hint: 'Content-Usage takes an optional path pattern then a Structured Fields dictionary, e.g.: Content-Usage: /docs/ train-ai=n, search=y.',
362
- learnMoreUrl: guideUrl(meta.id, 'invalid-content-usage'),
363
- });
364
- }
365
- }
366
- }
367
- //# sourceMappingURL=robots-txt.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"robots-txt.js","sourceRoot":"","sources":["../../src/checks/robots-txt.ts"],"names":[],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
2
- export declare const meta: CheckMeta;
3
- export default function check(ctx: CheckContext): Promise<CheckResult>;
4
- //# sourceMappingURL=rsl.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"rsl.d.ts","sourceRoot":"","sources":["../../src/checks/rsl.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAMjF,eAAO,MAAM,IAAI,EAAE,SAIlB,CAAC;AAmBF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA+F3E"}
@@ -1,242 +0,0 @@
1
- import { RSL_MIME, RSL_NAMESPACE, RSL_PAYMENT_TYPES, RSL_PERMIT_TYPES, RSL_USAGE_TOKENS, RSL_USER_TOKENS, } from '../constants.js';
2
- import { guideUrl } from '../guide-urls.js';
3
- import { buildResult, checkContentType } from './utils.js';
4
- import { findLinkTags, getAttribute, getTagAttribute } from './html-utils.js';
5
- import { parseLinkHeader } from './http-headers.js';
6
- import { parseRobotsLicenseDirectives } from './robots-parser.js';
7
- export const meta = {
8
- id: 'rsl',
9
- name: 'RSL License',
10
- description: 'Checks Really Simple Licensing (RSL) discovery and license document validity',
11
- };
12
- /** Score when discovery exists but the referenced license document cannot be fetched. */
13
- const UNREACHABLE_DOC_SCORE = 25;
14
- function isAbsoluteUrl(value) {
15
- try {
16
- new URL(value);
17
- return true;
18
- }
19
- catch {
20
- return false;
21
- }
22
- }
23
- export default async function check(ctx) {
24
- const start = performance.now();
25
- const findings = [];
26
- let score = 100;
27
- /* ── Discovery (RSL 1.0 §4.4–4.6) ─────────────────────────────────── */
28
- const discoveries = [];
29
- const robotsRes = await ctx.fetch(`${ctx.url}/robots.txt`);
30
- const directives = robotsRes.ok ? parseRobotsLicenseDirectives(robotsRes.body) : [];
31
- for (const value of directives) {
32
- discoveries.push({ mechanism: 'robots.txt License directive', url: value });
33
- }
34
- const linkEntries = parseLinkHeader(ctx.headers['link'] ?? '');
35
- for (const entry of linkEntries) {
36
- if (entry.params['rel'] === 'license' && (entry.params['type'] ?? '').includes(RSL_MIME)) {
37
- discoveries.push({ mechanism: 'Link header', url: entry.url });
38
- }
39
- }
40
- for (const tag of findLinkTags(ctx.html, 'license')) {
41
- const type = getAttribute(tag, 'type');
42
- const href = getAttribute(tag, 'href');
43
- if (type !== null && type.toLowerCase().includes(RSL_MIME) && href !== null) {
44
- discoveries.push({ mechanism: 'HTML <link rel="license">', url: href });
45
- }
46
- }
47
- if (discoveries.length === 0) {
48
- findings.push({
49
- status: 'fail',
50
- message: 'No RSL license discovery found',
51
- detail: 'Checked robots.txt License directive, Link header, and <link rel="license" type="application/rsl+xml">',
52
- hint: 'Declare machine-readable licensing terms for your content with Really Simple Licensing. ' +
53
- 'Add to robots.txt: License: https://your-site.com/license.xml — then publish the RSL document. ' +
54
- 'See https://rslstandard.org.',
55
- learnMoreUrl: guideUrl(meta.id, 'not-found'),
56
- });
57
- return buildResult(meta, 0, findings, start);
58
- }
59
- const mechanisms = [...new Set(discoveries.map((d) => d.mechanism))];
60
- findings.push({
61
- status: 'pass',
62
- message: `RSL license discovered via ${mechanisms.join(' + ')} (${discoveries.length} reference(s))`,
63
- });
64
- // The robots.txt directive value MUST be an absolute URI (RSL 1.0 §4.4.1).
65
- const relativeDirectives = directives.filter((d) => !isAbsoluteUrl(d));
66
- if (relativeDirectives.length > 0) {
67
- findings.push({
68
- status: 'warn',
69
- message: 'robots.txt License directive must be an absolute URI',
70
- detail: relativeDirectives.join(', '),
71
- hint: 'Use a fully qualified URL: License: https://your-site.com/license.xml',
72
- learnMoreUrl: guideUrl(meta.id, 'relative-license-url'),
73
- });
74
- score -= 10;
75
- }
76
- /* ── Fetch and validate the license document ──────────────────────── */
77
- const target = discoveries[0];
78
- const docUrl = isAbsoluteUrl(target.url) ? target.url : new URL(target.url, `${ctx.url}/`).toString();
79
- const docRes = await ctx.fetch(docUrl);
80
- if (!docRes.ok) {
81
- findings.push({
82
- status: 'fail',
83
- message: `RSL license document could not be fetched (${docUrl})`,
84
- detail: `HTTP ${docRes.status || 'network error'}`,
85
- hint: 'The discovery reference points to a missing document. Publish the RSL XML at the referenced URL.',
86
- learnMoreUrl: guideUrl(meta.id, 'fetch-failed'),
87
- });
88
- return buildResult(meta, Math.min(score, UNREACHABLE_DOC_SCORE), findings, start);
89
- }
90
- const ctFinding = checkContentType(docRes, [RSL_MIME], {
91
- checkId: meta.id,
92
- resourceLabel: 'RSL license document',
93
- anchor: 'wrong-content-type',
94
- });
95
- if (ctFinding) {
96
- findings.push(ctFinding);
97
- score -= 5;
98
- }
99
- else {
100
- findings.push({ status: 'pass', message: `RSL document Content-Type OK (${RSL_MIME})` });
101
- }
102
- score = validateRslDocument(docRes.body, findings, score);
103
- return buildResult(meta, score, findings, start);
104
- }
105
- /**
106
- * Regex-based structural validation of an RSL 1.0 document. Not a full XML parse —
107
- * consistent with the dependency-free approach used across all checks — but covers
108
- * the conformance points of §2.2 and §3: root element + namespace, <content url>,
109
- * <license> presence, permits/prohibits vocabulary, and payment types.
110
- */
111
- function validateRslDocument(rawBody, findings, score) {
112
- const body = rawBody.replace(/<!--[\s\S]*?-->/g, '');
113
- if (!/<rsl[\s>]/i.test(body)) {
114
- findings.push({
115
- status: 'fail',
116
- message: 'Root <rsl> element not found in license document',
117
- hint: 'An RSL document must have <rsl xmlns="https://rslstandard.org/rsl"> as its root element.',
118
- learnMoreUrl: guideUrl(meta.id, 'invalid-root'),
119
- });
120
- return score - 40;
121
- }
122
- const xmlns = getTagAttribute(body, 'rsl', 'xmlns');
123
- if (xmlns === RSL_NAMESPACE) {
124
- findings.push({ status: 'pass', message: 'Root <rsl> element with correct namespace' });
125
- }
126
- else {
127
- findings.push({
128
- status: 'warn',
129
- message: xmlns === null ? 'Missing xmlns on <rsl> root element' : `Wrong RSL namespace: "${xmlns}"`,
130
- hint: `Declare the default namespace: <rsl xmlns="${RSL_NAMESPACE}">`,
131
- learnMoreUrl: guideUrl(meta.id, 'wrong-namespace'),
132
- });
133
- score -= 15;
134
- }
135
- const contentTags = [...body.matchAll(/<content\b([^>]*?)\/?>/gi)].map((m) => m[1]);
136
- if (contentTags.length === 0) {
137
- findings.push({
138
- status: 'warn',
139
- message: 'No <content> elements found in license document',
140
- hint: 'Declare at least one <content url="/"> element wrapping a <license>.',
141
- learnMoreUrl: guideUrl(meta.id, 'no-content-elements'),
142
- });
143
- return score - 20;
144
- }
145
- // `url` is required on every <content>; an empty value is legal for HTML pages (§3.3).
146
- const missingUrl = contentTags.filter((attrs) => getAttribute(attrs, 'url') === null).length;
147
- if (missingUrl > 0) {
148
- findings.push({
149
- status: 'warn',
150
- message: `${missingUrl}/${contentTags.length} <content> element(s) missing the required url attribute`,
151
- hint: 'Every <content> element must carry a url attribute identifying the licensed asset (url="" is allowed for pages linked via <link rel="license">).',
152
- learnMoreUrl: guideUrl(meta.id, 'missing-content-url'),
153
- });
154
- score -= 10;
155
- }
156
- else {
157
- findings.push({ status: 'pass', message: `${contentTags.length} <content> element(s), all with url attribute` });
158
- }
159
- if (!/<license[\s>]/i.test(body)) {
160
- findings.push({
161
- status: 'warn',
162
- message: 'No <license> elements found in license document',
163
- hint: 'Each <content> element must contain at least one <license> element with its terms.',
164
- learnMoreUrl: guideUrl(meta.id, 'no-license-elements'),
165
- });
166
- score -= 15;
167
- }
168
- score = validatePermits(body, findings, score);
169
- score = validatePayments(body, findings, score);
170
- return score;
171
- }
172
- function validatePermits(body, findings, score) {
173
- const invalidTypes = [];
174
- const unknownTokens = [];
175
- const validSummaries = [];
176
- for (const m of body.matchAll(/<(permits|prohibits)\b([^>]*)>([^<]*)</gi)) {
177
- const element = m[1].toLowerCase();
178
- const type = (getAttribute(m[2], 'type') ?? '').toLowerCase();
179
- const tokens = m[3].trim().split(/\s+/).filter(Boolean);
180
- if (!RSL_PERMIT_TYPES.includes(type)) {
181
- invalidTypes.push(`<${element} type="${type || '(none)'}">`);
182
- continue;
183
- }
184
- const vocabulary = type === 'usage' ? RSL_USAGE_TOKENS : type === 'user' ? RSL_USER_TOKENS : null;
185
- const bad = vocabulary !== null
186
- ? tokens.filter((t) => !vocabulary.includes(t.toLowerCase()))
187
- : tokens.filter((t) => !/^[A-Z]{2}$/.test(t)); // geo: ISO 3166-1 alpha-2
188
- if (bad.length > 0) {
189
- unknownTokens.push(`<${element} type="${type}">: ${bad.join(' ')}`);
190
- }
191
- else if (tokens.length > 0) {
192
- validSummaries.push(`${element}[${type}]: ${tokens.join(' ')}`);
193
- }
194
- }
195
- if (validSummaries.length > 0) {
196
- findings.push({ status: 'pass', message: `License terms declared — ${validSummaries.join('; ')}` });
197
- }
198
- if (invalidTypes.length > 0) {
199
- findings.push({
200
- status: 'warn',
201
- message: 'permits/prohibits with invalid type attribute',
202
- detail: invalidTypes.join(', '),
203
- hint: `Valid types are: ${RSL_PERMIT_TYPES.join(', ')}.`,
204
- learnMoreUrl: guideUrl(meta.id, 'invalid-permits'),
205
- });
206
- score -= 5;
207
- }
208
- if (unknownTokens.length > 0) {
209
- findings.push({
210
- status: 'warn',
211
- message: 'permits/prohibits with tokens outside the RSL vocabulary',
212
- detail: unknownTokens.join('; '),
213
- hint: `Usage tokens: ${RSL_USAGE_TOKENS.join(', ')}. User tokens: ${RSL_USER_TOKENS.join(', ')}. ` +
214
- 'Geo tokens: ISO 3166-1 alpha-2 codes. Pre-1.0 draft tokens (train-ai, train-genai, ai-use, ai-summarize) ' +
215
- 'were replaced in RSL 1.0 by ai-train, ai-input, and ai-index.',
216
- learnMoreUrl: guideUrl(meta.id, 'invalid-permits'),
217
- });
218
- score -= 5;
219
- }
220
- return score;
221
- }
222
- function validatePayments(body, findings, score) {
223
- const invalid = [];
224
- for (const m of body.matchAll(/<payment\b([^>]*?)\/?>/gi)) {
225
- const type = getAttribute(m[1], 'type');
226
- if (type !== null && !RSL_PAYMENT_TYPES.includes(type.toLowerCase())) {
227
- invalid.push(type);
228
- }
229
- }
230
- if (invalid.length > 0) {
231
- findings.push({
232
- status: 'warn',
233
- message: 'payment element(s) with invalid type attribute',
234
- detail: invalid.join(', '),
235
- hint: `Valid payment types are: ${RSL_PAYMENT_TYPES.join(', ')}. Omitting type means free.`,
236
- learnMoreUrl: guideUrl(meta.id, 'invalid-payment-type'),
237
- });
238
- score -= 5;
239
- }
240
- return score;
241
- }
242
- //# sourceMappingURL=rsl.js.map