turva-llms-txt-validator 0.3.13 → 0.3.14

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.
package/README.md CHANGED
@@ -54,7 +54,7 @@ Use JSON for automation and strict mode when warnings should fail a CI step:
54
54
  npx --yes turva-llms-txt-validator example.com --json --strict
55
55
  ```
56
56
 
57
- Completed validation returns `{ target, summary, checks }`. Each check contains `{ id, status, label, detail }`. The `input-path` check, which reports a path given with the domain and then ignored, also carries `value`, that path cut to 120 characters. With `--json`, an invalid domain or a failed fetch returns `{ "error": "...", "target": "..." }` and exits with code `2`. The target is the address given on the command line, with any user name, password and query value masked, and a network error adds `code`, such as `EAI_AGAIN`. An unknown option, a second domain and a missing domain return `{ "error": "..." }` only, with no target, and also exit with code `2`. `--help` exits with code `0`, and `--version` or `-V` prints the installed version and exits with code `0`.
57
+ Completed validation returns `{ target, summary, checks }`. Each check contains `{ id, status, label, detail }`. The `input-path` check, which reports a path given with the domain and then ignored, also carries `value`, that path cut to 120 characters. With `--json`, an invalid domain or a failed fetch returns `{ "error": "...", "target": "..." }` and exits with code `2`. The target is the address given on the command line, with any password and query value masked. A user name is masked too, except in a value such as `user:secret@example.com`, where the part before the colon reads as a URL scheme and is shown as `user:***@example.com`. A value with no scheme and no slash is read as user information when a colon comes before its last `@`, so `user_name:secret@example.com` is shown as `***@example.com`. Without that colon, as in `path@2x.png` or an e-mail address, the value is shown as given and holds no password. A network error adds `code`, such as `EAI_AGAIN`. An unknown option, a second domain and a missing domain return `{ "error": "..." }` only, with no target, and also exit with code `2`. `--help` exits with code `0`, and `--version` or `-V` prints the installed version and exits with code `0`.
58
58
 
59
59
  ## What it checks
60
60
 
@@ -71,7 +71,7 @@ Completed validation returns `{ target, summary, checks }`. Each check contains
71
71
  | 7 | File is small enough to read cheaply | none | Over 50 KB, or truncated at the 256 KB read limit |
72
72
  | 8 | File contains no HTML markup | none | HTML tags found |
73
73
 
74
- Links are read the way CommonMark reads them for the forms listed here. A link inside inline code, after an escaped bracket or in an image does not count, and a link in an ordered list, a target in angle brackets and a target with a title in double or single quotes do. A title in parentheses is not read, so `[Guide](https://example.org/guide (Overview))` is not collected. An unbracketed target ends at its first closing parenthesis, so `https://example.org/docs_(v2)` is read only up to `https://example.org/docs_(v2`; write a target that contains parentheses in angle brackets. A link reference definition or label that spans several lines, and an image used as a reference, are not handled.
74
+ Links are read the way CommonMark reads them for the forms listed here. A link inside inline code, after an escaped bracket or in an image does not count, and a link in an ordered list, a target in angle brackets and a target with a title in double or single quotes do. A title in parentheses is not read, so `[Guide](https://example.org/guide (Overview))` is not collected. An unbracketed target ends at its first closing parenthesis, so `https://example.org/docs_(v2)` is read only up to `https://example.org/docs_(v2`; write a target that contains parentheses in angle brackets. A link reference definition or label that spans several lines, and an image used as a reference, are not handled. A quote escaped with a backslash inside a link title, as in `[A](https://example.org/a "Ti\"tle\"")`, is not recognised, so that link is dropped. A title that spans a line break is dropped the same way. Nested brackets in a link label, as in `[A [x] B](https://example.org/a)`, drop the link. A heading is an H2 only when `##` is followed by a space, so `##` followed by a tab is not read as one. A link in a four-space indented code block counts as a link, but such a block does not count as a file list under an H2.
75
75
 
76
76
  Some failures stop the file checks early. For example, an HTML response is reported as a failed plain-text check rather than parsed as Markdown.
77
77
 
@@ -136,7 +136,7 @@ For vulnerability reporting and supported versions, see [SECURITY.md](SECURITY.m
136
136
 
137
137
  The [hosted validator](https://turva.dev/llms-txt-validator) accepts any public domain in the browser. It requests the same two documents, `/llms.txt` and the home page, and runs the same checks. Its source is in the [turva.dev Cloudflare Worker](https://github.com/erekola/turva-worker). The hosted validator is the canonical implementation: if results diverge, this package is updated to match it.
138
138
 
139
- The hosted version also returns JSON with the same result shape for a completed validation. Its errors are shaped differently: an `error` field with HTTP status 400 and no `target`.
139
+ The hosted version also returns JSON with the same result shape for a completed validation. Its errors are shaped differently: an `error` field with HTTP status 400 and no `target`. The site-wide rate limiter can also answer HTTP 429 as plain text, with a `Retry-After` header, before the validator runs, and that answer is not JSON.
140
140
 
141
141
  The JSON request:
142
142
 
package/bin/cli.mjs CHANGED
@@ -2,7 +2,8 @@
2
2
  // CLI for turva-llms-txt-validator. Exit codes: 0 = valid (or valid with
3
3
  // warnings), 1 = not valid (or warnings with --strict), 2 = could not fetch
4
4
  // or bad input. The two v2 discovery checks and the input-path note carry status
5
- // "info" and move no exit code, by design. Same checks and JSON shape as the hosted validator:
5
+ // "info" and move no exit code, by design. Same checks as the hosted validator, and the same
6
+ // JSON shape for a completed validation:
6
7
  // curl -H "Accept: application/json" "https://turva.dev/llms-txt-validator?url=example.com"
7
8
  import { readFileSync } from "node:fs";
8
9
  import { fileURLToPath } from "node:url";
@@ -10,7 +11,7 @@ import { validateHost, stripControls, maskLocation } from "../src/index.mjs";
10
11
 
11
12
  const USAGE = [
12
13
  "usage: llms-txt-validate <domain-or-url> [--json] [--strict]",
13
- " --json print the result as JSON (same shape as the hosted validator)",
14
+ " --json print the result as JSON (same shape as the hosted validator for a completed validation)",
14
15
  " --strict exit 1 on warnings too, for CI gates",
15
16
  " --help print this help",
16
17
  " --version print the installed version and exit"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turva-llms-txt-validator",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Validate a site's llms.txt structure: eight checks as pass/warn/fail plus two llms.txt v2 discovery checks that read pass or info and never move the summary, JSON output. Same checks as the hosted validator at turva.dev/llms-txt-validator.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
package/src/index.mjs CHANGED
@@ -63,11 +63,20 @@ export function maskLocation(href, base) {
63
63
  // path ("path@2x.png" stays unmasked below). But when an @ still appears before the first
64
64
  // /, the text ahead of it reads as userinfo even when the parser here does not recognise it
65
65
  // as a scheme (an underscore, a leading digit or space, a stray control character), so that
66
- // shape is masked too instead of let through (VN1, round 3 Tek-542).
66
+ // shape is masked too instead of let through (VN1, round 3 Tek-542). With no slash at all
67
+ // the same holds when a ":" comes before the last @: "user_name:secret@example.com" is
68
+ // user:password and is masked, while "path@2x.png" has no ":" ahead of its @ and stays as
69
+ // given (outside review V13, D5-1: the slashless shape used to return early and print the
70
+ // password).
67
71
  if (!scheme && from - start < 2) {
68
72
  const firstSlash = s.indexOf("/");
69
73
  const firstAt = s.indexOf("@");
70
- if (firstAt < 0 || firstSlash < 0 || firstAt > firstSlash) return s;
74
+ if (firstAt < 0) return s;
75
+ if (firstSlash < 0) {
76
+ if (!s.slice(0, s.lastIndexOf("@")).includes(":")) return s;
77
+ } else if (firstAt > firstSlash) {
78
+ return s;
79
+ }
71
80
  }
72
81
  // Nothing tells which @ of a refused target ends the user information, so everything up to the
73
82
  // last one is shown as ***. The mask is visible on purpose: three review rounds on 2026-09-22