Skip to content

Commit f7f1412

Browse files
feat(DEVA11Y-657): composite GitHub Action (v1 scan+report) (#1)
* feat(DEVA11Y-463): composite GitHub Action (v1 scan+report) Scaffold the public browserstack-accessibility-devtools-action repo per ADR-0035 Appendix A (GitHub App + Service Account model). - action.yml: composite action. Inputs username/access-key (required, secret), comment, check-gate, fail-on-severity, inline-suggestions, sarif, comment-mode, remediation, ai-agent (validated ^[A-Za-z0-9-]+(\[bot\])?$). Outputs result, error-count, warning-count, findings-count, comment-url, sarif-file. Hardcoded: @AccessibilityDevTools trigger, diff scope, deva11y.config.* inference, commenter write/maintain/admin permission gate. Steps implement fork-PR "diff, don't build" safety (no install/postinstall of head code; skip when OIDC/secrets restricted), pinned npx CLI (placeholder 1.3.0), Actions OIDC mint (aud placeholder browserstack-accessibility-devtools), and POST to ci-review-service /v1/findings with {platform,repo,prNumber,headSha,findings,scanProof,oidcToken} + X-CI-Platform header. The Action holds no GitHub write credential; the App posts server-side. - examples/browserstack-a11y.yml: reference consumer workflow (issue_comment, read-only token + id-token: write, concurrency, if-guard). - .github/workflows/release.yml: release stub (esbuild dist build guard, action.yml validation, manual org-admin Marketplace publish note). - README.md: public customer README (v1 = scan + report; agent hand-off preview off by default; homepage browserstack.com). - LICENSE (MIT), .gitignore. Placeholders pending provisioning: CLI version 1.3.0, OIDC audience and service URL (https://devtools-a11y-linter.browserstack.com), GitHub App. Draft PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(DEVA11Y-657): don't hard-fail the commenter gate under a read-only token The commenter permission pre-check called GET /collaborators/{user}/permission, which requires push access. The documented least-privilege workflow token (contents: read) cannot call it, so the request 403s, the `|| echo none` fallback yielded "none", and the action refused to scan for EVERY commenter — non-functional under its own recommended setup. Make the client-side check best-effort: fast-refuse only on a DEFINITIVE non-privileged role; on an inconclusive call (the read-only-token case) proceed and rely on the authoritative server-side fail-closed permission gate in the ci-review-service (ADR-0035 §5). The runner is the customer's and was never the security boundary. Also correct the preflight fork-safety comments: issue_comment workflows run in base-repo context with secrets available even for fork PRs, so cred-absence is not what protects fork PRs — the permission gate + "diff, don't build" are. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(DEVA11Y-657): ready action for merge — @v1 refs + PR CI gate - example workflow + README now reference @v1 (with a note to SHA-pin for production) instead of the @<commit-sha> placeholder - add .github/workflows/ci.yml: PR gate validating action.yml structure + YAML well-formedness (the repo previously had no PR checks) CLI version pin (action.yml) stays a documented release-time value — no pr-scan CLI version is published to npm yet; the release step pins the real version. GitHub App id / service URL remain provisioning placeholders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(DEVA11Y-657): pin CLI to published 1.5.0; finalize audience/base-URL comments @browserstack/accessibility-devtools 1.5.0 is published (npmjs `latest`) and includes PR-scan mode, so pin CLI_VERSION 1.3.0 -> 1.5.0. The OIDC audience (browserstack-accessibility-devtools) already matches the deployed ci-review-service verifier config, and SERVICE_URL is the real global ingress; drop the stale PLACEHOLDER comments accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(DEVA11Y-657): address @aveek-bs README review - Title -> "BrowserStack Accessibility DevTools for GitHub"; workflow name -> "BrowserStack Accessibility DevTools". - Learn-more link -> /docs/accessibility-dev-tools/features/remediate-github. - Drop the internal-sounding "BrowserStack runs no AI model…what works today" copy from the intro bullet. - Remove the Service-Account "no user license…Enterprise plan" aside. - Inline the full ready-to-copy workflow in Quick start; drop the separate examples/ link. - Agent hand-off: replace the "what works today" internal note with a generic preview disclaimer, and add the bot allow-list as a prerequisite. - Remove Privacy & security and License sections. - Support -> single https://www.browserstack.com/support link. - examples/: use @main (always latest) instead of @v1; match workflow name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(DEVA11Y-657): use CLI 1.5.0 --pr-scan contract (fixes 'unknown option --head') The action called a nonexistent `scan` subcommand with --head/--changed-files/ --output and then minted OIDC + POSTed itself. Published CLI 1.5.0 does the whole PR-scan via `--pr-scan`: resolves the base..head diff locally (git), gets linter-server to normalize + HMAC-sign, mints the Actions OIDC token, and submits to the ci-review-service (POST <ci-service-url>/v1/findings) which posts as the App. - scan step -> `--pr-scan --include '**/*' --base <sha> --head-sha <sha> --repo <owner/name> --pr-number <n> --ci-service-url https://devtools-a11y-linter.browserstack.com` (--head-sha, not --head; drop --changed-files/--output/--format — the CLI derives the diff and submits itself). fail-on-severity=none -> --non-strict. - Remove the now-redundant `oidc` and `submit` steps (the CLI does both). - `report` gates on the CLI exit code (0 pass; 1/3/4/other fail). Known 1.5.0 limits (documented inline): --pr-scan does not forward the comment/ inline/sarif/remediation options or return per-severity counts / comment URL — the ci-review-service applies defaults; those outputs need a CLI enhancement. NOTE: --ci-service-url uses the shared host, so POST /v1/findings must be routed to ci-review-service on devtools-a11y-linter.browserstack.com (currently only /github is routed) for the submit to land. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(DEVA11Y-657): automatic PR trigger, honest inputs, real outputs + severity gate Addresses review feedback (inputs must be real, not for show; guide users to the automatic detection flow; VS Code output format) and the flag audit. action.yml: - Automatic flow (primary): trigger on pull_request opened/synchronize/reopened/ ready_for_review (actor = PR author, no commenter gate). Manual @mention stays as the secondary flow (with the commenter permission pre-check). - Trim inputs to the ones that do something: username, access-key, fail-on-severity. Removed comment / check-gate / comment-mode / inline-suggestions / sarif (now always-on server defaults) and remediation / ai-agent (deferred preview). check-gate is folded into fail-on-severity (none = never fail). - Real gate + outputs: parse the CLI's `[deva11y-result]` line into error-count / warning-count / findings-count / comment-url, and fail the run per fail-on-severity (a real `warning` gate, and `error` actually fails now — previously --pr-scan always exited 0 so the check never failed). Operational errors (FUP/permission/error) always fail. Dropped the sarif-file output (SARIF is uploaded server-side; there is no local file). README + example: - Automatic-first quick start (on: pull_request + issue_comment), @main (no v1 pin). - Inputs/outputs tables reflect reality; describe what actually gets posted (sticky comment, inline comments in VS Code format, Check Run, SARIF). - Inline are review comments, not one-click suggestion blocks (findings carry how-to-fix text, not literal replacement code) — corrected the earlier claim. - Single support link; removed the internal-note remediation section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(DEVA11Y-657): re-add AI remediation hand-off via a single ai-agent input Per review: keep remediation with a generic disclaimer (rather than dropping it). - action.yml: `ai-agent` input (optional, bare name); a validate-agent step (charset ^[A-Za-z0-9-]+(\[bot\])?$, fail fast); passes `--ai-agent` to the CLI only when set. Single input — presence enables the hand-off (no separate `remediation` boolean). - README: ai-agent row + a short "AI remediation hand-off (preview)" section with the generic disclaimer (supported if your agent accepts bot/App triggers). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(DEVA11Y-657): bump pinned CLI to 1.5.1 1.5.1 ships the --pr-scan additions consumed by this action: the machine-readable [deva11y-result] stdout line (populates the error/warning/findings-count + comment-url outputs) and --ai-agent forwarding. Bumps the CLI_VERSION pin from 1.5.0 so the action's outputs and the ai-agent mention activate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(action): remove internal references from action.yml comments Public repo — drop internal-only terms from the action source: ADR-#### design references, the DEVA11Y-### ticket, and internal component/mechanism names (scanProof, ci-review-service, linter-server). Reworded the header and step comments in plain, customer-facing language; kept the product term 'Service Account' and the functional [deva11y-result] CLI stdout token (the published CLI emits it and the action must grep it verbatim). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(action): drop the (preview) label from the ai-agent feature (review) Resolves Aveek's two README review comments — remove 'preview' from the ai-agent input row and the AI remediation hand-off heading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1e67b1f commit f7f1412

7 files changed

Lines changed: 668 additions & 2 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# PR gate for the composite action. Keeps action.yml + example/workflow YAML
2+
# structurally valid on every change so a broken action can't merge.
3+
name: CI
4+
5+
on:
6+
pull_request:
7+
push:
8+
branches: [main]
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
validate:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Validate YAML is well-formed
20+
shell: bash
21+
run: |
22+
set -euo pipefail
23+
for f in action.yml examples/*.yml .github/workflows/*.yml; do
24+
python3 -c "import sys,yaml; list(yaml.safe_load_all(open('$f')))" \
25+
&& echo "ok: $f" || { echo "invalid YAML: $f"; exit 1; }
26+
done
27+
28+
- name: Validate action.yml structure
29+
shell: bash
30+
run: |
31+
set -euo pipefail
32+
test -f action.yml || { echo "action.yml missing"; exit 1; }
33+
for key in name description runs inputs outputs; do
34+
grep -qE "^${key}:" action.yml || { echo "action.yml missing '${key}:'"; exit 1; }
35+
done
36+
grep -qE "using: 'composite'|using: composite" action.yml \
37+
|| { echo "action.yml must be a composite action"; exit 1; }
38+
echo "action.yml is structurally valid."

‎.github/workflows/release.yml‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Release pipeline for the BrowserStack Accessibility DevTools Action.
2+
#
3+
# v1 is a composite action (action.yml) that shells out to bash + the published
4+
# CLI, so there is nothing to bundle yet. This pipeline is a STUB that:
5+
# - builds any bundled `dist/` if/when the action gains JS entrypoints (esbuild),
6+
# - verifies action.yml is valid,
7+
# - documents the (manual, admin-only) Marketplace publish step.
8+
#
9+
# Marketplace publishing needs a GitHub ORG ADMIN and is intentionally NOT
10+
# automated here (see the "publish" job note below).
11+
name: Release
12+
13+
on:
14+
push:
15+
tags:
16+
- 'v*'
17+
workflow_dispatch:
18+
inputs:
19+
version:
20+
description: 'Release version tag (e.g. v1.0.0)'
21+
required: true
22+
23+
permissions:
24+
contents: read
25+
26+
jobs:
27+
build:
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- uses: actions/setup-node@v4
33+
with:
34+
node-version: '20'
35+
36+
# Bundle a dist/ IF the action grows JS entrypoints (esbuild pattern).
37+
# Guarded so the stub is a no-op for the current pure-composite action.
38+
- name: Build bundled dist (esbuild)
39+
shell: bash
40+
run: |
41+
set -euo pipefail
42+
if [ -f package.json ] && jq -e '.scripts.build' package.json >/dev/null 2>&1; then
43+
npm ci
44+
# Example bundling target for a future JS action entrypoint:
45+
# npx esbuild src/index.ts --bundle --platform=node \
46+
# --target=node20 --outfile=dist/index.js
47+
npm run build
48+
echo "Bundled dist/ produced."
49+
else
50+
echo "No package.json build script — pure composite action, nothing to bundle."
51+
fi
52+
53+
- name: Validate action.yml
54+
shell: bash
55+
run: |
56+
set -euo pipefail
57+
test -f action.yml || { echo "action.yml missing"; exit 1; }
58+
# Minimal structural sanity: required top-level keys present.
59+
for key in name description runs inputs outputs; do
60+
grep -qE "^${key}:" action.yml || { echo "action.yml missing '${key}:'"; exit 1; }
61+
done
62+
echo "action.yml looks structurally valid."
63+
64+
publish:
65+
needs: build
66+
runs-on: ubuntu-latest
67+
steps:
68+
- name: Marketplace publish (manual, org-admin only)
69+
shell: bash
70+
run: |
71+
echo "GitHub Marketplace publishing is a MANUAL step and requires a GitHub org admin."
72+
echo "Steps (performed in the GitHub UI by an admin):"
73+
echo " 1. Draft a release for the pushed tag."
74+
echo " 2. Tick 'Publish this Action to the GitHub Marketplace'."
75+
echo " 3. Select category (Code quality / Utilities), accept the agreement, publish."
76+
echo "This job is a placeholder and does not publish automatically."

‎.gitignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
*.log
3+
.DS_Store
4+
.env
5+
# Bundled output is produced by the release pipeline when/if JS entrypoints land.
6+
dist/

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 BrowserStack
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎README.md‎

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,124 @@
1-
# browserstack-accessibility-devtools-action
2-
A package to support Accessibility Devtools Action
1+
# BrowserStack Accessibility DevTools for GitHub
2+
3+
Catch accessibility issues on your pull requests. BrowserStack scans the changed
4+
code on every PR and posts the findings right back on the PR — a summary comment,
5+
inline comments on the offending lines, a merge check, and a SARIF upload to the
6+
Security tab.
7+
8+
- 🔎 **Scans only what changed** in the PR (fast, even on large repos).
9+
- 💬 **Results on the PR** — a sticky summary comment and inline comments.
10+
- ✅ **Merge gate** — fail the check when accessibility issues are found (configurable).
11+
- 🔐 **Zero write credentials on your runner** — every write is posted by the
12+
BrowserStack Accessibility App; your workflow's `GITHUB_TOKEN` stays read-only.
13+
14+
Learn more: <https://www.browserstack.com/docs/accessibility-dev-tools/features/remediate-github>
15+
16+
---
17+
18+
## Prerequisites
19+
20+
1. **Install the BrowserStack Accessibility GitHub App** on your organization or
21+
repository (a one-time step, done by a repo/org admin).
22+
2. **Add a BrowserStack Service Account key** as repository or organization
23+
**Actions secrets**:
24+
- `BROWSERSTACK_USERNAME`
25+
- `BROWSERSTACK_ACCESS_KEY`
26+
27+
## Quick start
28+
29+
Add `.github/workflows/browserstack-a11y.yml`. It runs **automatically on every
30+
pull request**, and can also be re-run on demand by commenting
31+
`@AccessibilityDevTools` on the PR:
32+
33+
```yaml
34+
name: BrowserStack Accessibility DevTools
35+
36+
on:
37+
pull_request:
38+
types: [opened, synchronize, reopened, ready_for_review]
39+
issue_comment:
40+
types: [created]
41+
42+
permissions:
43+
contents: read # read the PR's changed files
44+
pull-requests: read # resolve the PR
45+
id-token: write # prove the run came from your CI (required)
46+
47+
concurrency:
48+
group: a11y-${{ github.event.pull_request.number || github.event.issue.number }}
49+
cancel-in-progress: true
50+
51+
jobs:
52+
a11y:
53+
if: >
54+
github.event_name == 'pull_request' ||
55+
(github.event.issue.pull_request &&
56+
contains(github.event.comment.body, '@AccessibilityDevTools'))
57+
runs-on: ubuntu-latest
58+
timeout-minutes: 15
59+
steps:
60+
- uses: browserstack/browserstack-accessibility-devtools-action@main
61+
with:
62+
username: ${{ secrets.BROWSERSTACK_USERNAME }}
63+
access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
64+
fail-on-severity: error
65+
```
66+
67+
That's it — open a PR and the results appear within a couple of minutes. To
68+
re-run manually, comment `@AccessibilityDevTools` on the PR.
69+
70+
## How it works
71+
72+
1. On a pull request (or an `@AccessibilityDevTools` comment), the workflow runs
73+
BrowserStack's accessibility CLI against the PR's changed files, authenticated
74+
by your Service Account key.
75+
2. Results are posted back to the PR **by the BrowserStack Accessibility App** (a
76+
branded bot), so your workflow's default token never needs write access.
77+
78+
> **Why `id-token: write`?** GitHub mints a short-lived, repo-scoped OpenID
79+
> Connect token that proves the request genuinely came from this repository's CI
80+
> run. BrowserStack verifies it before posting. It carries **no** personal
81+
> identity and grants no standing access.
82+
83+
## What gets posted
84+
85+
On every scan the App posts, automatically:
86+
87+
- a **sticky summary comment** (one per PR, updated in place across runs);
88+
- **inline comments** on the offending lines, in the same format as the
89+
BrowserStack VS Code extension: `(rule): <description>` followed by how to fix it;
90+
- a **Check Run** with the pass/fail result;
91+
- a **SARIF upload** to GitHub code scanning (Security tab).
92+
93+
## Inputs
94+
95+
| Input | Default | Description |
96+
| ------------------ | ------- | ------------------------------------------------------------------------------------------------------- |
97+
| `username` | — | **Required.** Service Account username (store as a secret). |
98+
| `access-key` | — | **Required.** Service Account access key (store as a secret). |
99+
| `fail-on-severity` | `error` | Fail the check when findings at/above this severity exist: `error`, `warning`, or `none` (never fails). |
100+
| `ai-agent` | — | Optional. Bare agent name to `@mention` for AI remediation hand-off — see below. |
101+
102+
## Outputs
103+
104+
| Output | Description |
105+
| ---------------- | ------------------------------------ |
106+
| `result` | `pass` or `fail`. |
107+
| `error-count` | Number of error-severity findings. |
108+
| `warning-count` | Number of warning-severity findings. |
109+
| `findings-count` | Total findings. |
110+
| `comment-url` | Link to the posted PR summary comment. |
111+
112+
## AI remediation hand-off
113+
114+
Set `ai-agent` to the bare name of an agent you already use (e.g. `coderabbitai`).
115+
When findings are posted, the App `@mentions` it on the comment, and your agent
116+
acts under **your** own credentials and billing.
117+
118+
> This feature will be supported if your AI agent accepts triggers from a bot/App
119+
> comment; behaviour varies by agent, and it is a silent no-op if the agent isn't
120+
> configured to accept it.
121+
122+
## Support
123+
124+
<https://www.browserstack.com/support>

0 commit comments

Comments
 (0)