From 49c3e2c9057d132253a4d4db4d2b77ce2ef414e6 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sun, 7 Jun 2026 09:03:28 -0700 Subject: [PATCH 1/3] feat: add ignore_https_errors input for self-signed certificates Opt-in input that sets Playwright's ignoreHTTPSErrors on the browser context so the scanner can run against staging hosts with self-signed or invalid certs. Default off; behavior unchanged unless enabled. Closes #150 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/find/README.md | 4 ++ .github/actions/find/action.yml | 4 ++ .github/actions/find/src/findForUrl.ts | 2 + .github/actions/find/src/index.ts | 14 +++- .github/actions/find/tests/findForUrl.test.ts | 69 +++++++++++++++---- README.md | 2 + action.yml | 5 ++ 7 files changed, 84 insertions(+), 16 deletions(-) diff --git a/.github/actions/find/README.md b/.github/actions/find/README.md index b8bca81c..7549e98b 100644 --- a/.github/actions/find/README.md +++ b/.github/actions/find/README.md @@ -31,6 +31,10 @@ configuration option. [`colorScheme`](https://playwright.dev/docs/api/class-browser#browser-new-context-option-color-scheme) configuration option. +#### `ignore_https_errors` + +**Optional** Bool - whether to ignore HTTPS errors when scanning URLs with self-signed certificates. + #### `include_screenshots` **Optional** Bool - whether to capture screenshots of scanned pages and include links to them in the issue diff --git a/.github/actions/find/action.yml b/.github/actions/find/action.yml index fb53d901..d93bc02b 100644 --- a/.github/actions/find/action.yml +++ b/.github/actions/find/action.yml @@ -25,6 +25,10 @@ inputs: color_scheme: description: 'Playwright colorScheme setting: https://playwright.dev/docs/api/class-browser#browser-new-context-option-color-scheme' required: false + ignore_https_errors: + description: 'Whether to ignore HTTPS errors when scanning URLs with self-signed certificates' + required: false + default: 'false' outputs: findings_file: diff --git a/.github/actions/find/src/findForUrl.ts b/.github/actions/find/src/findForUrl.ts index d9f1ea87..a8928417 100644 --- a/.github/actions/find/src/findForUrl.ts +++ b/.github/actions/find/src/findForUrl.ts @@ -13,6 +13,7 @@ export async function findForUrl( includeScreenshots: boolean = false, reducedMotion?: ReducedMotionPreference, colorScheme?: ColorSchemePreference, + ignoreHttpsErrors: boolean = false, ): Promise { const {url, excludeSelectors} = urlConfig const browser = await playwright.chromium.launch({ @@ -23,6 +24,7 @@ export async function findForUrl( ...(authContext?.toPlaywrightBrowserContextOptions() ?? {}), ...(reducedMotion ? {reducedMotion} : {}), ...(colorScheme ? {colorScheme} : {}), + ...(ignoreHttpsErrors ? {ignoreHTTPSErrors: true} : {}), } const context = await browser.newContext(contextOptions) const page = await context.newPage() diff --git a/.github/actions/find/src/index.ts b/.github/actions/find/src/index.ts index 675dff6f..0b647d57 100644 --- a/.github/actions/find/src/index.ts +++ b/.github/actions/find/src/index.ts @@ -11,6 +11,7 @@ export default async function () { const urls = loadUrls({urlConfigs}) const reducedMotion = loadReducedMotion() const colorScheme = loadColorScheme() + const ignoreHttpsErrors = loadIgnoreHttpsErrors() const actualUrls = urlConfigs || urls || [] @@ -22,7 +23,14 @@ export default async function () { for (const urlConfig of actualUrls) { const {url} = urlConfig core.info(`Preparing to scan ${url}`) - const findingsForUrl = await findForUrl(urlConfig, authContext, includeScreenshots, reducedMotion, colorScheme) + const findingsForUrl = await findForUrl( + urlConfig, + authContext, + includeScreenshots, + reducedMotion, + colorScheme, + ignoreHttpsErrors, + ) if (findingsForUrl.length === 0) { core.info(`No accessibility gaps were found on ${url}`) continue @@ -101,3 +109,7 @@ function loadColorScheme() { return colorSchemeInput as ColorSchemePreference } + +function loadIgnoreHttpsErrors() { + return core.getInput('ignore_https_errors', {required: false}).toLowerCase() === 'true' +} diff --git a/.github/actions/find/tests/findForUrl.test.ts b/.github/actions/find/tests/findForUrl.test.ts index 85299c5c..dfeb0039 100644 --- a/.github/actions/find/tests/findForUrl.test.ts +++ b/.github/actions/find/tests/findForUrl.test.ts @@ -7,21 +7,29 @@ import * as pluginManager from '../src/pluginManager/index.js' import type {Plugin} from '../src/pluginManager/types.js' import {clearCache} from '../src/scansContextProvider.js' +const playwrightMocks = vi.hoisted(() => { + const page = { + pageUrl: '', + goto: vi.fn(), + url: vi.fn(), + } + const context = { + newPage: vi.fn(() => page), + close: vi.fn(), + } + const browser = { + newContext: vi.fn(() => context), + close: vi.fn(), + } + + return {browser, context, page} +}) + vi.mock('@actions/core', {spy: true}) vi.mock('playwright', () => ({ default: { chromium: { - launch: () => ({ - newContext: () => ({ - newPage: () => ({ - pageUrl: '', - goto: () => {}, - url: () => {}, - }), - close: () => {}, - }), - close: () => {}, - }), + launch: vi.fn(() => playwrightMocks.browser), }, }, })) @@ -49,7 +57,7 @@ describe('findForUrl', () => { async function axeOnlyTest() { clearAll() - await findForUrl('test.com') + await findForUrl({url: 'test.com'}) expect(AxeBuilder.prototype.analyze).toHaveBeenCalledTimes(1) expect(pluginManager.loadPlugins).toHaveBeenCalledTimes(0) expect(pluginManager.invokePlugin).toHaveBeenCalledTimes(0) @@ -80,7 +88,7 @@ describe('findForUrl', () => { actionInput = JSON.stringify(['axe', 'custom-scan-1']) clearAll() - await findForUrl('test.com') + await findForUrl({url: 'test.com'}) expect(AxeBuilder.prototype.analyze).toHaveBeenCalledTimes(1) expect(pluginManager.loadPlugins).toHaveBeenCalledTimes(1) expect(pluginManager.invokePlugin).toHaveBeenCalledTimes(1) @@ -97,7 +105,7 @@ describe('findForUrl', () => { actionInput = JSON.stringify(['custom-scan-1', 'custom-scan-2']) clearAll() - await findForUrl('test.com') + await findForUrl({url: 'test.com'}) expect(AxeBuilder.prototype.analyze).toHaveBeenCalledTimes(0) expect(pluginManager.loadPlugins).toHaveBeenCalledTimes(1) expect(pluginManager.invokePlugin).toHaveBeenCalledTimes(2) @@ -112,9 +120,40 @@ describe('findForUrl', () => { actionInput = JSON.stringify(['custom-scan-1']) clearAll() - await findForUrl('test.com') + await findForUrl({url: 'test.com'}) expect(loadedPlugins[0].default).toHaveBeenCalledTimes(1) expect(loadedPlugins[1].default).toHaveBeenCalledTimes(0) }) }) + + describe('when ignoreHttpsErrors is enabled', () => { + it('forwards ignoreHTTPSErrors to the browser context', async () => { + actionInput = '' + clearAll() + + await findForUrl({url: 'test.com'}, undefined, false, undefined, undefined, true) + + expect(playwrightMocks.browser.newContext).toHaveBeenCalledWith({ignoreHTTPSErrors: true}) + }) + }) + + describe('when ignoreHttpsErrors is not enabled', () => { + it('does not forward ignoreHTTPSErrors by default', async () => { + actionInput = '' + clearAll() + + await findForUrl({url: 'test.com'}) + + expect(playwrightMocks.browser.newContext).toHaveBeenCalledWith({}) + }) + + it('does not forward ignoreHTTPSErrors when explicitly disabled', async () => { + actionInput = '' + clearAll() + + await findForUrl({url: 'test.com'}, undefined, false, undefined, undefined, false) + + expect(playwrightMocks.browser.newContext).toHaveBeenCalledWith({}) + }) + }) }) diff --git a/README.md b/README.md index 88b68c11..133eeb31 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ jobs: # open_grouped_issues: false # Optional: Set to true to open an issue grouping individual issues per violation # reduced_motion: no-preference # Optional: Playwright reduced motion configuration option # color_scheme: light # Optional: Playwright color scheme configuration option + # ignore_https_errors: false # Optional: Set to true to ignore HTTPS errors when scanning URLs with self-signed certificates # scans: '["axe","reflow-scan"]' # Optional: An array of scans (or plugins) to be performed. If not provided, only Axe will be performed. # url_configs: '[{"url":"https://example.com","excludeSelectors":["iframe","#widget"]}]' # Optional: Per-URL config with CSS selectors to exclude from the Axe scan. When provided, takes precedence over 'urls'. ``` @@ -130,6 +131,7 @@ Trigger the workflow manually or automatically based on your configuration. The | `open_grouped_issues` | No | Whether to create a tracking issue which groups filed issues together by violation type. Default: `false` | `true` | | `reduced_motion` | No | Playwright `reducedMotion` setting for scan contexts. Allowed values: `reduce`, `no-preference` | `reduce` | | `color_scheme` | No | Playwright `colorScheme` setting for scan contexts. Allowed values: `light`, `dark`, `no-preference` | `dark` | +| `ignore_https_errors` | No | Whether to ignore HTTPS errors when scanning URLs with self-signed certificates. Default: `false` | `true` | | `scans` | No | An array of scans (or plugins) to be performed. If not provided, only Axe will be performed. | `'["axe", "reflow-scan", ...other plugins]'` | | `url_configs` | No | A stringified JSON array of URL config objects. Each object must have a `url` field and may have an optional `excludeSelectors` field (array of CSS selectors to exclude from the Axe scan for that URL). When provided, takes precedence over the `urls` input. | `'[{"url":"https://example.com","excludeSelectors":["iframe","#widget"]}]'` | diff --git a/action.yml b/action.yml index b86a45e5..44343444 100644 --- a/action.yml +++ b/action.yml @@ -51,6 +51,10 @@ inputs: color_scheme: description: 'Playwright colorScheme setting: https://playwright.dev/docs/api/class-browser#browser-new-context-option-color-scheme' required: false + ignore_https_errors: + description: 'Whether to ignore HTTPS errors when scanning URLs with self-signed certificates' + required: false + default: 'false' scans: description: 'Stringified JSON array of scans to perform. If not provided, only Axe will be performed' required: false @@ -117,6 +121,7 @@ runs: include_screenshots: ${{ inputs.include_screenshots }} reduced_motion: ${{ inputs.reduced_motion }} color_scheme: ${{ inputs.color_scheme }} + ignore_https_errors: ${{ inputs.ignore_https_errors }} scans: ${{ inputs.scans }} - name: File id: file From d3018b063d12fd099e72fda941c52a4d21042367 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Fri, 30 Oct 2026 00:20:41 -0700 Subject: [PATCH 2/3] fix: forward ignore_https_errors to auth and update find index test The built-in login flow ran in a browser context without ignoreHTTPSErrors, so login failed on self-signed hosts before Find could run. Auth now takes the same input and the root action passes it through. The find index test expects the new sixth argument. --- .github/actions/auth/README.md | 4 ++++ .github/actions/auth/action.yml | 4 ++++ .github/actions/auth/src/index.ts | 2 ++ .github/actions/find/tests/index.test.ts | 2 +- action.yml | 1 + 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/actions/auth/README.md b/.github/actions/auth/README.md index b857e0af..a9b96bea 100644 --- a/.github/actions/auth/README.md +++ b/.github/actions/auth/README.md @@ -21,6 +21,10 @@ Authenticate with Playwright and save session state. Supports HTTP Basic and for > [!IMPORTANT] > Don’t put passwords in your workflow as plain text; instead reference a [repository secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository). +#### `ignore_https_errors` + +**Optional** Bool - whether to ignore HTTPS errors when logging in to hosts with self-signed certificates. + ### Outputs #### `auth_context` diff --git a/.github/actions/auth/action.yml b/.github/actions/auth/action.yml index c68b3331..fbf913cb 100644 --- a/.github/actions/auth/action.yml +++ b/.github/actions/auth/action.yml @@ -11,6 +11,10 @@ inputs: password: description: "Password" required: true + ignore_https_errors: + description: "Whether to ignore HTTPS errors when logging in to hosts with self-signed certificates" + required: false + default: "false" outputs: auth_context: diff --git a/.github/actions/auth/src/index.ts b/.github/actions/auth/src/index.ts index 68ff4e82..01f08eeb 100644 --- a/.github/actions/auth/src/index.ts +++ b/.github/actions/auth/src/index.ts @@ -15,6 +15,7 @@ export default async function () { const username = core.getInput('username', {required: true}) const password = core.getInput('password', {required: true}) core.setSecret(password) + const ignoreHttpsErrors = core.getInput('ignore_https_errors', {required: false}).toLowerCase() === 'true' // Launch a headless browser browser = await playwright.chromium.launch({ @@ -27,6 +28,7 @@ export default async function () { username, password, }, + ...(ignoreHttpsErrors ? {ignoreHTTPSErrors: true} : {}), }) page = await context.newPage() diff --git a/.github/actions/find/tests/index.test.ts b/.github/actions/find/tests/index.test.ts index f5d760fc..13cf72f8 100644 --- a/.github/actions/find/tests/index.test.ts +++ b/.github/actions/find/tests/index.test.ts @@ -43,7 +43,7 @@ describe('url_configs', () => { await find() - expect(mocks.findForUrl).toHaveBeenCalledWith(urlConfig, expect.anything(), false, undefined, undefined) + expect(mocks.findForUrl).toHaveBeenCalledWith(urlConfig, expect.anything(), false, undefined, undefined, false) }) it('rejects waitForSelectors values that are not arrays of strings', async () => { diff --git a/action.yml b/action.yml index 525d0213..8f305a3a 100644 --- a/action.yml +++ b/action.yml @@ -127,6 +127,7 @@ runs: login_url: ${{ inputs.login_url }} username: ${{ inputs.username }} password: ${{ inputs.password }} + ignore_https_errors: ${{ inputs.ignore_https_errors }} - name: Find id: find uses: ./../../_actions/github/accessibility-scanner/current/.github/actions/find From babe9022cef822932a882e9b780bf34210350d71 Mon Sep 17 00:00:00 2001 From: Abdul Ahmad Date: Fri, 30 Oct 2026 14:59:46 -0400 Subject: [PATCH 3/3] Update README with warning on HTTPS errors Added a warning about disabling TLS certificate validation when ignoring HTTPS errors in the README. --- .github/actions/auth/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/actions/auth/README.md b/.github/actions/auth/README.md index a9b96bea..a6fb9484 100644 --- a/.github/actions/auth/README.md +++ b/.github/actions/auth/README.md @@ -25,6 +25,11 @@ Authenticate with Playwright and save session state. Supports HTTP Basic and for **Optional** Bool - whether to ignore HTTPS errors when logging in to hosts with self-signed certificates. +[!WARNING] +Enabling this disables TLS certificate validation for the authentication step itself. If an attacker can intercept traffic, they can present an untrusted certificate and capture submitted credentials (including HTTP Basic auth and form passwords) and resulting session cookies/storage. + +Use this only in trusted test/staging environments. For shared workflows, prefer a separate explicit opt-in for auth flows (for example, `auth_ignore_https_errors`) so certificate checks are not unintentionally disabled during credentialed requests. + ### Outputs #### `auth_context`