What a read-only reviewer can actually check in a diff. Verified 2026-09 against learn.microsoft.com/azure/devops/pipelines and OWASP Top 10:2025 A03.
Dependency and lockfile diffs
- Read the lockfile, not only the manifest. A one-line manifest bump can add dozens of transitive packages; the lockfile diff shows them. Flag new top-level and new transitive names in the review.
- Check the source. A dependency pointing at a git URL, a fork, a personal registry or a tarball instead of the official registry is a finding until someone explains it.
- Check the jump. A major-version bump in a security-relevant library (auth, crypto, serialization, templating) needs the changelog read; call it out when the PR does not mention it.
- Postinstall and build scripts in a newly added package run on every developer machine and build agent. Name them.
- Typosquats: compare the new name against the package it resembles (
lodahs,reqeusts,@types/node-fetchvsnode-fetch). Confirm the publisher and the repository link. - Removed pins — a lockfile deleted, a range widened from
1.2.3to^1.2.3,--no-saveinstalls in a script — reduce reproducibility; treat asmediumwhen the dependency reaches a sink.
azure-pipelines.yml and templates
| Look for | Why it matters | Fix |
|---|---|---|
Secrets as plain variables: values, or echoed in a script |
Plain variables are visible in logs and to anyone who can read the repository | Variable group linked to Key Vault; reference as $(name); never echo it |
A secret used in a script without an env: mapping |
Secret variables are deliberately not injected into the environment, so the script silently gets an empty value — or the author works around it by making the variable non-secret | Map explicitly: env: { API_KEY: $(apiKey) } |
persistCredentials: true on checkout |
Leaves a usable repository token in the work folder for every later step, including third-party tasks | Omit it (the default is false); when a step really must push, scope it to that job |
Unpinned task or container versions (@0, :latest, an unpinned action) |
The build's behaviour changes without a commit | Pin the major version (UseDotNet@2) and digest-pin images |
trigger: or pr: widened to all branches, or a pr trigger on a fork with secrets available |
A fork PR that runs with secrets is remote code execution against your pipeline | Explicit branch lists; no secrets for fork builds; deployment behind an environment with approvals |
| Scripts fetched from the internet and piped to a shell | Unreviewed code runs on the agent with repository credentials | Vendor the script or pin it by digest |
System.AccessToken handed to a script, or a service connection widened |
The token acts as the build identity across the project | Grant per job, only where needed, and say which permission the connection holds |
| Deployment steps without an environment or approval | Anyone who can merge can reach production | Use environments with checks; keep the approval in the pipeline, not in convention |
Self-hosted agents add one more: anything written to the work folder or global tool cache persists for the next pipeline that runs on that agent.
What a review cannot settle
Runtime configuration, network egress rules, secret-store contents and agent-pool membership are invisible in a diff. Put them under Needs verification with the check that would settle them — "confirm the prod-deploy service connection is scoped to the resource group, not the subscription" — rather than asserting either way.