This Skill usesprojectas the scope, checking each repository'sdefault branchone by one to see which branch policies are applied, and generates a checklist against the Microsoft Learn security baseline. It only calls GET throughout, and does not create or modify any policies; for remediation it always provides web-portal steps for the user to handle themselves.
Project selection
-
If the user has provided a project name (for example "Contoso project"), use it directly and do not call the project list API.
-
If not provided, ask the user to provide it once.
-
If it is still not provided, list the projects for the user to choose from:
curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/_apis/projects?%24top=500&api-version=7.1" -
When the user wants "all projects", repeat the steps for each project in the list; if there are more than 20 projects, first inform them of the number and confirm once.
-
Do not continue until the user provides or selects a project.
When the user specifies repositories, audit only those repositories; when none are specified, audit all repositories in the project.
Connection and authentication
Always use curl to call the Azure DevOps REST API directly, with each command carrying its own authentication parameters.
- Organization: Read the environment variable
AZURE_DEVOPS_ORG(organization name, for examplecontoso). If it is not set, ask the user once; if the user gives a URL, extract the organization name fromhttps://dev.azure.com/{org}orhttps://{org}.visualstudio.com. - Token: Send the PAT with Basic authentication:
-u ":$AZURE_DEVOPS_PAT"; if onlyAZURE_DEVOPS_EXT_PATis set (the variable used by the Azure CLI azure-devops extension), use that instead. When switching to a Microsoft Entra ID token, replace it with-H "Authorization: Bearer $AZURE_DEVOPS_TOKEN". If neither is set, ask the user to setAZURE_DEVOPS_PATin their own terminal and retry, then stop. Read the token only from the environment variable: do not ask the user to paste the token into the conversation, and do not print it or write it to a file. - PAT permission scopes: Code (Read); listing projects additionally requires Project and Team (Read). This Skill does not need any write permissions.
- Encoding: The project name in the path must be URL-encoded (write spaces as
%20); in the query string, write$as%24(for example%24top) to avoid shell expansion. - Errors: HTTP 401, 403, 203, or an HTML login page in the response means the token is invalid, expired, or lacks sufficient permissions; report it and stop. HTTP 404 means the organization, project, or repository is incorrect.
- Windows PowerShell: Call
curl.exe(not thecurlalias), and write the environment variable as$env:AZURE_DEVOPS_PAT; PowerShell does not support<<'EOF', so first write the JSON body to a temporary file, then send it with--data-binary "@body.json".
Audit baseline
Based on Secure repositories and pull requests 's "Security baseline for most teams" and Branch policies. Match by policy type GUID (type.id), not by displayName .
Baseline items(Not passed ❌):
| # | Check | Policy type (GUID) | Pass condition |
|---|---|---|---|
| 1 | At least two reviewers | Minimum number of reviewers(fa4e907d-c16b-4a4c-9dfa-4906e5d171dd) |
Enabled and blocking,minimumApproverCount ≥ 2 |
| 2 | Creator votes are not counted | Same as above | creatorVoteCounts is false(UI:Allow requestors to approve their own changes closed) |
| 3 | Requires re-approval after push | Same as above | resetOnSourcePush、requireVoteOnLastIteration or requireVoteOnEachIteration either is true |
| 4 | Link work items | Work item linking(40e92b44-2fe1-4dd6-b3d8-74a9c21d0c6e) |
Enabled and blocking |
| 5 | Build validation | Build(0609b952-1397-4640-95ec-e00a01b2c241) |
At least one enabled and blocking |
| 6 | Comments must be resolved | Comment requirements(c6a1889d-b943-4856-b76f-9e46bb6b0df2) |
Enabled and blocking |
Reference items(Determined by team needs, not counted as a failure; if not configured, mark ❌ and note "Reference"):
| # | Check | Policy type (GUID) | Determination method |
|---|---|---|---|
| 7 | The most recent pusher cannot approve | Minimum number of reviewers | blockLastPusherVote is true(UI:Prohibit the most recent pusher from approving their own changes; Learn recommends enabling when separation of duties is needed) |
| 8 | Restrict merge types | Require a merge strategy(fa4e907d-c16b-4a4c-9dfa-4916e5d171ab) |
List allowed types:allowNoFastForward(Basic merge)、allowSquash(Squash)、allowRebase(Rebase and fast-forward)、allowRebaseMerge(Semi-linear merge). Learn explains that this is a history governance setting, not a direct security control |
| 9 | Required reviewers for sensitive paths | Required reviewers(fd2167ab-b0be-447a-8ec8-39368250530e) |
Enabled and blocking,settings.filenamePatterns has a value (no path filter means it applies to all PRs) |
| 10 | Advanced Security status check | Status(cbdc66da-9728-4af8-aada-9a5a32e4a226) |
settings.statusGenre is AdvancedSecurity,settings.statusName is NewHighAndCritical or AllHighAndCritical; mark ❓ when Advanced Security is not enabled for the repository |
Determination principles:
- Ignore
isDeletedistruepolicies;isEnabledisfalsepolicies are treated as nonexistent.isBlockingisfalse(Optional in the UI) is not counted as a pass baseline item. - Older policies may not have newer settings fields (for example
requireVoteOnLastIteration、blockLastPusherVote); when a field does not exist, treat it asfalse(unchecked). - When there are multiple policies of the same type, use the strictest enabled, blocking one for the determination.
- For a Build policy, when
settings.manualQueueOnlyistrue(Trigger is Manual) orsettings.filenamePatternshas a value (only triggers for specific paths), it still counts as passed, but note it in the details. allowDownvotesistruemeans it can still be completed even if someone votes to reject; note it in the details.
Steps
-
Confirm policy type GUID. Get the policy types available to the project and confirm that all GUIDs in the table above exist; when a GUID is not in the list, mark that column ❓:
curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/policy/types?api-version=7.1"value[].idis a GUID,value[].displayNameis used only for display. -
List repositories:
curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/git/repositories?api-version=7.1"isDisabledistrue: mark "Disabled" and skip.- If the response does not have the
defaultBranchfield (an empty repository does not return this field): mark "Empty repository" and skip. - For the rest, record
id、name、defaultBranch(for examplerefs/heads/main) andwebUrl。
-
Get policies applied to the default branch. Call once per repository, with
repositoryIdandrefName; the response contains policies set only on this branch as well as project-level (cross-repository) policies:curl -sS -u ":$AZURE_DEVOPS_PAT" -D - \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/git/policy/configurations?repositoryId={repositoryId}&refName={defaultBranch}&api-version=7.1"-D -to check the response header: when there isx-ms-continuationtoken, add its value to&continuationToken={token}and fetch the next page until this header is absent.- Each policy has
type.id、isEnabled、isBlocking、isDeletedandsettings;settings.scope[]whererepositoryIdisnullare project-level policies, annotated as "Project-level" in the details. - When the branch name contains special characters,
refNamemust be URL-encoded. - Returning 0 records means this default branch has no policies at all; include it in "Repositories with no policies".
-
Evaluation. Determine each repository item by item according to the "Audit Baseline": ✅ Pass, ❌ Fail, ❓ Cannot determine (for example, the policy type does not exist, or the repository does not have Advanced Security enabled).
-
Items requiring manual confirmation. The API cannot reliably determine who has permission to bypass policies; ask the user to check on the web page and list it in the results:
- Bypass policies when completing pull requests and Bypass policies when pushing: keep it reserved for a small number of trusted administrators.
- Force push (rewrite history, delete branches and tags)、Edit policies、Manage permissions。
- Location:Project settings > Repositories > Repositories > Security(repository level), or Repos > Branches > branch's … > Branch security(branch level).
- Basis:Secure repositories and pull requests 's "Limit branch permissions" and Branch policies 's "Allow policy bypass when needed".
Examples: "Audit the branch policies of all repositories in the Contoso project", "Does the main branch protection of MyApp comply with Microsoft recommendations?", "Which repositories' default branches have no policies at all?"
Present the results
Remove refs/heads/from the branch name; link the repository name to webUrl. Present in order:
-
Summary: project, number of repositories audited, number that pass all baseline items, number with no policies at all, number of skipped empty repositories and disabled repositories.
-
Baseline checklist(one row per repository):
Repository Default branch Reviewers ≥2 Creator not counted Re-approve after push Work items Build validation Comment resolution MyApp main ✅ ❌ ✅ ✅ ❌ ✅ The reviewers column may include the actual number of people, for example
❌(1)。 -
Reference items table: repository, recent pushers cannot approve, allowed merge types, path reviewers, Advanced Security status checks.
-
Repositories with no policies: list the names and default branches; if there are none, explicitly state "The default branch of every repository has at least one policy." Also list empty repositories and disabled repositories.
-
Remediation steps: group by failed items, and list the affected repositories and web page actions:
- Open policy settings:Repos > Branches > default branch's … > Branch policies, or Project settings > Repositories > Repositories > Policies > Branch Policies > Branches.
- Reviewers: open Require a minimum number of reviewers,Minimum number of reviewers and set it to
2, uncheck Allow requestors to approve their own changes, in When new changes are pushed select Require at least one approval on the last iteration or Reset all approval votes。 - Work item: open Check for linked work items, select Required。
- Build validation: in Build validation click +, select the pipeline used for validation, for Trigger select Automatic,Policy requirement select Required。
- Comment: open Check for comment resolution, select Required。
- Advanced Security status check: first complete the Advanced Security scan and build validation, then in Status checks click +,Status to check fill in
AdvancedSecurity/NewHighAndCritical。 - When multiple repositories are all missing the same item, it is recommended to set up a cross-repository policy:Project settings > Repositories > Policies tab > Branch Policies of + > Protect the default branch of each repository(see Set Git repository settings)。
Each set of fixes includes the corresponding Microsoft Learn link; finally, note that this report did not modify any settings.
-
Needs manual confirmation: list the permission check bypass items from step 5.