Tamp.AxeCore 0.1.2

Prefix Reserved
dotnet add package Tamp.AxeCore --version 0.1.2
                    
NuGet\Install-Package Tamp.AxeCore -Version 0.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tamp.AxeCore" Version="0.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tamp.AxeCore" Version="0.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Tamp.AxeCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tamp.AxeCore --version 0.1.2
                    
#r "nuget: Tamp.AxeCore, 0.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Tamp.AxeCore@0.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tamp.AxeCore&version=0.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Tamp.AxeCore&version=0.1.2
                    
Install as a Cake Tool

Tamp.AxeCore

Tamp CommandPlan wrappers for standalone web accessibility scanning — @axe-core/cli for the scan itself and axe-sarif-converter for the JSON → SARIF emission that feeds the Tamp security pipeline.

Package Status
Tamp.AxeCore 0.1.0 (initial)

Install

dotnet add package Tamp.AxeCore

Multi-targets net8 / net9 / net10. The wrapper is .NET; it shells out to two npm-distributed CLIs (axe and axe-sarif-converter) that live in your project's node_modules (or a global install). See Binary resolution below for the resolution order the wrapper uses.

When to use this — and when NOT to

Use Tamp.AxeCore when you want to scan a deployed URL for accessibility violations from your build pipeline — typical for SaaS / public-facing apps where the security target probes a staging / preview environment after deploy.

Don't use Tamp.AxeCore if you already have Playwright tests. @axe-core/playwright is a JS-side library that runs AxeBuilder({ page }).analyze() from inside your existing test files — install via Tamp.Npm and the findings flow through your test results. No .NET wrapper layer needed (and no separate SARIF conversion step).

Why two verbs

Unlike ESLint or Trivy, @axe-core/cli does not emit SARIF natively — it produces axe's own JSON shape. The Tamp security chain consumes SARIF, so a separate conversion step is needed. Tamp.AxeCore ships both verbs:

  • AxeCore.Scan(...) — wraps @axe-core/cli, produces raw axe JSON.
  • AxeCore.ConvertToSarif(...) — wraps axe-sarif-converter, converts that JSON to SARIF 2.1.0.

Both axe and axe-sarif-converter are separate npm packages — install both in your project's devDependencies:

pnpm add -D @axe-core/cli axe-sarif-converter
# or
npm install -D @axe-core/cli axe-sarif-converter

Quick start — skippable security-pipeline target

using Tamp;
using Tamp.AxeCore;

[Parameter] readonly string StagingUrl = "https://staging.example.com";
AbsolutePath WebRoot => RootDirectory / "web";
AbsolutePath SecurityArtifacts => RootDirectory / "artifacts" / "security";

Target SecurityScanAxeCore => _ => _
    .Description("Standalone a11y scan against the deployed staging URL.")
    .Executes(() =>
    {
        if (!AxeCoreBinaryResolver.IsAvailable(WebRoot))
        {
            Log.Info("[security] AxeCore skipped — toolchain not installed at {Dir}", WebRoot);
            return;
        }

        SecurityArtifacts.CreateDirectory();
        var json  = SecurityArtifacts / "axe.json";
        var sarif = SecurityArtifacts / "axe.sarif";

        var scanPlan = AxeCore.Scan(s => s
            .SetWorkingDirectory(WebRoot)
            .AddUrl(StagingUrl)
            .SetOutputFile(json)
            .AddTag("wcag2a").AddTag("wcag2aa").AddTag("wcag21aa").AddTag("best-practice")
            .SetBrowser("chromium")
            .SetNoSandbox()          // required for Docker/CI containers
            .SetTimeoutSeconds(60)
            .SetLoadDelayMs(2000));

        var scanExit = ProcessRunner.Execute(scanPlan);
        // axe exits 1 on violations when --exit is set; treat 0 and 1 as successful scan.
        if (scanExit > 1) throw new InvalidOperationException($"axe-core failed with exit {scanExit}.");

        var sarifPlan = AxeCore.ConvertToSarif(s => s
            .SetWorkingDirectory(WebRoot)
            .SetInputFile(json)
            .SetOutputFile(sarif));
        ProcessRunner.Execute(sarifPlan);

        // 0.1.1+ — stamp `properties.tags: ["accessibility"]` on every result so the
        // downstream SARIF consumer can route by category without scanner-name heuristics.
        AxeCoreSarif.AnnotateResults(sarif, "accessibility");

        // sarif is now ready for /ingest/findings (tamp-ingest-v1).
    });

Verb surface (v1)

AxeCore.Scan — axe <urls...> --save <json> [flags]

Setting CLI flag Required
AddUrl(url) positional (repeatable) ≥1
SetOutputFile(json) --save <path> yes
AddRule(id) (repeatable) --rules <id,id,...> no
AddTag(tag) (repeatable) --tags <tag,tag,...> no
AddInclude(selector) --include <selector> (repeatable) no
AddExclude(selector) --exclude <selector> (repeatable) no
SetBrowser(name) --browser <name> no
SetChromiumBinary(path) --chromium-binary <path> no
SetExit(true) (default) --exit —
SetTimeoutSeconds(n) --timeout <n> no
SetLoadDelayMs(ms) --load-delay <ms> no
SetNoSandbox(true) --no-sandbox no
SetNoReporter(true) --no-reporter no

Common WCAG tags: wcag2a, wcag2aa, wcag21aa, wcag22aa, best-practice. Combine to scope the rule set.

AxeCore.ConvertToSarif — axe-sarif-converter <input.json> <output.sarif>

Setting Position Required
SetInputFile(json) positional 1 yes
SetOutputFile(sarif) positional 2 yes

Pure conversion — no flags beyond the positional input/output. The output SARIF 2.1.0 plugs straight into the tamp-ingest-v1 /ingest/findings contract.

Source-side category tagging (0.1.1+)

@axe-core/cli + axe-sarif-converter produce SARIF 2.1.0 but don't stamp a category property on each result. Downstream sinks consuming SARIF from multiple scanners end up doing scanner-name string-matching to route findings into severity / sub-category buckets. AxeCoreSarif.AnnotateResults injects properties.tags on every result after conversion so consumers can route by tag instead — matches what Trivy emits natively.

AxeCoreSarif.AnnotateResults(sarifPath, "accessibility");
// every runs[*].results[*].properties.tags now includes "accessibility"
  • Idempotent. Re-running with the same tag is a no-op; existing tags preserved; case-sensitive dedup per SARIF spec.
  • Preserves other properties untouched (severity, custom fields, etc.).
  • Defensive against malformed input — if properties.tags exists but isn't a JSON array, the annotator replaces it with a fresh array rather than throwing.
  • No new dependency surface — uses System.Text.Json.Nodes.JsonNode from the BCL.
  • Throws ArgumentNullException / FileNotFoundException / InvalidDataException on bad input.

Typical tags: "accessibility" (matches the tamp-ingest-v1 SubCategory vocabulary), "wcag2aa", "axe-core" (scanner name), or domain-specific labels you want downstream sinks to route by.

Binary resolution

Neither axe nor axe-sarif-converter are .NET tools — both ship as npm packages. The wrapper resolves them at Scan(...) / ConvertToSarif(...) time in priority order:

  1. Project-local — {WorkingDirectory}/node_modules/.bin/<binary> (or <binary>.cmd on Windows). Preferred because the project's exact pinned versions run.
  2. pnpm exec — pnpm exec <binary> -- .... Used when pnpm-lock.yaml or pnpm-workspace.yaml is present AND pnpm is on PATH.
  3. npm exec — npm exec <binary> -- .... Used when package-lock.json is present AND npm is on PATH.
  4. Global — <binary> on PATH.
  5. Not found — ToCommandPlan() throws InvalidOperationException with an actionable message.

Pre-flighting (skip when not installed)

AxeCoreBinaryResolver.IsAvailable(workingDirectory) returns true only when both axe and axe-sarif-converter resolve. Use it to skip the security-pipeline target cleanly when the a11y toolchain isn't installed:

if (!AxeCoreBinaryResolver.IsAvailable(WebRoot)) return;

For finer-grained checks, call TryResolveAxe(...) / TryResolveSarifConverter(...) directly.

Overriding resolution explicitly

var binary = new AxeCoreBinaryResolution
{
    Executable = "/opt/ci/axe/4.10.0/axe",
    Source = AxeCoreResolutionSource.Explicit,
};

var plan = AxeCore.Scan(s => s
    .SetBinary(binary)
    .AddUrl("https://staging.example.com")
    .SetOutputFile("axe.json"));

Exit-code semantics

@axe-core/cli follows the linter convention; mirrors Tamp.OpenGrep / Tamp.Eslint.V9:

Exit Meaning Treat as
0 Clean run, no findings success
1 Violations reported (when --exit is set, which is the default) success (findings are still a successful scan)
2+ Tool / config / browser error failure

A security-pipeline target should only throw on exit > 1.

Browser runtime

@axe-core/cli spawns a headless browser (Chromium by default). In CI containers, you'll typically need:

# Either pre-install Chromium via Playwright (recommended):
npx playwright install chromium

# Or rely on @axe-core/cli's puppeteer-bundled Chromium and pass --no-sandbox
# (set via SetNoSandbox(true) in the wrapper).

If you have a specific Chromium binary path (e.g. system-installed via the runner image), pass it via SetChromiumBinary(path).

Settings authoring — fluent or object-init

Both styles produce identical CommandPlans; fluent is canonical in docs.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.2 94 9/27/2026
0.1.1 148 5/26/2026
0.1.0 127 5/25/2026