{"_id":"@althorlabs/a11yscan","_rev":"2-1214649ee3a06c41f26d0f01c8c1d352","name":"@althorlabs/a11yscan","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.2":{"name":"@althorlabs/a11yscan","version":"0.1.2","keywords":["accessibility","a11y","wcag","axe-core","playwright","cli","github-action"],"author":{"name":"althor.dev"},"license":"MIT","_id":"@althorlabs/a11yscan@0.1.2","maintainers":[{"name":"althor","email":"eyeoftheworld44@gmail.com"}],"homepage":"https://a11yscan.althor.dev","bugs":{"url":"https://github.com/st0rm-bless3d/a11yscan/issues"},"bin":{"a11yscan":"dist/cli.js"},"dist":{"shasum":"071306a6ecf9c8770ec9ef7aa610e2dd6bb6ee66","tarball":"https://registry.npmjs.org/@althorlabs/a11yscan/-/a11yscan-0.1.2.tgz","fileCount":38,"integrity":"sha512-ZT7MQ1tZ0QAS6ybdeDBQ17sk2nJ1m+dcjpu2hjR+X5kHgNFio4iWZLk2X3kix5DfRZg6wGKOgJcz/0xkqVIAJQ==","signatures":[{"sig":"MEYCIQDoBuuVSITRWzdVXj583LlFcjjWW2vL2a2LelJwhMmjnAIhAOknZMvhm1qH/3TWc02UAw/OPkGLjVbopjzXpuPytFuA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":139536},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && chmod +x dist/cli.js","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"althor","email":"eyeoftheworld44@gmail.com"},"repository":{"url":"git+https://github.com/st0rm-bless3d/a11yscan.git","type":"git"},"_npmVersion":"10.9.8","description":"Open-source WCAG accessibility scanner CLI and GitHub Action (headless Chromium + axe-core). Scans URLs on your own machine's network, prioritized report by impact, optional LLM fix hints.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"undici":"^6.21.1","ipaddr.js":"^2.2.0","playwright":"^1.50.1","@axe-core/playwright":"^4.10.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","typescript":"^5.7.3","@types/node":"^22.10.5"},"_npmOperationalInternal":{"tmp":"tmp/a11yscan_0.1.2_1786240266596_0.1456104151749471","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@althorlabs/a11yscan","version":"0.1.3","description":"Open-source WCAG accessibility scanner CLI and GitHub Action (headless Chromium + axe-core). Scans URLs on your own machine's network, prioritized report by impact, optional LLM fix hints.","keywords":["accessibility","a11y","wcag","axe-core","playwright","cli","github-action"],"license":"MIT","author":{"name":"althor.dev"},"homepage":"https://a11yscan.althor.dev","repository":{"type":"git","url":"git+https://github.com/st0rm-bless3d/a11yscan.git"},"type":"module","engines":{"node":">=20"},"bin":{"a11yscan":"dist/cli.js"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc -p tsconfig.json && chmod +x dist/cli.js","typecheck":"tsc -p tsconfig.json --noEmit","test":"node --import tsx --test test/*.test.ts","prepublishOnly":"npm run build && npm test"},"dependencies":{"@axe-core/playwright":"^4.10.2","ipaddr.js":"^2.2.0","playwright":"^1.50.1","undici":"^6.21.1"},"devDependencies":{"@types/node":"^22.10.5","tsx":"^4.19.2","typescript":"^5.7.3"},"bugs":{"url":"https://github.com/st0rm-bless3d/a11yscan/issues"},"publishConfig":{"access":"public"},"gitHead":"a92f359460327f2a0757cc5c3b0324379034e095","_id":"@althorlabs/a11yscan@0.1.3","_nodeVersion":"22.23.1","_npmVersion":"12.0.2","dist":{"integrity":"sha512-Js6WL8mUbgNta4I8dOsv+uZU7UhSRHX7Qn8rxS52kPBeVD5if/rK8vwb3DlKkycIvPt8qzOs8NDMEg44uIYX4w==","shasum":"cdf01f71ada7deaac7c5f5b1bb1aaab6ba969cea","tarball":"https://registry.npmjs.org/@althorlabs/a11yscan/-/a11yscan-0.1.3.tgz","fileCount":38,"unpackedSize":140497,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@althorlabs%2fa11yscan@0.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIH7PMKCQP/tt1FPapSokgP0hqGxPAK1QtpfgBIOaeR5QAiAd6TbfliSaS33gpflycdhWUHybT1BXvL3p+vyv5HgY4A=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:08abef05-41ed-47c9-86c2-4672abaf1904"}},"directories":{},"maintainers":[{"name":"althor","email":"eyeoftheworld44@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/a11yscan_0.1.3_1786242293836_0.3783425737406261"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T01:51:06.423Z","modified":"2026-08-09T02:24:54.316Z","0.1.2":"2026-08-09T01:51:06.741Z","0.1.3":"2026-08-09T02:24:53.987Z"},"bugs":{"url":"https://github.com/st0rm-bless3d/a11yscan/issues"},"author":{"name":"althor.dev"},"license":"MIT","homepage":"https://a11yscan.althor.dev","keywords":["accessibility","a11y","wcag","axe-core","playwright","cli","github-action"],"repository":{"type":"git","url":"git+https://github.com/st0rm-bless3d/a11yscan.git"},"description":"Open-source WCAG accessibility scanner CLI and GitHub Action (headless Chromium + axe-core). Scans URLs on your own machine's network, prioritized report by impact, optional LLM fix hints.","maintainers":[{"name":"althor","email":"eyeoftheworld44@gmail.com"}],"readme":"# a11yscan\n\nOpen-source WCAG accessibility scanner CLI and GitHub Action. Runs headless\nChromium + [axe-core](https://github.com/dequelabs/axe-core) against one or\nmore URLs on your own machine's network and prints a prioritized report,\ngrouped by impact, with WCAG success-criterion tags, CSS selectors, and\naxe's help text (or an optional LLM-generated plain-English fix hint).\n\n> Want continuous monitoring across all your sites — scheduled scans,\n> history, diffs, alerts? Join the waitlist: `https://a11yscan.althor.dev`\n\n## Install\n\nPublished on npm as `@althorlabs/a11yscan`. The scope is not decoration: the\nbare name `a11yscan` on npm belongs to an unrelated project, so an unscoped\ncommand would run someone else's package. Every command below is verified\nagainst the published artifact from a container that had never run this tool.\n\nRun without installing:\n\n```bash\nnpx @althorlabs/a11yscan https://example.com\n```\n\nInstall as a dev dependency:\n\n```bash\nnpm install --save-dev @althorlabs/a11yscan\nnpx a11yscan https://example.com\n```\n\nThe first run downloads a Chromium build (~115 MB) and caches it. On an image\nwithout Chromium's system libraries, the CLI prints the exact root command to\nrun first; it will not install OS packages on your behalf.\n\nInstalling straight from GitHub also works and is the way to pin a full commit\nSHA, which is the strongest guarantee because a tag can be moved:\n\n```bash\nnpx github:st0rm-bless3d/a11yscan#v0.1.3 https://example.com\nnpx github:st0rm-bless3d/a11yscan#<full-commit-sha> https://example.com\n```\n\nFrom a clone:\n\n```bash\ngit clone https://github.com/st0rm-bless3d/a11yscan.git\ncd a11yscan\nnpm install && npm run build\nnode dist/cli.js https://example.com\n```\n\n## What the first run does\n\na11yscan drives a real Chromium through Playwright, and installing the package\ndoes not put a browser on disk. So the first scan on a new machine downloads\none: about 150MB, once. a11yscan prints a notice on stderr before it starts and\nthe progress bar comes from Playwright.\n\nThe browser is cached per user, not per project:\n\n| Platform | Cache location |\n|---|---|\n| Linux | `~/.cache/ms-playwright` |\n| macOS | `~/Library/Caches/ms-playwright` |\n| Windows | `%USERPROFILE%\\AppData\\Local\\ms-playwright` |\n| Any (override) | `$PLAYWRIGHT_BROWSERS_PATH` |\n\nLater runs start in a couple of seconds and download nothing. The download is\ndone by the Playwright that ships inside a11yscan, so the browser revision\nalways matches the one it expects. A globally resolved `npx playwright install`\ncan fetch a different revision, which is why using the official\n`mcr.microsoft.com/playwright` image was not enough on its own — its\npreinstalled browsers are a different build.\n\n### If you would rather control it yourself\n\nInstall Chromium ahead of time and nothing is downloaded during a scan:\n\n```bash\nnpm install --save-dev github:st0rm-bless3d/a11yscan#v0.1.3\n./node_modules/.bin/playwright install --with-deps chromium\nnpx a11yscan https://example.com\n```\n\nUse `--no-auto-install` (or `A11YSCAN_AUTO_INSTALL=0`) to make a missing\nbrowser a hard failure instead of a download. The scan then exits `2` and\nprints the command to run. This is the right setting for CI that must not pull\n150MB at scan time:\n\n```bash\nnpx a11yscan https://example.com --exit-code --no-auto-install\n```\n\n### System libraries\n\nDownloading the browser is not always enough. Chromium needs OS packages\n(`libnspr4`, `libnss3`, `libdbus-1-3` and friends) that a bare `node:22` image\ndoes not ship. a11yscan will not install OS packages — that needs root and runs\nyour package manager — so it reports that case separately and tells you what to\nrun:\n\n```bash\n./node_modules/.bin/playwright install --with-deps chromium   # browser + packages\n./node_modules/.bin/playwright install-deps chromium          # packages only\n```\n\nOrdinary developer machines (macOS, a desktop Linux install) and the official\n`mcr.microsoft.com/playwright` images already have these.\n\n## Usage\n\n```\na11yscan <url> [<url2> ...] [options]\n```\n\n| Option | Description |\n|---|---|\n| `--json` | Machine-readable JSON output instead of a text report. |\n| `--min-impact <level>` | Only report/count violations at or above this impact: `minor \\| moderate \\| serious \\| critical`. Default: `minor` (report everything). |\n| `--exit-code` | Exit with code `1` if any violation at/above `--min-impact` was found. Without this flag the process always exits `0` on a clean scan run. |\n| `--fix-hints` | Ask an OpenAI-compatible LLM for a short plain-English fix per violation. Falls back to axe's own help text if unset or unreachable — never fails the run. |\n| `--no-auto-install` | Do not download Chromium if it is missing. The scan fails with exit code `2` and prints the command to run. Same as `A11YSCAN_AUTO_INSTALL=0`. |\n| `--auto-install` | Force the first-run download back on, overriding `A11YSCAN_AUTO_INSTALL=0` from the environment. |\n| `-h, --help` | Show usage. |\n\n### Exit codes\n\n- **0** — every URL scanned successfully, and either `--exit-code` was not\n  passed, or no violation met `--min-impact`.\n- **1** — `--exit-code` was passed AND at least one scanned URL had a\n  violation at or above `--min-impact`.\n- **2** — a usage error (bad flags, no URL given), or at least one URL could\n  not be scanned at all (invalid URL, navigation failure, timeout, or a browser\n  that could not be installed or started). This is checked regardless of\n  `--exit-code` — a failed scan is never reported as \"clean.\"\n\n### Examples\n\nThese use the short `npx a11yscan` form, which works once the package is\ninstalled in the project (see \"Install\"). Without installing, use the full\n`npx github:st0rm-bless3d/a11yscan#v0.1.3 ...` form.\n\nHuman-readable report, everything shown, always exits 0:\n\n```bash\nnpx a11yscan https://example.com\n```\n\nCI-style gate: fail the build only on serious/critical findings:\n\n```bash\nnpx a11yscan https://example.com --min-impact serious --exit-code\n```\n\nJSON output for piping into another tool:\n\n```bash\nnpx a11yscan https://example.com --json > report.json\n```\n\nMultiple URLs in one run, with LLM fix hints:\n\n```bash\nnpx a11yscan https://example.com/ https://example.com/checkout --fix-hints\n```\n\n### `--fix-hints` configuration\n\nSet these to enable LLM-generated fix suggestions; any OpenAI-compatible\n`/v1/chat/completions` endpoint works (LiteLLM, a local Ollama gateway,\nOpenAI itself, etc.):\n\n```bash\nexport A11YSCAN_LLM_URL=\"https://your-gateway.example.com\"   # no trailing /v1/...\nexport A11YSCAN_LLM_KEY=\"sk-...\"\nexport A11YSCAN_LLM_MODEL=\"gpt-4o-mini\"\n```\n\nIf these are unset, `--fix-hints` makes no network call at all and every\nviolation's fix text is axe-core's own `help` string. If the endpoint is set\nbut unreachable, times out, or is rejected by the SSRF guard (see\n\"Security\" below), the same axe fallback is used per-violation — a bad or\nmisconfigured LLM endpoint never fails a scan.\n\n## GitHub Action\n\nPin a released tag, as shown. For the strongest guarantee pin the full commit\nSHA (`st0rm-bless3d/a11yscan@<sha>`) — a tag can be moved, a SHA cannot. Do\nnot reference the default branch.\n\n```yaml\nname: accessibility\non: [pull_request]\njobs:\n  a11yscan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: st0rm-bless3d/a11yscan@v0.1.3\n        with:\n          url: https://staging.example.com\n          min-impact: serious\n```\n\nWith fix hints (requires the LLM env vars as workflow secrets):\n\n```yaml\n      - uses: st0rm-bless3d/a11yscan@v0.1.3\n        with:\n          url: https://staging.example.com\n          min-impact: serious\n          fix-hints: \"true\"\n        env:\n          A11YSCAN_LLM_URL: ${{ secrets.A11YSCAN_LLM_URL }}\n          A11YSCAN_LLM_KEY: ${{ secrets.A11YSCAN_LLM_KEY }}\n          A11YSCAN_LLM_MODEL: gpt-4o-mini\n```\n\nThe action installs Chromium via Playwright, runs the CLI with `--exit-code`\nalways set, and fails the job when `min-impact`'s threshold is met or a scan\nerror occurs.\n\n## Security\n\n**This tool runs with your own machine's network access — same trust model\nas any local scanner (pa11y, axe-cli) run in your own CI.** Playwright\nnavigating to the URL you pass on the command line is this tool's normal,\nintended job. There is no SSRF guard on that navigation — blocking it would\ndefeat the point of a local scanner (you're allowed to scan your own\ninternal staging server, localhost dev server, or anything else on your\nnetwork).\n\nA separate SSRF guard (ported from the hosted a11yscan web-service's\n`ssrf.ts` + `pinnedfetch.ts`) DOES apply to the CLI's own internal HTTP\ncalls — today, only the optional `--fix-hints` request to\n`A11YSCAN_LLM_URL`. That guard resolves the configured hostname, rejects\nprivate/link-local/loopback/reserved ranges (including the\n`169.254.169.254` cloud metadata address), and pins the TCP connection to\nthe validated IP to close the DNS-rebind window between the check and the\nrequest. One consequence worth knowing: if you run your LLM gateway on\n`127.0.0.1` or `localhost`, `--fix-hints` will reject it by design and\nsilently fall back to axe's help text. Point `A11YSCAN_LLM_URL` at a LAN\nhostname or IP (or `host.docker.internal` from inside a container) instead.\n\nThis is defense in depth, not a response to any live incident: today\n`A11YSCAN_LLM_URL` is a value you set yourself. It stays anyway because a\nmistyped or compromised config value should not be able to make this\nprocess silently reach internal network services.\n\n**The first-run browser install spawns a child process**, so it is scoped\nnarrowly. The interpreter is the running Node binary (`process.execPath`); the\nscript is the Playwright CLI resolved by absolute path out of a11yscan's own\n`node_modules`, and is refused if it does not sit inside that package\ndirectory; the arguments are the fixed literal list `[\"install\", \"chromium\"]`;\nand it runs with `shell: false`, so nothing is word-split or glob-expanded. No\nvalue you pass on the command line — URL, flag, or environment variable —\nreaches that argument list, so a scan target cannot influence what is executed.\nIt installs browsers only, never OS packages: `--with-deps` needs root and runs\nyour package manager, which is a much larger action than a scan should take on\nits own. Use `--no-auto-install` to disable the child process entirely.\n\n**No compliance claims.** This tool detects and reports; it does not\ncertify. You will not see the words \"compliant,\" \"certified,\" or\n\"guaranteed\" anywhere in this codebase, its CLI output, or its LLM prompts\n— only \"detected N violations\" / \"no violations detected by this scan\"\nframing. (The FTC finalized a $1M order against accessiBe in 2025 for\n\"fully compliant\" marketing language; this constraint is not optional.)\nGenerated fix-hint text is filtered by a banned-phrase guard as a second\nlayer in case the LLM ignores its system prompt.\n\n## What this is not\n\nThis is a detection tool, not a certification. Automated scanners\n(axe-core included) catch a meaningful subset of WCAG failures — reliably\nthings like missing alt text, contrast ratios, and missing form labels —\nbut cannot catch everything a manual audit with real assistive technology\nwould (task-flow usability, meaning of alt text, keyboard-trap edge cases\noutside what axe checks, etc.). Treat a clean report as \"no violations\ndetected by this scan,\" not as a guarantee of accessibility.\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm test\nnpm run build\n```\n\nTests use `node --test` + `tsx`, matching the reused scanner codebase's own\ntest runner. No test launches a real browser or makes a live network call —\naxe-core results, exit-code logic, impact filtering, and the SSRF guard are\nall exercised with fixtures/mocked resolvers.\n\n## License\n\nMIT © althor.dev — see [LICENSE](./LICENSE).\n","readmeFilename":"README.md"}