Azure DevOps Pipelines
bomdrift runs in Azure Pipelines and posts a single upserted PR thread per pull request.
Quickstart
Copy examples/azure-devops/azure-pipelines.yml
to your repo root and add a secret pipeline variable named
BOMDRIFT_API_TOKEN containing a PAT with the Code (Read & Write)
scope.
What the job does
- Installs Rust + bomdrift + Syft on the
ubuntu-latestagent. - Generates a CycloneDX SBOM for the PR target branch and the PR head.
- Renders the diff to markdown with
bomdrift diff --platform azure-devops. - Looks up the existing bomdrift PR thread (by the
<!-- bomdrift:diff -->marker) and either creates a new thread or updates the existing comment.
Tokens & permissions
| Variable | Scope | Why |
|---|---|---|
BOMDRIFT_API_TOKEN | PAT, Code (Read & Write) | Creating / updating PR threads. |
The default System.AccessToken is not used because most
organizations don’t grant it permission to create PR threads.
CLI auto-detection
Setting TF_BUILD=true (Azure Pipelines sets this on every job)
auto-selects --platform azure-devops when the flag is omitted.
BUILD_REPOSITORY_URI is honored as a --repo-url fallback. Note
that this variable is empty for some local debug runs; passing
--repo-url explicitly is fine.
Suppressions
The supported, no-infrastructure-required flow is the manual baseline
edit: run bomdrift baseline add locally and commit the result to
your PR branch.
Comment-driven suppression (advanced, v0.9.5+)
Trade-off up front. Comment-driven suppression turns a reviewer comment like
/bomdrift suppress GHSA-...into an automatic baseline edit. To wire it up safely you need to operate a small public webhook handler. The manual flow above is supported and lower-risk; reach for the bridge only when the zero-click UX is worth running a service.
examples/azure-devops/comment-bridge/ ships a Cloudflare Worker
reference implementation that enforces five security guards:
- Webhook secret verification (
X-Bomdrift-Bridge-Secretcustom header, constant-time compare). - Event-type filter (
ms.vss-code.git-pullrequest-comment-eventonly). - Project-UUID allowlist.
- Commenter-permission lookup (Contributors team membership).
- PR-context guard (active PR targeting the protected main branch).
When the guards pass, the worker POSTs to
/_apis/pipelines/{id}/runs with BOMDRIFT_NOTE_BODY as a template
parameter. The example azure-pipelines.yml defines a conditional
bomdrift_suppress stage gated on that parameter; it runs
bomdrift baseline add --from-comment "$BOMDRIFT_NOTE_BODY" and
pushes the resulting baseline edit back to the PR’s source branch.
Normal PR-build runs leave the parameter empty so the suppress stage
is skipped.
The full threat model and deployment guide live in
examples/azure-devops/comment-bridge/README.md.
The same logic ports to Vercel / Netlify / AWS Lambda — see
vercel-equivalent.md.
Input reference
The example pipeline exposes optional template parameters that are
forwarded to bomdrift diff as CLI flags. Override them at queue
time (Run pipeline, Variables / Parameters dialog) or pin defaults
by editing the parameters: block in the YAML; you can also wire a
pipeline variable through a parameter for org-wide defaults. Unset
parameters contribute zero CLI arguments, so the default invocation
matches the bare v0.9 template exactly.
This mirrors the GitHub Action input
surface;
descriptions are abridged from action.yml.
VEX
vex(newline-separated paths to OpenVEX documents), each forwarded as a repeated--vex <path>.emit_vex(path), write a fresh OpenVEX document derived from the diff (--emit-vex <path>).vex_author, author identity recorded on emitted VEX (--vex-author <author>).vex_default_justification, default OpenVEXnot_affectedjustification (--vex-default-justification <id>).
License policy
allow_licenses, comma-separated SPDX expressions to allow (--allow-licenses).deny_licenses, comma-separated SPDX expressions to deny (--deny-licenses).allow_exception, SPDX exception identifiers to allow insideWITHclauses (--allow-exception).deny_exception, SPDX exception identifiers to deny (--deny-exception).allow_ambiguous_licenses(boolean), set totrueto treat unresolvable expressions as allowed (--allow-ambiguous-licenses).
Enrichment toggles
no_epss(boolean), set totrueto disable EPSS enrichment (--no-epss).no_kev(boolean), set totrueto disable CISA KEV enrichment (--no-kev).no_registry(boolean), set totrueto disable registry and maintainer enrichment (--no-registry).fail_on_epss, exit 2 when any new advisory has an EPSS score at or above this threshold (0.0 to 1.0,--fail-on-epss <FLOAT>).
Calibration
recently_published_days, window (days) for the recently-published maintainer-age signal.typosquat_similarity_threshold, Damerau-Levenshtein similarity threshold (0.0 to 1.0).young_maintainer_days, age threshold (days) below which a maintainer is flagged as young.cache_ttl_hours, TTL (hours) for the on-disk enrichment cache.multi_major_delta, major-version delta at or above which a version jump is flagged as multi-major (default 2, minimum 1).
Attestation
before_attestation, OCI reference for the cosign attestation covering the before SBOM (--before-attestation <oci-ref>).after_attestation, OCI reference for the after SBOM (--after-attestation <oci-ref>).cosign_identity, regex matched against the cosign certificate identity (--cosign-identity <regex>).cosign_issuer, OIDC issuer URL used for keyless cosign verification (--cosign-issuer <url>).require_attestation(boolean), set totrueto fail the diff when either side is missing a verified attestation (--require-attestation).
Plugins
plugin(newline-separated paths to plugin manifests, i.e.plugin.toml), each forwarded as a repeated--plugin <path>.