Skip to content

CI integration

- run: uvx ad-doctor scan "$DC" --ldaps -u "$AD_USER" -f sarif -o ad.sarif --baseline last.json --fail-on high
  env: { AD_DOCTOR_PASSWORD: "${{ secrets.AD_PASSWORD }}" }
- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with: { sarif_file: ad.sarif }

Each SARIF rule carries the check's summary as fullDescription, its remediation and references as help.markdown, and a helpUri to its catalog page, so GitHub code-scanning alerts show the full explanation even for rules that did not fire. The JUnit format writes one test case per check (failed when it found something, skipped when accepted) and the Markdown report links each finding to its docs page; the HTML viewer links nowhere and shows the URL as text to copy.

Exit codes and --fail-on

ad-doctor exits 0 when it ran, 2 when --fail-on high|medium|low is set and a finding at or above that severity exists, and 1 on an error. Pair it with a baseline so a job fails only on regressions.

Baselines

--baseline FILE marks findings missing from an earlier report or snapshot as new, by a stable (check, object) fingerprint; --fail-on then considers only those new findings. A scheduled scan can therefore stay green until something changes.

ad-doctor scan "$DC" --ldaps -u "$AD_USER" --baseline last.json --fail-on high
ad-doctor diff 2026-10.json 2026-11.json       # or compare two saved runs directly

Accepted risks

Risks you have reviewed and chosen to keep go in a file passed with --accept. Each entry needs a check ID, a reason and an expiry; leave out objects to accept the whole check.

[[accept]]
check = "AD-KRB-003"
objects = ["svc_backup"]
reason = "Backup appliance cannot use a gMSA; 32-character password, rotation ticket OPS-1182"
expires = 2027-03-31

Expired acceptances turn back into findings, acceptances that no longer match anything are reported as stale, and the ones expiring within 30 days are listed. Every check page links here, because accepting a risk is the documented alternative to fixing it.