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,104 +1,15 @@
1
1
  # Architecture
2
2
 
3
- ax-audit is a dependency-light TypeScript codebase: two runtime dependencies (`chalk`, `commander`), Node 18+ built-in `fetch`, no HTTP libraries, no XML/HTML parser dependencies (regex-based primitives), and the built-in `node:test` runner.
3
+ Version 6 separates the public client from the private audit service.
4
4
 
5
- ## Pipeline
5
+ - `src/orchestrator.ts` sends authenticated requests to the fixed AX Rush audit endpoint. It contains no checks, HTML parsers, target fetcher or offline fallback.
6
+ - `src/license.ts` reads the account key and defines actionable client errors. Local validation is only for usability; the server owns authorization.
7
+ - `src/metadata.ts` holds check descriptions and aliases, without executable `run` functions.
8
+ - `src/reporter/`, `src/baseline.ts` and `src/scorer.ts` format, compare and summarize existing results locally.
9
+ - The private AX Rush application owns check implementations, safe outbound HTTP, server-side API-key and billing checks, and shared abuse limits. Authorization runs for every audit.
6
10
 
7
- ```
8
- cli.ts ──► orchestrator.ts ──► checks/* (Promise.allSettled, parallel)
9
- │ │
10
- ▼ ▼
11
- fetcher.ts scorer.ts ──► reporter/{terminal,json,html,markdown}
12
- (cache + retries) │
13
- ▲ baseline.ts (save / load / diff)
14
- └── shared by every check via CheckContext.fetch
15
- ```
11
+ Only URL, check selection, profile, request timeout and retry count are sent in the request body. The account key travels in the Authorization header only. There is no endpoint override or bypass flag. Private network targets are not supported.
16
12
 
17
- 1. **cli.ts** parses and validates flags, loads the baseline if requested, and dispatches to single or batch mode.
18
- 2. **orchestrator.ts** (`audit`) creates one fetcher per run, fetches the homepage once, builds the `CheckContext` (`url`, `html`, `headers`, `fetch`), and runs all selected checks in parallel. A check that throws is converted into a score-0 result with the error as a finding — one bad check never kills the audit. `batchAudit` runs `audit` per URL through an order-preserving work queue with configurable `concurrency`.
19
- 3. **fetcher.ts** wraps `fetch` with: per-run in-memory caching keyed on URL + normalized (lowercased, sorted) custom headers — mirroring HTTP `Vary` semantics so a `text/markdown` probe never collides with the HTML fetch; case-insensitive header merging over defaults; timeouts via `AbortController`; and retries with exponential backoff for transient failures (status 0, 408, 425, 429, 5xx). Errors never throw — they become `{ status: 0, ok: false, error }` results, also cached.
20
- 4. **checks/** — one module per check (26). Each exports `default` (async check function) and `meta` (`{ id, name, description, weight, category?, aliases? }`).
21
- 5. **scorer.ts** computes the weighted average over the checks that ran *and apply*; a check reporting `applicable: false` is excluded from both numerator and denominator. When every applicable check has weight 0 it falls back to a plain average.
22
- 6. **reporter/** renders to terminal (chalk), JSON, self-contained HTML, or Markdown, grouping checks by category and showing n/a where a check does not apply.
23
- 7. **baseline.ts** persists minimal score snapshots and computes per-check diffs for regression gating.
13
+ `npm test` checks the public API, report rendering, baselines and the remote transport, including denied access and unavailable service. Engine check tests live with the private server. `npm run prepublishOnly` builds from a clean `dist` and verifies the package allowlist so check implementations cannot accidentally ship.
24
14
 
25
- ## Anatomy of a check
26
-
27
- ```typescript
28
- import { guideUrl } from '../guide-urls.js';
29
- import type { CheckContext, CheckResult, CheckMeta, Finding } from '../types.js';
30
- import { buildResult } from './utils.js';
31
-
32
- export const meta: CheckMeta = {
33
- id: 'my-check',
34
- name: 'My Check',
35
- description: 'One-line description shown in reports',
36
- category: 'discovery',
37
- // No `weight` here: weights live in CHECK_WEIGHTS, and only there.
38
- };
39
-
40
- export default async function check(ctx: CheckContext): Promise<CheckResult> {
41
- const start = performance.now();
42
- const findings: Finding[] = [];
43
- let score = 100;
44
-
45
- const res = await ctx.fetch(`${ctx.url}/something`, { headers: { Accept: 'application/json' } });
46
- if (!res.ok) {
47
- findings.push({
48
- status: 'fail',
49
- message: '/something not found',
50
- hint: 'Actionable, copy-pasteable advice.',
51
- learnMoreUrl: guideUrl(meta.id, 'not-found'),
52
- });
53
- return buildResult(meta, 0, findings, start);
54
- }
55
-
56
- // ... validations, each pushing a pass/warn/fail Finding and adjusting score
57
-
58
- return buildResult(meta, score, findings, start);
59
- }
60
- ```
61
-
62
- Conventions:
63
-
64
- - **Findings are actionable.** Every `warn`/`fail` carries a `hint` with concrete remediation and a `learnMoreUrl` pointing to `axrush.com/guides/<check-id>#<anchor>`. Every anchor must have a section in that guide.
65
- - **Scores are clamped** to [0, 100] by `buildResult`.
66
- - **Shared HTML primitives** live in `checks/html-utils.ts` (`getMetaContent`, `findLinkTags`, `getAttribute`, `extractVisibleText`, …) — no per-check regex duplication.
67
- - **robots.txt is parsed once**, by `checks/robots-parser.ts`. It returns User-agent groups with their rules plus `Content-Signal`, `Content-Usage`, `License`, `Sitemap` and `Agentmap` directives. `robots-txt`, `rsl` and `agent-access` all consume it, so the grouping rules have one definition.
68
- - **Responses are classified**, not just status-checked. `checks/waf.ts` turns a response into ok / challenge / paywall / needs-signature / license-required / rate-limited / blocked, with the evidence that produced it and an `inconclusive` flag for what an unsigned probe cannot settle.
69
- - **Probed paths carry their standing.** `checks/well-known.ts` records every path as IANA-registered, vendor convention, draft or legacy, so a missing draft file is never reported like a missing registered one.
70
- - **An HTML body means absent, not broken.** `isHtmlDocument` in `checks/utils.ts` gates speculative probes: an SPA catch-all returns its index shell for every unknown path, and reporting that as a malformed document sends operators hunting for a bug in a file they never wrote.
71
- - **A check that does not apply reports N/A**, via `notApplicable()`, rather than scoring 0. Commerce discovery on a blog, OAuth metadata where nothing needs authorizing, WebMCP on a page with no forms: scoring these zero would say something false about the site. Everything counted against a site must be something the site could have done.
72
- - **Check ids are a public interface** — they appear in `--checks` flags and in saved baselines. A rename declares the old id in `meta.aliases`; `src/check-ids.ts` resolves aliases for selection and for baseline diffing.
73
- - **Content-Type validation** uses `checkContentType` from `checks/utils.ts` (−5 convention for mismatches).
74
- - **Network goes through `ctx.fetch`** — never raw `fetch` — so caching, retries, timeouts, and `--verbose` logging apply uniformly.
75
-
76
- ## Adding a new check
77
-
78
- 1. Create `src/checks/your-check.ts` exporting `default` + `meta` (weight 0 — see scoring policy below).
79
- 2. Register it in `src/checks/index.ts`.
80
- 3. Add its weight to `CHECK_WEIGHTS` in `src/constants.ts`.
81
- 4. Add a test suite in `test/checks/your-check.test.js` using `mockContext` / `mockResponse` from `test/helpers.js`. Route values can be functions `(url, fetchOptions) => response` when the response must vary by request headers.
82
- 5. Document it in `docs/checks.md` and the README table.
83
- 6. Write the remediation guide covering every `learnMoreUrl` anchor you emit.
84
-
85
- ## Scoring policy
86
-
87
- Score deltas on the same site are treated as **breaking**. Within a major version:
88
-
89
- - New checks ship with **weight 0**: full findings, no effect on the overall score or baselines.
90
- - New findings inside weighted checks must be informational, with no deduction.
91
- - Weight redistribution happens in a major version, and the baseline schema version is bumped with it so an existing baseline is not read as a regression.
92
-
93
- Weights live in `CHECK_WEIGHTS` in `src/constants.ts`, and only there. Checks used to declare their own `meta.weight` alongside the map; the two drifted, and a redistribution silently did nothing. `CheckMeta.weight` remains as an override that nothing uses, and a test asserts no check declares one.
94
-
95
- Two check states exist beyond a score:
96
-
97
- - **Weight 0** means the check runs and reports but rests on something too unsettled to score: a draft specification that may be renamed.
98
- - **Not applicable** means the question does not arise for this site. It leaves the denominator entirely. `--profile` overrides the detection.
99
-
100
- ## Testing
101
-
102
- `npm test` builds (`tsc`) and runs `node --test`. The suite (964 tests) covers every check, the scorer, baseline logic, the Markdown reporter, plus integration tests that spin up real local HTTP servers for the fetcher (per-header caching, retries, HEAD and manual redirects) and the batch orchestrator (ordering, concurrency caps). No test dependencies beyond Node.
103
-
104
- Two classes of test exist specifically to keep the 3.x promise that no score goes down: **score-stability tests** assert that a configuration which scored 100 in 3.6 still scores 100, and that findings added inside a weighted check leave the score untouched. When those fail, the change belongs in the next major, not the current minor.
15
+ The server must be deployed and verified before publishing a client version that relies on it.
package/docs/ci.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # CI Integration
2
2
 
3
+ For ax-audit 6, create a paid organization API key at
4
+ https://axrush.com/account/api, save it as the `AXRUSH_API_KEY` repository secret,
5
+ and expose it only to trusted audit jobs:
6
+
7
+ ```yaml
8
+ env:
9
+ AXRUSH_API_KEY: ${{ secrets.AXRUSH_API_KEY }}
10
+ ```
11
+
12
+ Fork pull requests do not receive secrets. Skip licensed audits for those runs;
13
+ never use `pull_request_target` to execute untrusted checkout code with this key.
14
+ The job needs outbound HTTPS to axrush.com; authorization and audit execution
15
+ both happen on AX Rush. License errors fail the command instead of publishing a score.
16
+
17
+
3
18
  ax-audit's exit codes (see [cli.md](./cli.md)) make it a drop-in quality gate: `0` for Good/Excellent, `1` for Fair/Poor or regressions.
4
19
 
5
20
  ## GitHub Actions
@@ -87,3 +102,7 @@ jobs:
87
102
  ```
88
103
 
89
104
  `--fail-on-regression 0` makes any per-check drop fail the workflow — appropriate for scheduled runs where every change is unexpected.
105
+
106
+ ## Remote execution in v6
107
+
108
+ Audit a public preview or deployed URL. Localhost and private CI network hosts are not reachable by AX Rush. The target URL and options are sent to AX Rush; no engine is downloaded. Each URL requires an active paid subscription and consumes the service rate limit.
package/docs/cli.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # CLI Reference
2
2
 
3
+ Version 6 requires `AXRUSH_API_KEY`, generated at https://axrush.com/account/api
4
+ by an owner/admin of an active paid organization. Set it through your environment
5
+ or CI secret store. It is never a command-line flag, target header or report field.
6
+ The server rejects missing/revoked keys and inactive subscriptions before
7
+ contacting the target. Service failures never trigger a local audit. `--help` and `--version` work without a key.
8
+
9
+
3
10
  ```bash
4
11
  ax-audit <urls...> [options]
5
12
  ```
@@ -101,3 +108,7 @@ Each area is reported against its threshold on stderr, so the output survives `-
101
108
  ## Baselines across a scoring change
102
109
 
103
110
  Baselines record which scoring model produced them. Comparing a baseline written by an older version shows the deltas but suspends regression gating, because a rescore is not something the site did. Re-save with `--save-baseline` to resume gating. Checks that changed applicability are excluded from regressions and improvements for the same reason.
111
+
112
+ ## Version 6 service boundary
113
+
114
+ The CLI sends the URL and options to AX Rush and renders the returned report. The engine is private to the server. Set `AXRUSH_API_KEY` using a key from an active paid account. Public HTTP(S) URLs only, ports 80/443, no URL credentials. `--timeout` is 100–10000 ms per server request, `--retries` is 0–2, and `--concurrency` is 1–4. Verbose output is client-side only; server network traces are not returned. Rate limits and API errors exit with code 2.
package/docs/faq.md CHANGED
@@ -1,3 +1,21 @@
1
+ # Version 6: remote service
2
+
3
+ ## Can I use the engine without a subscription?
4
+
5
+ No. The npm package contains only a client. AX Rush authorizes each audit against an active paid subscription. Modifying the client cannot expose engine code that it does not contain.
6
+
7
+ ## Can I audit localhost or run offline?
8
+
9
+ No. Audit a public preview or deployed site. AX Rush must be able to reach it.
10
+
11
+ ## What data leaves my machine?
12
+
13
+ The target URL and selected audit options are sent to AX Rush. AX Rush fetches the pages and returns a report. The API key is sent only to AX Rush, never to the target site.
14
+
15
+ ## What about versions 1–5?
16
+
17
+ They were distributed with local engine code. This release cannot disable downloaded copies or revoke previously granted license rights. They are legacy releases, not the current AX Rush service.
18
+
1
19
  # FAQ & Troubleshooting
2
20
 
3
21
  ## Scores & results
@@ -1,6 +1,38 @@
1
1
  # Getting Started
2
2
 
3
- This walkthrough takes you from zero to a passing AX score: run your first audit, learn to read the report, and fix findings in the order that moves your score most.
3
+ ## AX Rush service access (v6)
4
+
5
+ Install with `npm install ax-audit`. Version 6 is a CLI and SDK for the private
6
+ AX Rush service: **the npm package contains no audit engine or check implementations**.
7
+
8
+ 1. Subscribe at [AX Rush](https://axrush.com/pricing).
9
+ 2. Generate an organization key in [Account → API keys](https://axrush.com/account/api).
10
+ 3. Store it as `AXRUSH_API_KEY` in your environment or trusted CI secret store.
11
+ 4. Run `npx ax-audit@6 https://example.com`.
12
+
13
+ `audit({ url, apiKey })` can pass a key explicitly. Never commit keys or put them
14
+ in command-line arguments. The service checks the key and an active paid
15
+ subscription for **each audit**; revoked keys, canceled subscriptions, past-due
16
+ payments, trial-only and complimentary-only accounts cannot execute the engine.
17
+
18
+ The client sends the target URL and audit options to
19
+ `https://axrush.com/api/v1/engine/audit`, with the key in the Authorization header.
20
+ AX Rush fetches the public pages and returns the report. The key is never sent
21
+ to the target website. This endpoint does not save the report to your dashboard;
22
+ use the authenticated site API for stored scan history.
23
+
24
+ Only publicly reachable HTTP(S) sites on ports 80/443 are supported. Localhost,
25
+ private networks, URL credentials and custom ports are rejected. CI should audit
26
+ a public preview or deployed environment. Internet access to AX Rush is required;
27
+ there is no offline mode or local fallback. Current abuse limits are 10 audits
28
+ per organization per hour and 5 per target per hour, shared across keys and scan
29
+ surfaces. A batch consumes one audit for each URL.
30
+
31
+ Rendering an existing report, reading check descriptions and comparing saved
32
+ baselines remain local. `checks` contains metadata only; `checks[i].run()` was
33
+ removed. The CLI and client utilities remain Apache-2.0. Earlier releases and
34
+ rights already granted under their licenses cannot be revoked by this release.
35
+
4
36
 
5
37
  ## 1. Run your first audit
6
38
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "ax-audit",
3
- "version": "4.2.1",
4
- "description": "Audit websites for AI Agent Experience (AX) readiness. Lighthouse for AI Agents.",
3
+ "version": "6.0.0",
4
+ "description": "CLI and SDK for the private AX Rush audit service. Requires an active paid subscription and API key.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Durani Technologies (https://axrush.com)",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/duranitech/ax-audit.git"
10
+ "url": "git+https://github.com/duranitech/ax-audit.git"
11
11
  },
12
12
  "homepage": "https://axrush.com",
13
13
  "bugs": {
@@ -36,7 +36,7 @@
36
36
  "typescript"
37
37
  ],
38
38
  "bin": {
39
- "ax-audit": "./bin/ax-audit.js"
39
+ "ax-audit": "bin/ax-audit.js"
40
40
  },
41
41
  "exports": {
42
42
  ".": {
@@ -66,7 +66,7 @@
66
66
  "format": "prettier --write \"src/**/*.ts\"",
67
67
  "format:check": "prettier --check \"src/**/*.ts\"",
68
68
  "prepare": "husky",
69
- "prepublishOnly": "npm run build"
69
+ "prepublishOnly": "npm run build && node scripts/verify-package.mjs"
70
70
  },
71
71
  "lint-staged": {
72
72
  "src/**/*.ts": [
@@ -1,33 +0,0 @@
1
- import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
2
- /**
3
- * "agent-access" — the gap between what robots.txt permits and what the server
4
- * actually does.
5
- *
6
- * The failure this catches is invisible from the inside: robots.txt says
7
- * `Allow: /` for GPTBot, and the WAF returns 403 to anything whose user agent
8
- * says GPTBot. The operator sees a permissive robots.txt and assumes the site
9
- * is reachable. Cloudflare's "Block AI Crawlers" toggle produces exactly this,
10
- * and so does a hand-written rule that outlived its reason.
11
- *
12
- * The check probes the homepage once per core crawler and compares the result
13
- * against the default-user-agent baseline. Since 3.7 it classifies *how* a
14
- * request was turned away, because the remedies are completely different: a
15
- * JavaScript challenge needs a bot-management exception, a hard 403 needs a
16
- * firewall rule change, a 402 is a deliberate price, and a signature demand is
17
- * the site working as designed.
18
- *
19
- * Honesty constraint: this probe is unsigned and comes from the auditor's own
20
- * network. An edge that verifies crawlers by IP range or Web Bot Auth signature
21
- * will reject it while admitting the genuine crawler. Those outcomes are scored
22
- * as inconclusive and labelled as such, never as "blocks AI crawlers".
23
- */
24
- export declare const meta: CheckMeta;
25
- /**
26
- * Build a realistic crawler User-Agent for a given bot token. WAF and
27
- * bot-management rules match on the token substring, which is what we need
28
- * to trigger the same code path the real crawler would hit.
29
- */
30
- export declare function crawlerUserAgent(token: string): string;
31
- export { intentBlocked } from './robots-parser.js';
32
- export default function check(ctx: CheckContext): Promise<CheckResult>;
33
- //# sourceMappingURL=agent-access.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"agent-access.d.ts","sourceRoot":"","sources":["../../src/checks/agent-access.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAMjF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AA+BF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAGD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAoCnD,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA8E3E"}
@@ -1,256 +0,0 @@
1
- import { PROBEABLE_CORE_CRAWLERS, crawlerInfo } from '../constants.js';
2
- import { guideUrl } from '../guide-urls.js';
3
- import { buildResult } from './utils.js';
4
- import { extractVisibleText, findJsonLdBlocks } from './html-utils.js';
5
- import { parseUserAgents, intentBlocked } from './robots-parser.js';
6
- import { classifyResponse, INCONCLUSIVE_CAVEAT } from './waf.js';
7
- /**
8
- * "agent-access" — the gap between what robots.txt permits and what the server
9
- * actually does.
10
- *
11
- * The failure this catches is invisible from the inside: robots.txt says
12
- * `Allow: /` for GPTBot, and the WAF returns 403 to anything whose user agent
13
- * says GPTBot. The operator sees a permissive robots.txt and assumes the site
14
- * is reachable. Cloudflare's "Block AI Crawlers" toggle produces exactly this,
15
- * and so does a hand-written rule that outlived its reason.
16
- *
17
- * The check probes the homepage once per core crawler and compares the result
18
- * against the default-user-agent baseline. Since 3.7 it classifies *how* a
19
- * request was turned away, because the remedies are completely different: a
20
- * JavaScript challenge needs a bot-management exception, a hard 403 needs a
21
- * firewall rule change, a 402 is a deliberate price, and a signature demand is
22
- * the site working as designed.
23
- *
24
- * Honesty constraint: this probe is unsigned and comes from the auditor's own
25
- * network. An edge that verifies crawlers by IP range or Web Bot Auth signature
26
- * will reject it while admitting the genuine crawler. Those outcomes are scored
27
- * as inconclusive and labelled as such, never as "blocks AI crawlers".
28
- */
29
- export const meta = {
30
- id: 'agent-access',
31
- name: 'Agent Access',
32
- description: 'Checks that AI crawler user-agents are not blocked or served reduced content (cloaking)',
33
- category: 'access',
34
- };
35
- /** Content below this fraction of the baseline visible text counts as "reduced". */
36
- const REDUCED_CONTENT_RATIO = 0.5;
37
- /** Baselines with less visible text than this are too small for meaningful content comparison. */
38
- const MIN_BASELINE_TEXT = 200;
39
- /** Credit each outcome contributes to the access score. */
40
- const OUTCOME_CREDIT = {
41
- ok: 1,
42
- 'blocked-consistent': 1,
43
- conditional: 1,
44
- inconclusive: 0.75,
45
- reduced: 0.5,
46
- blocked: 0,
47
- };
48
- /**
49
- * Build a realistic crawler User-Agent for a given bot token. WAF and
50
- * bot-management rules match on the token substring, which is what we need
51
- * to trigger the same code path the real crawler would hit.
52
- */
53
- export function crawlerUserAgent(token) {
54
- return `Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ${token}/1.0)`;
55
- }
56
- // `intentBlocked` moved to robots-parser.ts in 3.7; re-exported for compatibility.
57
- export { intentBlocked } from './robots-parser.js';
58
- function shapeOf(html) {
59
- const title = html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i);
60
- const h1 = html.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i);
61
- return {
62
- textLength: extractVisibleText(html).length,
63
- title: title ? extractVisibleText(title[1]) : null,
64
- h1: h1 ? extractVisibleText(h1[1]) : null,
65
- jsonLdBlocks: findJsonLdBlocks(html).length,
66
- };
67
- }
68
- /** Differences between a crawler's page and the baseline, in reader-facing terms. */
69
- function shapeDiff(baseline, actual) {
70
- const diffs = [];
71
- if (baseline.title !== null && actual.title !== baseline.title) {
72
- diffs.push(`title differs ("${actual.title ?? 'none'}" vs "${baseline.title}")`);
73
- }
74
- if (baseline.h1 !== null && actual.h1 !== baseline.h1) {
75
- diffs.push(`h1 differs ("${actual.h1 ?? 'none'}" vs "${baseline.h1}")`);
76
- }
77
- if (baseline.jsonLdBlocks > 0 && actual.jsonLdBlocks < baseline.jsonLdBlocks) {
78
- diffs.push(`${baseline.jsonLdBlocks - actual.jsonLdBlocks} JSON-LD block(s) missing`);
79
- }
80
- return diffs;
81
- }
82
- export default async function check(ctx) {
83
- const start = performance.now();
84
- const findings = [];
85
- const baseline = await ctx.fetch(ctx.url);
86
- if (!baseline.ok) {
87
- findings.push({
88
- status: 'fail',
89
- message: 'Baseline homepage request failed — cannot compare crawler access',
90
- detail: `HTTP ${baseline.status || 'network error'}`,
91
- learnMoreUrl: guideUrl(meta.id, 'baseline-unavailable'),
92
- });
93
- return buildResult(meta, 0, findings, start);
94
- }
95
- const baselineShape = shapeOf(baseline.body);
96
- const baselineText = baselineShape.textLength;
97
- const robotsRes = await ctx.fetch(`${ctx.url}/robots.txt`);
98
- const robotsEntries = robotsRes.ok ? parseUserAgents(robotsRes.body) : [];
99
- const outcomes = new Map();
100
- for (const crawler of PROBEABLE_CORE_CRAWLERS) {
101
- const res = await ctx.fetch(ctx.url, { headers: { 'User-Agent': crawlerUserAgent(crawler) } });
102
- const cls = classifyResponse(res);
103
- const blockedByRobots = intentBlocked(robotsEntries, crawler);
104
- if (cls.kind === 'ok') {
105
- const shape = shapeOf(res.body);
106
- const diffs = shapeDiff(baselineShape, shape);
107
- const textDropped = baselineText >= MIN_BASELINE_TEXT && shape.textLength < baselineText * REDUCED_CONTENT_RATIO;
108
- if (textDropped || diffs.length > 0) {
109
- outcomes.set(crawler, 'reduced');
110
- findings.push({
111
- status: 'warn',
112
- message: `${crawler} receives a different page than a regular client`,
113
- detail: [textDropped ? `${shape.textLength} vs ${baselineText} chars of visible text` : null, ...diffs]
114
- .filter(Boolean)
115
- .join('; '),
116
- hint: 'The server returns 200 but serves this crawler different content — often an interstitial, a consent ' +
117
- 'wall, or conditional rendering. Agents index exactly what they receive, so the version they see is the ' +
118
- 'version that gets cited.',
119
- learnMoreUrl: guideUrl(meta.id, 'reduced-content'),
120
- });
121
- }
122
- else {
123
- outcomes.set(crawler, 'ok');
124
- }
125
- continue;
126
- }
127
- outcomes.set(crawler, recordTurnedAway(crawler, cls, blockedByRobots, robotsRes.ok, findings));
128
- }
129
- const okCount = [...outcomes.values()].filter((o) => o === 'ok').length;
130
- if (okCount === PROBEABLE_CORE_CRAWLERS.length) {
131
- findings.unshift({
132
- status: 'pass',
133
- message: `All ${PROBEABLE_CORE_CRAWLERS.length} core AI crawler user-agents receive the same page as a regular client`,
134
- });
135
- }
136
- const inconclusive = [...outcomes.entries()].filter(([, o]) => o === 'inconclusive');
137
- if (inconclusive.length > 0) {
138
- findings.push({
139
- status: 'warn',
140
- message: `${inconclusive.length} crawler probe(s) could not be settled from outside`,
141
- detail: inconclusive.map(([name]) => name).join(', '),
142
- hint: INCONCLUSIVE_CAVEAT,
143
- learnMoreUrl: guideUrl(meta.id, 'inconclusive-probe'),
144
- });
145
- }
146
- const credit = [...outcomes.values()].reduce((acc, o) => acc + OUTCOME_CREDIT[o], 0);
147
- const score = Math.round((credit / PROBEABLE_CORE_CRAWLERS.length) * 100);
148
- return buildResult(meta, score, findings, start);
149
- }
150
- /**
151
- * Turn a non-200 crawler response into a finding and an outcome. The
152
- * classification decides both the wording and the credit: a challenge is not a
153
- * block, a price is not a refusal, and a signature demand is a site working as
154
- * designed.
155
- */
156
- function recordTurnedAway(crawler, cls, blockedByRobots, robotsAvailable, findings) {
157
- const info = crawlerInfo(crawler);
158
- const evidence = cls.evidence.length > 0 ? cls.evidence.join('; ') : cls.label;
159
- switch (cls.kind) {
160
- case 'challenge':
161
- findings.push({
162
- status: 'warn',
163
- message: `${crawler} receives a ${cls.vendor ?? 'JavaScript'} challenge instead of the page`,
164
- detail: evidence,
165
- hint: 'Challenge pages require running JavaScript. Crawlers that only fetch HTML — which is most of them — never ' +
166
- 'get past one, so the page is effectively unavailable to them even though nothing is "blocked". ' +
167
- 'Add a bot-management exception for verified AI crawlers. ' +
168
- INCONCLUSIVE_CAVEAT,
169
- learnMoreUrl: guideUrl(meta.id, 'challenge-page'),
170
- });
171
- return 'inconclusive';
172
- case 'needs-signature':
173
- findings.push({
174
- status: 'pass',
175
- message: `${crawler} must present a Web Bot Auth signature`,
176
- detail: evidence,
177
- hint: 'The origin asks unverified clients to re-request with an HTTP Message Signature. Vendors that sign their ' +
178
- 'requests will pass; this probe does not sign, so it cannot confirm the outcome for the real crawler.',
179
- learnMoreUrl: guideUrl(meta.id, 'needs-signature'),
180
- });
181
- return 'inconclusive';
182
- case 'paywall':
183
- findings.push({
184
- status: 'pass',
185
- message: `${crawler} is offered priced access${cls.price ? ` at ${cls.price}` : ''}`,
186
- detail: evidence,
187
- hint: 'Content is monetised for crawlers rather than blocked. Crawlers that support the payment flow can still reach it.',
188
- learnMoreUrl: guideUrl(meta.id, 'pay-per-crawl'),
189
- });
190
- return 'conditional';
191
- case 'license-required':
192
- findings.push({
193
- status: 'pass',
194
- message: `${crawler} is asked to obtain a licence before use`,
195
- detail: evidence,
196
- hint: 'The origin implements the RSL Open Licensing Protocol. Clients that negotiate a licence can reach the content.',
197
- learnMoreUrl: guideUrl(meta.id, 'license-required'),
198
- });
199
- return 'conditional';
200
- case 'rate-limited':
201
- findings.push({
202
- status: 'warn',
203
- message: `${crawler} is rate limited on a single request`,
204
- detail: evidence,
205
- hint: 'One probe should not hit a rate limit. A limit this tight will stall any crawl. Always answer 429 with a ' +
206
- 'Retry-After header so well-behaved crawlers back off correctly instead of giving up.',
207
- learnMoreUrl: guideUrl(meta.id, 'rate-limited'),
208
- });
209
- return 'inconclusive';
210
- case 'server-error':
211
- case 'network-error':
212
- findings.push({
213
- status: 'warn',
214
- message: `${crawler} probe failed: ${cls.label}`,
215
- detail: evidence,
216
- hint: 'The request did not complete, so access for this crawler is unknown. Re-run the audit; if it persists, check origin health for this user agent.',
217
- learnMoreUrl: guideUrl(meta.id, 'probe-failed'),
218
- });
219
- return 'inconclusive';
220
- default: {
221
- // A plain refusal. Whether it is a finding depends on robots.txt intent.
222
- if (blockedByRobots) {
223
- findings.push({
224
- status: 'pass',
225
- message: `${crawler} blocked at the server — consistent with its robots.txt Disallow`,
226
- detail: evidence,
227
- });
228
- return 'blocked-consistent';
229
- }
230
- if (cls.inconclusive) {
231
- findings.push({
232
- status: 'warn',
233
- message: `${crawler} is refused by ${cls.vendor ?? 'the origin'} while robots.txt permits it`,
234
- detail: evidence,
235
- hint: (info ? `${info.impact} ` : '') +
236
- 'This edge verifies crawlers by IP range or signature, so the refusal may be correct anti-spoofing rather ' +
237
- 'than a policy block. ' +
238
- INCONCLUSIVE_CAVEAT,
239
- learnMoreUrl: guideUrl(meta.id, 'blocked-crawler'),
240
- });
241
- return 'inconclusive';
242
- }
243
- findings.push({
244
- status: 'warn',
245
- message: `${crawler} is ${robotsAvailable ? 'allowed in robots.txt' : 'not restricted'} but its User-Agent is refused`,
246
- detail: evidence,
247
- hint: (info ? `${info.impact} ` : '') +
248
- 'Your firewall rejects this crawler token even though robots.txt permits it — the block is invisible to you ' +
249
- 'but fatal for the agent. Check your firewall rules and AI-bot toggles (for example Cloudflare "Block AI Crawlers").',
250
- learnMoreUrl: guideUrl(meta.id, 'blocked-crawler'),
251
- });
252
- return 'blocked';
253
- }
254
- }
255
- }
256
- //# sourceMappingURL=agent-access.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"agent-access.js","sourceRoot":"","sources":["../../src/checks/agent-access.ts"],"names":[],"mappings":"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"}
@@ -1,37 +0,0 @@
1
- import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
2
- /**
3
- * "agent-card" — the A2A (Agent2Agent) Agent Card.
4
- *
5
- * Two things changed since this check was written, and both matter:
6
- *
7
- * 1. **The path moved.** A2A v0.3.0 (2025-07-30) relocated the card from
8
- * `/.well-known/agent.json` to `/.well-known/agent-card.json`, which is now
9
- * an IANA-registered well-known URI. A site serving only the old path is
10
- * invisible to a current client.
11
- *
12
- * 2. **The shape changed.** A2A 1.0 (2026-03-12, breaking) folded the
13
- * top-level `url`, `protocolVersion`, `preferredTransport` and
14
- * `additionalInterfaces` fields into a single `supportedInterfaces[]` array,
15
- * and renamed `supportsAuthenticatedExtendedCard` to
16
- * `capabilities.extendedAgentCard`. Most deployed cards are still 0.3-shaped.
17
- *
18
- * Validating a 1.0 card against 0.3 rules (or the reverse) produces nonsense,
19
- * so the check detects which generation a card belongs to from its own
20
- * structure and applies the matching rules. `authentication`, dropped back in
21
- * 0.2.x in favour of `securitySchemes`, is flagged wherever it appears.
22
- *
23
- * Spec: https://a2a-protocol.org/latest/specification/
24
- * Normative field list: specification/a2a.proto in github.com/a2aproject/A2A
25
- */
26
- export declare const meta: CheckMeta;
27
- /** Which generation of the A2A spec a card was written against. */
28
- type Generation = 'v1' | 'v0.3' | 'unknown';
29
- /**
30
- * Detect the card generation from its own structure rather than from a version
31
- * field, because 0.3 cards state `protocolVersion` at the top level and 1.0
32
- * cards state it per interface.
33
- */
34
- export declare function detectGeneration(data: Record<string, unknown>): Generation;
35
- export default function check(ctx: CheckContext): Promise<CheckResult>;
36
- export {};
37
- //# sourceMappingURL=agent-card.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"agent-card.d.ts","sourceRoot":"","sources":["../../src/checks/agent-card.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAC;AAKhG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,IAAI,EAAE,SAMlB,CAAC;AAKF,mEAAmE;AACnE,KAAK,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;AAE5C;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAI1E;AAqBD,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAkF3E"}