azure-pipelines-ci-doctor
Audit any azure-pipelines.yml for waste, cost leaks, and
security gaps. Eight rules. One command. MIT. Sister project to
ci-doctor
(GitHub Actions),
gitlab-ci-doctor
(GitLab CI), and
bitbucket-ci-doctor
(Bitbucket Pipelines).
npm npmjs.com/package/azure-pipelines-ci-doctor | source github.com/depmedicdev-byte/azure-pipelines-ci-doctor | try it in-browser scanner
Try it in 5 seconds
$ npx azure-pipelines-ci-doctor # audit current repo $ npx azure-pipelines-ci-doctor --markdown # PR-comment friendly output $ npx azure-pipelines-ci-doctor --json # machine-readable $ npx azure-pipelines-ci-doctor --severity=warn # warn + error only $ npx azure-pipelines-ci-doctor --rules # list checks $ npx azure-pipelines-ci-doctor --demo # smoke-test against bundled bad pipeline
Exit code is 1 when there are error-level findings, so it drops into an Azure DevOps pipeline (or a pre-commit hook) without ceremony.
The 8 rules
| Rule | Severity | What it catches |
|---|---|---|
expensive-vm-image | warn / cost | pool.vmImage = macOS-latest (~10x) or windows-latest (~2x) without commands that need it. Move to ubuntu-latest. |
container-no-pin | warn / security | Job container.image is not pinned to image@sha256:<digest> — floating tags break reproducibility and are a supply-chain risk. |
missing-timeout-in-minutes | warn / cost | Job has no timeoutInMinutes. Default is 60 min on Microsoft-hosted, 360 min on self-hosted — a hang can burn the whole window. |
missing-cache | warn / cost | Job runs npm/pip/maven/gradle/cargo/go/bundler install but no Cache@2 task — redownloads everything every run. |
wide-trigger | warn / cost | trigger: or pr: is unscoped (no branch / path filter). Pipeline runs on every push including draft PRs and doc-only changes. |
inline-secret-leak | warn / security | Step uses $(SECRET_NAME) macro that expands inline in build logs. Pass via env: mapping and reference $env:NAME / $NAME instead. |
legacy-task-version | warn / reliability | Built-in task pinned to outdated major version (e.g. UseNode@1). Loses Node 20 runtime + current bug fixes. |
unbounded-parallelism | warn / cost | strategy.parallel >= 5 or matrix >= 5 legs without maxParallel. Will starve other pipelines on shared agent pools. |
Drop into a pipeline
- stage: AuditPipeline
jobs:
- job: cidoctor
timeoutInMinutes: 5
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseNode@2
inputs:
version: '20.x'
- script: npx --yes azure-pipelines-ci-doctor --markdown > $(Build.ArtifactStagingDirectory)/ci-doctor.md
- publish: $(Build.ArtifactStagingDirectory)/ci-doctor.md
artifact: ci-doctor-report
Why a fourth one?
Azure DevOps Pipelines have their own quirks: free Microsoft-hosted parallelism
is limited (1 free job for new orgs) and the
macOS-latest image bills at 10x the
ubuntu-latest rate. Every minute spent on the wrong image, every
hour spent on a missing timeout, eats your budget directly.
And: the $(SECRET_NAME) macro syntax is famously easy to leak into
logs — even Microsoft's own docs flag this as the #1 secret-handling
mistake. The same hygiene the other three CIs have, applied here.
Audit one repo right now
The in-browser scanner takes a paste, runs all 8 rules, and shows the report inline. Nothing leaves your browser.
Open the scanner View source